Skip to content

Commit 43f2503

Browse files
committed
feat: export tables & variables (#659)
1 parent a43cb3c commit 43f2503

File tree

4 files changed

+148
-80
lines changed

4 files changed

+148
-80
lines changed

src/background/BackgroundEventsListeners.js

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
import browser from 'webextension-polyfill';
22
import { initElementSelector } from '@/newtab/utils/elementSelector';
33
import dayjs from 'dayjs';
4+
import dbStorage from '@/db/storage';
45
import cronParser from 'cron-parser';
56
import BackgroundUtils from './BackgroundUtils';
67
import BackgroundWorkflowTriggers from './BackgroundWorkflowTriggers';
78

89
async function handleScheduleBackup() {
910
try {
10-
const { scheduleLocalBackup, workflows } = await browser.storage.local.get([
11-
'scheduleLocalBackup',
11+
const { localBackupSettings, workflows } = await browser.storage.local.get([
12+
'localBackupSettings',
1213
'workflows',
1314
]);
14-
if (!scheduleLocalBackup) return;
15+
if (!localBackupSettings) return;
1516

1617
const workflowsData = Object.values(workflows || []).reduce(
1718
(acc, workflow) => {
@@ -29,26 +30,40 @@ async function handleScheduleBackup() {
2930
},
3031
[]
3132
);
32-
const base64 = btoa(JSON.stringify(workflowsData));
33+
34+
const payload = {
35+
workflows: JSON.stringify(workflowsData),
36+
};
37+
38+
if (localBackupSettings.includedItems.includes('storage:table')) {
39+
const tables = await dbStorage.tablesItems.toArray();
40+
payload.storageTables = JSON.stringify(tables);
41+
}
42+
if (localBackupSettings.includedItems.includes('storage:variables')) {
43+
const variables = await dbStorage.variables.toArray();
44+
payload.storageVariables = JSON.stringify(variables);
45+
}
46+
47+
const base64 = btoa(JSON.stringify(payload));
3348
const filename = `${
34-
scheduleLocalBackup.folderName ? `${scheduleLocalBackup.folderName}/` : ''
49+
localBackupSettings.folderName ? `${localBackupSettings.folderName}/` : ''
3550
}${dayjs().format('DD-MMM-YYYY--HH-mm')}.json`;
3651

3752
await browser.downloads.download({
3853
filename,
3954
url: `data:application/json;base64,${base64}`,
4055
});
4156
await browser.storage.local.set({
42-
scheduleLocalBackup: {
43-
...scheduleLocalBackup,
57+
localBackupSettings: {
58+
...localBackupSettings,
4459
lastBackup: Date.now(),
4560
},
4661
});
4762

4863
const expression =
49-
scheduleLocalBackup.schedule === 'custom'
50-
? scheduleLocalBackup.customSchedule
51-
: scheduleLocalBackup.schedule;
64+
localBackupSettings.schedule === 'custom'
65+
? localBackupSettings.customSchedule
66+
: localBackupSettings.schedule;
5267
const parsedExpression = cronParser.parseExpression(expression).next();
5368
if (!parsedExpression) return;
5469

src/components/ui/UiSelect.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
{{ label }}
1010
</slot>
1111
</label>
12-
<div class="ui-select__content relative block flex w-full items-center">
12+
<div class="ui-select__content relative flex w-full items-center">
1313
<v-remixicon
1414
v-if="prependIcon"
1515
size="20"

src/locales/en/newtab.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@
153153
"needSignin": "You need to sign in first",
154154
"backup": {
155155
"button": "Backup",
156+
"settings": "Backup settings",
156157
"encrypt": "Encrypt with password",
157158
"schedule": "Schedule local backup"
158159
},

src/newtab/pages/settings/SettingsBackup.vue

Lines changed: 121 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -81,30 +81,50 @@
8181
{{ t('settings.backupWorkflows.backup.encrypt') }}
8282
</ui-checkbox>
8383
<div class="flex items-center gap-2">
84-
<ui-button class="flex-1" @click="backupWorkflows">
85-
{{ t('settings.backupWorkflows.backup.button') }}
86-
</ui-button>
8784
<ui-popover @close="registerScheduleBackup">
8885
<template #trigger>
8986
<ui-button
90-
v-tooltip="t('settings.backupWorkflows.backup.schedule')"
87+
v-tooltip="t('settings.backupWorkflows.backup.settings')"
9188
icon
9289
:class="{ 'text-primary': localBackupSchedule.schedule }"
9390
>
94-
<v-remixicon name="riCalendarLine" />
91+
<v-remixicon name="riSettings3Line" />
9592
</ui-button>
9693
</template>
97-
<div class="min-w-[14rem]">
98-
<p class="mb-2">
94+
<div class="w-64">
95+
<p class="mb-2 font-semibold">
96+
{{ t('settings.backupWorkflows.backup.settings') }}
97+
</p>
98+
<p>Also backup</p>
99+
<div class="flex mt-1 flex-col gap-2">
100+
<ui-checkbox
101+
v-for="item in BACKUP_ITEMS_INCLUDES"
102+
:key="item.id"
103+
:model-value="
104+
localBackupSchedule.includedItems.includes(item.id)
105+
"
106+
@change="
107+
$event
108+
? localBackupSchedule.includedItems.push(item.id)
109+
: localBackupSchedule.includedItems.splice(
110+
localBackupSchedule.includedItems.indexOf(item.id),
111+
1
112+
)
113+
"
114+
>
115+
{{ item.name }}
116+
</ui-checkbox>
117+
</div>
118+
<p class="mt-4">
99119
{{ t('settings.backupWorkflows.backup.schedule') }}
100120
</p>
101121
<template v-if="!downloadPermission.has.downloads">
102-
<p class="text-gray-600 dark:text-gray-300">
103-
Automa requires the "Downloads" permission for this feature to
104-
work
122+
<p class="text-gray-600 dark:text-gray-300 mt-1">
123+
Automa requires the "Downloads" permission for the schedule
124+
backup to work
105125
</p>
106126
<ui-button
107-
class="mt-4 w-full"
127+
class="mt-2 w-full"
108128
@click="downloadPermission.request()"
109129
>
110130
Allow "Downloads" permission
@@ -113,8 +133,7 @@
113133
<template v-else>
114134
<ui-select
115135
v-model="localBackupSchedule.schedule"
116-
label="Schedule"
117-
class="w-full"
136+
class="w-full mt-2"
118137
>
119138
<option value="">Never</option>
120139
<option
@@ -138,6 +157,7 @@
138157
</p>
139158
</template>
140159
<ui-input
160+
v-if="localBackupSchedule.schedule !== ''"
141161
v-model="localBackupSchedule.folderName"
142162
label="Folder name"
143163
class="w-full mt-2"
@@ -153,6 +173,9 @@
153173
</template>
154174
</div>
155175
</ui-popover>
176+
<ui-button class="flex-1" @click="backupWorkflows">
177+
{{ t('settings.backupWorkflows.backup.button') }}
178+
</ui-button>
156179
</div>
157180
</div>
158181
<div class="w-6/12 rounded-lg border p-4 dark:border-gray-700">
@@ -188,6 +211,7 @@ import { useToast } from 'vue-toastification';
188211
import dayjs from 'dayjs';
189212
import AES from 'crypto-js/aes';
190213
import cronParser from 'cron-parser';
214+
import dbStorage from '@/db/storage';
191215
import encUtf8 from 'crypto-js/enc-utf8';
192216
import browser from 'webextension-polyfill';
193217
import hmacSHA256 from 'crypto-js/hmac-sha256';
@@ -204,6 +228,10 @@ const BACKUP_SCHEDULES = {
204228
'0 8 * * *': 'Every day',
205229
'0 8 * * 0': 'Every week',
206230
};
231+
const BACKUP_ITEMS_INCLUDES = [
232+
{ id: 'storage:table', name: 'Storage tables' },
233+
{ id: 'storage:variables', name: 'Storage variables' },
234+
];
207235
208236
const { t } = useI18n();
209237
const toast = useToast();
@@ -227,6 +255,7 @@ const backupState = reactive({
227255
const localBackupSchedule = reactive({
228256
schedule: '',
229257
lastBackup: null,
258+
includedItems: [],
230259
customSchedule: '',
231260
folderName: 'automa-backup',
232261
});
@@ -249,7 +278,7 @@ async function registerScheduleBackup() {
249278
}
250279
251280
browser.storage.local.set({
252-
scheduleLocalBackup: toRaw(localBackupSchedule),
281+
localBackupSettings: toRaw(localBackupSchedule),
253282
});
254283
} catch (error) {
255284
console.error(error);
@@ -292,56 +321,70 @@ async function syncBackupWorkflows() {
292321
state.loadingSync = false;
293322
}
294323
}
295-
function backupWorkflows() {
296-
const workflows = workflowStore.getWorkflows.reduce((acc, workflow) => {
297-
if (workflow.isProtected) return acc;
298-
299-
delete workflow.$id;
300-
delete workflow.createdAt;
301-
delete workflow.data;
302-
delete workflow.isDisabled;
303-
delete workflow.isProtected;
304-
305-
acc.push(workflow);
306-
307-
return acc;
308-
}, []);
309-
const payload = {
310-
isProtected: state.encrypt,
311-
workflows: JSON.stringify(workflows),
312-
};
313-
const downloadFile = (data) => {
314-
const fileName = `automa-${dayjs().format('DD-MM-YYYY')}.json`;
315-
const blob = new Blob([JSON.stringify(data)], {
316-
type: 'application/json',
317-
});
318-
const objectUrl = URL.createObjectURL(blob);
319-
320-
fileSaver(fileName, objectUrl);
321-
322-
URL.revokeObjectURL(objectUrl);
323-
};
324-
325-
if (state.encrypt) {
326-
dialog.prompt({
327-
placeholder: t('common.password'),
328-
title: t('settings.backupWorkflows.title'),
329-
okText: t('settings.backupWorkflows.backup.button'),
330-
inputType: 'password',
331-
onConfirm: (password) => {
332-
const encryptedWorkflows = AES.encrypt(
333-
payload.workflows,
334-
password
335-
).toString();
336-
const hmac = hmacSHA256(encryptedWorkflows, password).toString();
337-
338-
payload.workflows = hmac + encryptedWorkflows;
339-
340-
downloadFile(payload);
341-
},
342-
});
343-
} else {
344-
downloadFile(payload);
324+
async function backupWorkflows() {
325+
try {
326+
const workflows = workflowStore.getWorkflows.reduce((acc, workflow) => {
327+
if (workflow.isProtected) return acc;
328+
329+
delete workflow.$id;
330+
delete workflow.createdAt;
331+
delete workflow.data;
332+
delete workflow.isDisabled;
333+
delete workflow.isProtected;
334+
335+
acc.push(workflow);
336+
337+
return acc;
338+
}, []);
339+
const payload = {
340+
isProtected: state.encrypt,
341+
workflows: JSON.stringify(workflows),
342+
};
343+
344+
if (localBackupSchedule.includedItems.includes('storage:table')) {
345+
const tables = await dbStorage.tablesItems.toArray();
346+
payload.storageTables = JSON.stringify(tables);
347+
}
348+
if (localBackupSchedule.includedItems.includes('storage:variables')) {
349+
const variables = await dbStorage.variables.toArray();
350+
payload.storageVariables = JSON.stringify(variables);
351+
}
352+
353+
const downloadFile = (data) => {
354+
const fileName = `automa-${dayjs().format('DD-MM-YYYY')}.json`;
355+
const blob = new Blob([JSON.stringify(data)], {
356+
type: 'application/json',
357+
});
358+
const objectUrl = URL.createObjectURL(blob);
359+
360+
fileSaver(fileName, objectUrl);
361+
362+
URL.revokeObjectURL(objectUrl);
363+
};
364+
365+
if (state.encrypt) {
366+
dialog.prompt({
367+
placeholder: t('common.password'),
368+
title: t('settings.backupWorkflows.title'),
369+
okText: t('settings.backupWorkflows.backup.button'),
370+
inputType: 'password',
371+
onConfirm: (password) => {
372+
const encryptedWorkflows = AES.encrypt(
373+
payload.workflows,
374+
password
375+
).toString();
376+
const hmac = hmacSHA256(encryptedWorkflows, password).toString();
377+
378+
payload.workflows = hmac + encryptedWorkflows;
379+
380+
downloadFile(payload);
381+
},
382+
});
383+
} else {
384+
downloadFile(payload);
385+
}
386+
} catch (error) {
387+
console.error(error);
345388
}
346389
}
347390
async function restoreWorkflows() {
@@ -360,7 +403,7 @@ async function restoreWorkflows() {
360403
const showMessage = (event) => {
361404
toast(
362405
t('settings.backupWorkflows.workflowsAdded', {
363-
count: event.workflows.length,
406+
count: Object.values(event).length,
364407
})
365408
);
366409
};
@@ -374,9 +417,18 @@ async function restoreWorkflows() {
374417
375418
reader.onload = ({ target }) => {
376419
const payload = parseJSON(target.result, null);
377-
378420
if (!payload) return;
379421
422+
const storageTables = parseJSON(payload.storageTables, null);
423+
if (Array.isArray(storageTables)) {
424+
dbStorage.tablesItems.bulkPut(storageTables);
425+
}
426+
427+
const storageVariables = parseJSON(payload.storageVariables, null);
428+
if (Array.isArray(storageVariables)) {
429+
dbStorage.variables.bulkPut(storageVariables);
430+
}
431+
380432
if (payload.isProtected) {
381433
dialog.prompt({
382434
placeholder: t('common.password'),
@@ -420,14 +472,14 @@ async function restoreWorkflows() {
420472
}
421473
422474
onMounted(async () => {
423-
const { lastBackup, lastSync, scheduleLocalBackup } =
475+
const { lastBackup, lastSync, localBackupSettings } =
424476
await browser.storage.local.get([
425477
'lastSync',
426478
'lastBackup',
427-
'scheduleLocalBackup',
479+
'localBackupSettings',
428480
]);
429481
430-
Object.assign(localBackupSchedule, scheduleLocalBackup || {});
482+
Object.assign(localBackupSchedule, localBackupSettings || {});
431483
432484
state.lastSync = lastSync;
433485
state.lastBackup = lastBackup;

0 commit comments

Comments
 (0)