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
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
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"
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';
188211import dayjs from ' dayjs' ;
189212import AES from ' crypto-js/aes' ;
190213import cronParser from ' cron-parser' ;
214+ import dbStorage from ' @/db/storage' ;
191215import encUtf8 from ' crypto-js/enc-utf8' ;
192216import browser from ' webextension-polyfill' ;
193217import 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
208236const { t } = useI18n ();
209237const toast = useToast ();
@@ -227,6 +255,7 @@ const backupState = reactive({
227255const 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}
347390async 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
422474onMounted (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