implement backup/restore

This commit is contained in:
Maxi Quoß
2022-02-21 02:09:25 +01:00
parent bbfc21ae3d
commit cb8b987ab5
4 changed files with 98 additions and 37 deletions

View File

@@ -36,7 +36,7 @@ class WSConsumer(AsyncWebsocketConsumer):
}
)
async def disconnect(self, code):
async def disconnect(self, _):
await self.channel_layer.group_discard("wol", self.channel_name)
await self.remove_visitor()
await self.channel_layer.group_send(
@@ -86,6 +86,11 @@ class WSConsumer(AsyncWebsocketConsumer):
"type": "scan_network",
"message": await self.scan_network()
}))
elif received["type"] == "backup":
await self.send(text_data=json.dumps({
"type": "backup",
"message": await self.get_all_devices()
}))
async def send_group(self, event):
await self.send(json.dumps(event["message"]))
@@ -170,31 +175,32 @@ class WSConsumer(AsyncWebsocketConsumer):
p = Port.objects.get(number=port["number"])
obj.port.remove(p)
if data["cron"]["enabled"]:
cron_value = data["cron"]["value"].strip().split(" ")
if not len(cron_value) == 5:
return
minute, hour, dom, month, dow = cron_value
schedule, _ = CrontabSchedule.objects.get_or_create(
minute=minute,
hour=hour,
day_of_week=dow,
day_of_month=dom,
month_of_year=month
)
PeriodicTask.objects.update_or_create(
name=data["name"],
defaults={
"crontab": schedule,
"task": "wol.tasks.scheduled_wake",
"args": json.dumps([data["id"]]),
"enabled": True
}
)
else:
for task in PeriodicTask.objects.filter(name=data["name"], task="wol.tasks.scheduled_wake"):
task.enabled = False
task.save()
if data.get("cron"):
if data["cron"]["enabled"]:
cron_value = data["cron"]["value"].strip().split(" ")
if not len(cron_value) == 5:
return
minute, hour, dom, month, dow = cron_value
schedule, _ = CrontabSchedule.objects.get_or_create(
minute=minute,
hour=hour,
day_of_week=dow,
day_of_month=dom,
month_of_year=month
)
PeriodicTask.objects.update_or_create(
name=data["name"],
defaults={
"crontab": schedule,
"task": "wol.tasks.scheduled_wake",
"args": json.dumps([data["id"]]),
"enabled": True
}
)
else:
for task in PeriodicTask.objects.filter(name=data["name"], task="wol.tasks.scheduled_wake"):
task.enabled = False
task.save()
@database_sync_to_async
def update_port(self, data):

View File

@@ -59,6 +59,15 @@
btnScan.disabled = false;
btnScanSpinner.classList.add("d-none");
btnScanText.innerText = "Scan";
} else if (currentMessage.type == "backup") {
// download backup file
const now = new Date();
const fileName = `upsnap_backup_${now.toISOString()}.json`
const a = document.createElement("a");
const file = new Blob([JSON.stringify(currentMessage.message)], { type: "text/plain" });
a.href = URL.createObjectURL(file);
a.download = fileName;
a.click();
}
})
})

View File

@@ -182,7 +182,7 @@
* /4 * * * (Wake every 4 hours)
0 9 * * 1-5 (Wake from Mo-Fr at 9 a.m.)
</pre>
<p class="mb-0">Read more about <a href="https://linux.die.net/man/5/crontab" target="_blank">valid syntax</a> here or <a href="https://crontab.guru/" target="_blank">generate</a> it. Expressions starting with "@..." are not supported.</p>
<p class="mb-0">Read more about <a href="https://linux.die.net/man/5/crontab" target="_blank">valid syntax here</a> or <a href="https://crontab.guru/" target="_blank">generate</a> it. Expressions starting with "@..." are not supported.</p>
</div>
</form>
</div>

View File

@@ -44,6 +44,34 @@
const dev = settings.scan_network[i];
updateDevice(dev);
}
const restoreFromFile = (e) => {
let file = e.target.files[0];
let reader = new FileReader();
reader.readAsText(file);
reader.onload = e => {
let data = JSON.parse(e.target.result);
if (Array.isArray(data)) {
// v2 file restore
data.forEach((device) => {
updateDevice(device)
})
} else {
// v1 file restore
for (const [key, value] of Object.entries(data)) {
value["mac"] = key
updateDevice(value)
}
}
}
}
function backupToFile() {
store.sendMessage({
type: "backup"
})
}
</script>
<nav class="navbar navbar-expand-sm navbar-light bg-light">
@@ -196,20 +224,34 @@
</div>
<div class="row">
<div class="col-sm">
<div class="mb-3">
<div class="form-check">
<label class="form-check-label" for="flexCheckDefault">
Enable notifications
</label>
<input class="form-check-input" type="checkbox" value="" id="flexCheckDefault" bind:checked={settings.notifications}>
</div>
<div class="form-check">
<label class="form-check-label" for="flexCheckDefault">
Enable notifications
</label>
<input class="form-check-input" type="checkbox" value="" id="flexCheckDefault" bind:checked={settings.notifications}>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="submit" form="settingsForm" class="btn btn-outline-success">Save</button>
<div class="row mb-3">
<div class="col-auto ms-auto">
<button type="submit" form="settingsForm" class="btn btn-outline-success">Save</button>
</div>
</div>
<h5 class="fw-bold">Backup/Restore</h5>
<div class="callout callout-info">
<p class="mb-0">Backup file structure has changed in v2. You can still restore both versions with this file upload.</p>
</div>
<div class="mb-3">
<label for="inputRestore" class="form-label">Restore from .json</label>
<input id="inputRestore" type="file" class="form-control" accept=".json" on:change={(e) => restoreFromFile(e)}>
</div>
<div class="mb-3">
<button type="button" class="btn btn-secondary" on:click={backupToFile}>
<i class="fa-solid fa-download me-2"></i>
Export .json
</button>
</div>
</div>
</div>
</div>
@@ -242,6 +284,10 @@
border-left-width: 0.25rem;
border-radius: 0.25rem;
&.callout-info {
border-left-color: $info;
}
&.callout-danger {
border-left-color: $danger;
}