Sfoglia il codice sorgente

feat: export tables & variables (#659)

Ahmad Kholid 1 anno fa
parent
commit
43f2503587

+ 25 - 10
src/background/BackgroundEventsListeners.js

@@ -1,17 +1,18 @@
 import browser from 'webextension-polyfill';
 import { initElementSelector } from '@/newtab/utils/elementSelector';
 import dayjs from 'dayjs';
+import dbStorage from '@/db/storage';
 import cronParser from 'cron-parser';
 import BackgroundUtils from './BackgroundUtils';
 import BackgroundWorkflowTriggers from './BackgroundWorkflowTriggers';
 
 async function handleScheduleBackup() {
   try {
-    const { scheduleLocalBackup, workflows } = await browser.storage.local.get([
-      'scheduleLocalBackup',
+    const { localBackupSettings, workflows } = await browser.storage.local.get([
+      'localBackupSettings',
       'workflows',
     ]);
-    if (!scheduleLocalBackup) return;
+    if (!localBackupSettings) return;
 
     const workflowsData = Object.values(workflows || []).reduce(
       (acc, workflow) => {
@@ -29,9 +30,23 @@ async function handleScheduleBackup() {
       },
       []
     );
-    const base64 = btoa(JSON.stringify(workflowsData));
+
+    const payload = {
+      workflows: JSON.stringify(workflowsData),
+    };
+
+    if (localBackupSettings.includedItems.includes('storage:table')) {
+      const tables = await dbStorage.tablesItems.toArray();
+      payload.storageTables = JSON.stringify(tables);
+    }
+    if (localBackupSettings.includedItems.includes('storage:variables')) {
+      const variables = await dbStorage.variables.toArray();
+      payload.storageVariables = JSON.stringify(variables);
+    }
+
+    const base64 = btoa(JSON.stringify(payload));
     const filename = `${
-      scheduleLocalBackup.folderName ? `${scheduleLocalBackup.folderName}/` : ''
+      localBackupSettings.folderName ? `${localBackupSettings.folderName}/` : ''
     }${dayjs().format('DD-MMM-YYYY--HH-mm')}.json`;
 
     await browser.downloads.download({
@@ -39,16 +54,16 @@ async function handleScheduleBackup() {
       url: `data:application/json;base64,${base64}`,
     });
     await browser.storage.local.set({
-      scheduleLocalBackup: {
-        ...scheduleLocalBackup,
+      localBackupSettings: {
+        ...localBackupSettings,
         lastBackup: Date.now(),
       },
     });
 
     const expression =
-      scheduleLocalBackup.schedule === 'custom'
-        ? scheduleLocalBackup.customSchedule
-        : scheduleLocalBackup.schedule;
+      localBackupSettings.schedule === 'custom'
+        ? localBackupSettings.customSchedule
+        : localBackupSettings.schedule;
     const parsedExpression = cronParser.parseExpression(expression).next();
     if (!parsedExpression) return;
 

+ 1 - 1
src/components/ui/UiSelect.vue

@@ -9,7 +9,7 @@
         {{ label }}
       </slot>
     </label>
-    <div class="ui-select__content relative block flex w-full items-center">
+    <div class="ui-select__content relative flex w-full items-center">
       <v-remixicon
         v-if="prependIcon"
         size="20"

+ 1 - 0
src/locales/en/newtab.json

@@ -153,6 +153,7 @@
       "needSignin": "You need to sign in first",
       "backup": {
         "button": "Backup",
+        "settings": "Backup settings",
         "encrypt": "Encrypt with password",
         "schedule": "Schedule local backup"
       },

+ 121 - 69
src/newtab/pages/settings/SettingsBackup.vue

@@ -81,30 +81,50 @@
           {{ t('settings.backupWorkflows.backup.encrypt') }}
         </ui-checkbox>
         <div class="flex items-center gap-2">
-          <ui-button class="flex-1" @click="backupWorkflows">
-            {{ t('settings.backupWorkflows.backup.button') }}
-          </ui-button>
           <ui-popover @close="registerScheduleBackup">
             <template #trigger>
               <ui-button
-                v-tooltip="t('settings.backupWorkflows.backup.schedule')"
+                v-tooltip="t('settings.backupWorkflows.backup.settings')"
                 icon
                 :class="{ 'text-primary': localBackupSchedule.schedule }"
               >
-                <v-remixicon name="riCalendarLine" />
+                <v-remixicon name="riSettings3Line" />
               </ui-button>
             </template>
-            <div class="min-w-[14rem]">
-              <p class="mb-2">
+            <div class="w-64">
+              <p class="mb-2 font-semibold">
+                {{ t('settings.backupWorkflows.backup.settings') }}
+              </p>
+              <p>Also backup</p>
+              <div class="flex mt-1 flex-col gap-2">
+                <ui-checkbox
+                  v-for="item in BACKUP_ITEMS_INCLUDES"
+                  :key="item.id"
+                  :model-value="
+                    localBackupSchedule.includedItems.includes(item.id)
+                  "
+                  @change="
+                    $event
+                      ? localBackupSchedule.includedItems.push(item.id)
+                      : localBackupSchedule.includedItems.splice(
+                          localBackupSchedule.includedItems.indexOf(item.id),
+                          1
+                        )
+                  "
+                >
+                  {{ item.name }}
+                </ui-checkbox>
+              </div>
+              <p class="mt-4">
                 {{ t('settings.backupWorkflows.backup.schedule') }}
               </p>
               <template v-if="!downloadPermission.has.downloads">
-                <p class="text-gray-600 dark:text-gray-300">
-                  Automa requires the "Downloads" permission for this feature to
-                  work
+                <p class="text-gray-600 dark:text-gray-300 mt-1">
+                  Automa requires the "Downloads" permission for the schedule
+                  backup to work
                 </p>
                 <ui-button
-                  class="mt-4 w-full"
+                  class="mt-2 w-full"
                   @click="downloadPermission.request()"
                 >
                   Allow "Downloads" permission
@@ -113,8 +133,7 @@
               <template v-else>
                 <ui-select
                   v-model="localBackupSchedule.schedule"
-                  label="Schedule"
-                  class="w-full"
+                  class="w-full mt-2"
                 >
                   <option value="">Never</option>
                   <option
@@ -138,6 +157,7 @@
                   </p>
                 </template>
                 <ui-input
+                  v-if="localBackupSchedule.schedule !== ''"
                   v-model="localBackupSchedule.folderName"
                   label="Folder name"
                   class="w-full mt-2"
@@ -153,6 +173,9 @@
               </template>
             </div>
           </ui-popover>
+          <ui-button class="flex-1" @click="backupWorkflows">
+            {{ t('settings.backupWorkflows.backup.button') }}
+          </ui-button>
         </div>
       </div>
       <div class="w-6/12 rounded-lg border p-4 dark:border-gray-700">
@@ -188,6 +211,7 @@ import { useToast } from 'vue-toastification';
 import dayjs from 'dayjs';
 import AES from 'crypto-js/aes';
 import cronParser from 'cron-parser';
+import dbStorage from '@/db/storage';
 import encUtf8 from 'crypto-js/enc-utf8';
 import browser from 'webextension-polyfill';
 import hmacSHA256 from 'crypto-js/hmac-sha256';
@@ -204,6 +228,10 @@ const BACKUP_SCHEDULES = {
   '0 8 * * *': 'Every day',
   '0 8 * * 0': 'Every week',
 };
+const BACKUP_ITEMS_INCLUDES = [
+  { id: 'storage:table', name: 'Storage tables' },
+  { id: 'storage:variables', name: 'Storage variables' },
+];
 
 const { t } = useI18n();
 const toast = useToast();
@@ -227,6 +255,7 @@ const backupState = reactive({
 const localBackupSchedule = reactive({
   schedule: '',
   lastBackup: null,
+  includedItems: [],
   customSchedule: '',
   folderName: 'automa-backup',
 });
@@ -249,7 +278,7 @@ async function registerScheduleBackup() {
     }
 
     browser.storage.local.set({
-      scheduleLocalBackup: toRaw(localBackupSchedule),
+      localBackupSettings: toRaw(localBackupSchedule),
     });
   } catch (error) {
     console.error(error);
@@ -292,56 +321,70 @@ async function syncBackupWorkflows() {
     state.loadingSync = false;
   }
 }
-function backupWorkflows() {
-  const workflows = workflowStore.getWorkflows.reduce((acc, workflow) => {
-    if (workflow.isProtected) return acc;
-
-    delete workflow.$id;
-    delete workflow.createdAt;
-    delete workflow.data;
-    delete workflow.isDisabled;
-    delete workflow.isProtected;
-
-    acc.push(workflow);
-
-    return acc;
-  }, []);
-  const payload = {
-    isProtected: state.encrypt,
-    workflows: JSON.stringify(workflows),
-  };
-  const downloadFile = (data) => {
-    const fileName = `automa-${dayjs().format('DD-MM-YYYY')}.json`;
-    const blob = new Blob([JSON.stringify(data)], {
-      type: 'application/json',
-    });
-    const objectUrl = URL.createObjectURL(blob);
-
-    fileSaver(fileName, objectUrl);
-
-    URL.revokeObjectURL(objectUrl);
-  };
-
-  if (state.encrypt) {
-    dialog.prompt({
-      placeholder: t('common.password'),
-      title: t('settings.backupWorkflows.title'),
-      okText: t('settings.backupWorkflows.backup.button'),
-      inputType: 'password',
-      onConfirm: (password) => {
-        const encryptedWorkflows = AES.encrypt(
-          payload.workflows,
-          password
-        ).toString();
-        const hmac = hmacSHA256(encryptedWorkflows, password).toString();
-
-        payload.workflows = hmac + encryptedWorkflows;
-
-        downloadFile(payload);
-      },
-    });
-  } else {
-    downloadFile(payload);
+async function backupWorkflows() {
+  try {
+    const workflows = workflowStore.getWorkflows.reduce((acc, workflow) => {
+      if (workflow.isProtected) return acc;
+
+      delete workflow.$id;
+      delete workflow.createdAt;
+      delete workflow.data;
+      delete workflow.isDisabled;
+      delete workflow.isProtected;
+
+      acc.push(workflow);
+
+      return acc;
+    }, []);
+    const payload = {
+      isProtected: state.encrypt,
+      workflows: JSON.stringify(workflows),
+    };
+
+    if (localBackupSchedule.includedItems.includes('storage:table')) {
+      const tables = await dbStorage.tablesItems.toArray();
+      payload.storageTables = JSON.stringify(tables);
+    }
+    if (localBackupSchedule.includedItems.includes('storage:variables')) {
+      const variables = await dbStorage.variables.toArray();
+      payload.storageVariables = JSON.stringify(variables);
+    }
+
+    const downloadFile = (data) => {
+      const fileName = `automa-${dayjs().format('DD-MM-YYYY')}.json`;
+      const blob = new Blob([JSON.stringify(data)], {
+        type: 'application/json',
+      });
+      const objectUrl = URL.createObjectURL(blob);
+
+      fileSaver(fileName, objectUrl);
+
+      URL.revokeObjectURL(objectUrl);
+    };
+
+    if (state.encrypt) {
+      dialog.prompt({
+        placeholder: t('common.password'),
+        title: t('settings.backupWorkflows.title'),
+        okText: t('settings.backupWorkflows.backup.button'),
+        inputType: 'password',
+        onConfirm: (password) => {
+          const encryptedWorkflows = AES.encrypt(
+            payload.workflows,
+            password
+          ).toString();
+          const hmac = hmacSHA256(encryptedWorkflows, password).toString();
+
+          payload.workflows = hmac + encryptedWorkflows;
+
+          downloadFile(payload);
+        },
+      });
+    } else {
+      downloadFile(payload);
+    }
+  } catch (error) {
+    console.error(error);
   }
 }
 async function restoreWorkflows() {
@@ -360,7 +403,7 @@ async function restoreWorkflows() {
       const showMessage = (event) => {
         toast(
           t('settings.backupWorkflows.workflowsAdded', {
-            count: event.workflows.length,
+            count: Object.values(event).length,
           })
         );
       };
@@ -374,9 +417,18 @@ async function restoreWorkflows() {
 
     reader.onload = ({ target }) => {
       const payload = parseJSON(target.result, null);
-
       if (!payload) return;
 
+      const storageTables = parseJSON(payload.storageTables, null);
+      if (Array.isArray(storageTables)) {
+        dbStorage.tablesItems.bulkPut(storageTables);
+      }
+
+      const storageVariables = parseJSON(payload.storageVariables, null);
+      if (Array.isArray(storageVariables)) {
+        dbStorage.variables.bulkPut(storageVariables);
+      }
+
       if (payload.isProtected) {
         dialog.prompt({
           placeholder: t('common.password'),
@@ -420,14 +472,14 @@ async function restoreWorkflows() {
 }
 
 onMounted(async () => {
-  const { lastBackup, lastSync, scheduleLocalBackup } =
+  const { lastBackup, lastSync, localBackupSettings } =
     await browser.storage.local.get([
       'lastSync',
       'lastBackup',
-      'scheduleLocalBackup',
+      'localBackupSettings',
     ]);
 
-  Object.assign(localBackupSchedule, scheduleLocalBackup || {});
+  Object.assign(localBackupSchedule, localBackupSettings || {});
 
   state.lastSync = lastSync;
   state.lastBackup = lastBackup;