Browse Source

feat: update dashboard UI and chrome manifest file

Ahmad Kholid 2 năm trước cách đây
mục cha
commit
35dd15a054

+ 9 - 0
src/background/BackgroundEventsListeners.js

@@ -0,0 +1,9 @@
+import BackgroundUtils from './BackgroundUtils';
+
+class BackgroundEventsListeners {
+  static onActionClicked() {
+    BackgroundUtils.openDashboard();
+  }
+}
+
+export default BackgroundEventsListeners;

+ 38 - 0
src/background/BackgroundUtils.js

@@ -0,0 +1,38 @@
+import browser from 'webextension-polyfill';
+
+class BackgroundUtils {
+  static async openDashboard(url) {
+    const tabUrl = browser.runtime.getURL(
+      `/newtab.html#${typeof url === 'string' ? url : ''}`
+    );
+
+    try {
+      const [tab] = await browser.tabs.query({
+        url: browser.runtime.getURL('/newtab.html'),
+      });
+
+      if (tab) {
+        await browser.tabs.update(tab.id, { url: tabUrl, active: true });
+        await browser.windows.update(tab.windowId, {
+          focused: true,
+          state: 'maximized',
+        });
+
+        if (tabUrl.includes('workflows/')) {
+          await browser.tabs.reload(tab.id);
+        }
+      } else {
+        browser.windows.create({
+          url: tabUrl,
+          focused: true,
+          type: 'popup',
+          state: 'maximized',
+        });
+      }
+    } catch (error) {
+      console.error(error);
+    }
+  }
+}
+
+export default BackgroundUtils;

+ 7 - 30
src/background/index.js

@@ -18,6 +18,8 @@ import WorkflowState from './WorkflowState';
 import WorkflowEngine from './workflowEngine/engine';
 import blocksHandler from './workflowEngine/blocksHandler';
 import WorkflowLogger from './WorkflowLogger';
+import BackgroundUtils from './BackgroundUtils';
+import BackgroundEventsListeners from './BackgroundEventsListeners';
 
 const validateUrl = (str) => str?.startsWith('http');
 const flattenTeamWorkflows = (workflows) =>
@@ -207,32 +209,6 @@ async function updateRecording(callback) {
 
   await browser.storage.local.set({ recording });
 }
-async function openDashboard(url) {
-  const tabOptions = {
-    active: true,
-    url: browser.runtime.getURL(
-      `/newtab.html#${typeof url === 'string' ? url : ''}`
-    ),
-  };
-
-  try {
-    const [tab] = await browser.tabs.query({
-      url: browser.runtime.getURL('/newtab.html'),
-    });
-
-    if (tab) {
-      await browser.tabs.update(tab.id, tabOptions);
-
-      if (tabOptions.url.includes('workflows/')) {
-        await browser.tabs.reload(tab.id);
-      }
-    } else {
-      browser.tabs.create(tabOptions);
-    }
-  } catch (error) {
-    console.error(error);
-  }
-}
 async function checkVisitWebTriggers(tabId, tabUrl) {
   const visitWebTriggers = await browserStorage.get('visitWebTriggers');
   if (!visitWebTriggers || visitWebTriggers.length === 0) return;
@@ -279,7 +255,7 @@ browser.webNavigation.onCompleted.addListener(
   }
 );
 browser.commands.onCommand.addListener((name) => {
-  if (name === 'open-dashboard') openDashboard();
+  if (name === 'open-dashboard') BackgroundUtils.openDashboard();
 });
 browser.webNavigation.onCommitted.addListener(
   ({ frameId, tabId, url, transitionType }) => {
@@ -433,6 +409,7 @@ browser.alarms.onAlarm.addListener(async ({ name }) => {
     }
   }
 });
+browser.action.onClicked.addListener(BackgroundEventsListeners.onActionClicked);
 
 const contextMenu =
   BROWSER_TYPE === 'firefox' ? browser.menus : browser.contextMenus;
@@ -470,7 +447,7 @@ if (browser.notifications && browser.notifications.onClicked) {
   browser.notifications.onClicked.addListener((notificationId) => {
     if (notificationId.startsWith('logs')) {
       const { 1: logId } = notificationId.split(':');
-      openDashboard(`/logs/${logId}`);
+      BackgroundUtils.openDashboard(`/logs/${logId}`);
     }
   });
 }
@@ -570,7 +547,7 @@ message.on('fetch:text', (url) => {
   return fetch(url).then((response) => response.text());
 });
 message.on('open:dashboard', async (url) => {
-  await openDashboard(url);
+  await BackgroundUtils.openDashboard(url);
 
   return Promise.resolve(true);
 });
@@ -660,7 +637,7 @@ message.on('workflow:added', ({ workflowId, teamId, source = 'community' }) => {
           active: true,
         });
       } else {
-        openDashboard(`${path}?permission=true`);
+        BackgroundUtils.openDashboard(`${path}?permission=true`);
       }
     });
 });

+ 5 - 5
src/components/newtab/logs/LogsFilters.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="flex items-center mb-6 space-x-4">
+  <div class="flex items-center mb-6 md:space-x-4 flex-wrap">
     <ui-input
       id="search-input"
       :model-value="filters.query"
@@ -7,10 +7,10 @@
         shortcut['action:search'].readable
       })`"
       prepend-icon="riSearch2Line"
-      class="flex-1"
+      class="w-6/12 md:w-auto md:flex-1"
       @change="updateFilters('query', $event)"
     />
-    <div class="flex items-center workflow-sort">
+    <div class="flex items-center workflow-sort w-5/12 ml-4 md:ml-0 md:w-auto">
       <ui-button
         icon
         class="rounded-r-none border-gray-300 border-r"
@@ -30,7 +30,7 @@
         </option>
       </ui-select>
     </div>
-    <ui-popover>
+    <ui-popover class="mt-4 md:mt-0">
       <template #trigger>
         <ui-button>
           <v-remixicon name="riFilter2Line" class="mr-2 -ml-1" />
@@ -68,7 +68,7 @@
         </ui-select>
       </div>
     </ui-popover>
-    <ui-button @click="$emit('clear')">
+    <ui-button class="ml-4 md:ml-0 mt-4 md:mt-0" @click="$emit('clear')">
       <v-remixicon name="riDeleteBin7Line" class="mr-2 -ml-1" />
       <span>
         {{ t('log.clearLogs.title') }}

+ 6 - 6
src/components/newtab/logs/LogsHistory.vue

@@ -8,8 +8,8 @@
     <v-remixicon name="riArrowLeftLine" class="mr-2" />
     {{ t('log.goBack', { name: parentLog.name }) }}
   </router-link>
-  <div class="flex items-start">
-    <div class="flex-1">
+  <div class="flex items-start flex-col-reverse lg:flex-row">
+    <div class="lg:flex-1 w-full lg:w-auto">
       <div class="rounded-lg bg-gray-900 dark:bg-gray-800 text-gray-100 dark">
         <div
           class="border-b px-4 pt-4 flex items-center text-gray-200 pb-4 mb-4"
@@ -47,7 +47,7 @@
           <ui-popover trigger-width class="mr-4">
             <template #trigger>
               <ui-button>
-                <span>Export logs</span>
+                <span> Export <span class="hidden lg:block">logs</span> </span>
                 <v-remixicon name="riArrowDropDownLine" class="ml-2 -mr-1" />
               </ui-button>
             </template>
@@ -173,9 +173,9 @@
       </div>
       <div
         v-if="currentLog.history.length >= 25"
-        class="flex items-center justify-between mt-4"
+        class="lg:flex lg:items-center lg:justify-between mt-4"
       >
-        <div>
+        <div class="mb-4 lg:mb-0">
           {{ t('components.pagination.text1') }}
           <select v-model="pagination.perPage" class="p-1 rounded-md bg-input">
             <option
@@ -201,7 +201,7 @@
     </div>
     <div
       v-if="state.itemId && activeLog"
-      class="w-4/12 ml-8 rounded-lg bg-gray-900 dark:bg-gray-800 text-gray-100 dark"
+      class="w-full lg:w-4/12 lg:ml-8 mb-4 lg:mb-0 rounded-lg bg-gray-900 dark:bg-gray-800 text-gray-100 dark"
     >
       <div class="p-4 relative">
         <v-remixicon

+ 8 - 4
src/components/newtab/shared/SharedLogsTable.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="logs-table">
+  <div class="logs-table overflow-x-auto scroll">
     <transition-expand>
       <div v-if="state.selected.length > 0" class="border-x border-t px-4 py-2">
         <ui-button @click="stopSelectedWorkflow"> Stop selected </ui-button>
@@ -26,8 +26,8 @@
               </router-link>
             </td>
             <td
-              class="log-time w-2/12 dark:text-gray-200"
               :title="t('log.duration')"
+              class="log-time w-2/12 dark:text-gray-200"
             >
               <v-remixicon name="riTimerLine"></v-remixicon>
               <span>{{
@@ -73,7 +73,10 @@
               {{ log.name }}
             </router-link>
           </td>
-          <td class="log-time w-3/12 dark:text-gray-200">
+          <td
+            class="log-time w-3/12 dark:text-gray-200"
+            style="min-width: 200px"
+          >
             <v-remixicon
               :title="t('log.startedDate')"
               name="riCalendarLine"
@@ -84,8 +87,9 @@
             </span>
           </td>
           <td
-            class="log-time w-2/12 dark:text-gray-200"
             :title="t('log.duration')"
+            class="log-time w-2/12 dark:text-gray-200"
+            style="min-width: 85px"
           >
             <v-remixicon name="riTimerLine"></v-remixicon>
             <span>{{ countDuration(log.startedAt, log.endedAt) }}</span>

+ 6 - 1
src/components/newtab/storage/StorageCredentials.vue

@@ -6,7 +6,12 @@
       prepend-icon="riSearch2Line"
     />
     <div class="flex-grow"></div>
-    <ui-button variant="accent" @click="addState.show = true">
+    <ui-button
+      variant="accent"
+      style="min-width: 120px"
+      class="ml-4"
+      @click="addState.show = true"
+    >
       {{ t('credential.add') }}
     </ui-button>
   </div>

+ 45 - 31
src/components/newtab/storage/StorageTables.vue

@@ -6,40 +6,47 @@
       prepend-icon="riSearch2Line"
     />
     <div class="flex-grow"></div>
-    <ui-button variant="accent" @click="state.showAddTable = true">
+    <ui-button
+      variant="accent"
+      class="ml-4"
+      style="min-width: 120px"
+      @click="state.showAddTable = true"
+    >
       {{ t('storage.table.add') }}
     </ui-button>
   </div>
-  <ui-table
-    item-key="id"
-    :headers="tableHeaders"
-    :items="items"
-    :search="state.query"
-    class="w-full mt-4"
-  >
-    <template #item-name="{ item }">
-      <router-link
-        :to="`/storage/tables/${item.id}`"
-        class="w-full block"
-        style="min-height: 29px"
-      >
-        {{ item.name }}
-      </router-link>
-    </template>
-    <template #item-createdAt="{ item }">
-      {{ formatDate(item.createdAt) }}
-    </template>
-    <template #item-modifiedAt="{ item }">
-      {{ formatDate(item.modifiedAt) }}
-    </template>
-    <template #item-actions="{ item }">
-      <v-remixicon
-        name="riDeleteBin7Line"
-        class="cursor-pointer"
-        @click="deleteTable(item)"
-      />
-    </template>
-  </ui-table>
+  <div class="overflow-x-auto w-full scroll">
+    <ui-table
+      item-key="id"
+      :headers="tableHeaders"
+      :items="items"
+      :search="state.query"
+      class="w-full mt-4"
+    >
+      <template #item-name="{ item }">
+        <router-link
+          :to="`/storage/tables/${item.id}`"
+          class="w-full block"
+          style="min-height: 29px"
+        >
+          {{ item.name }}
+        </router-link>
+      </template>
+      <template #item-createdAt="{ item }">
+        {{ formatDate(item.createdAt) }}
+      </template>
+      <template #item-modifiedAt="{ item }">
+        {{ formatDate(item.modifiedAt) }}
+      </template>
+      <template #item-actions="{ item }">
+        <v-remixicon
+          name="riDeleteBin7Line"
+          class="cursor-pointer"
+          @click="deleteTable(item)"
+        />
+      </template>
+    </ui-table>
+  </div>
   <storage-edit-table v-model="state.showAddTable" @save="saveTable" />
 </template>
 <script setup>
@@ -68,17 +75,24 @@ const tableHeaders = [
     text: t('common.name'),
     attrs: {
       class: 'w-4/12',
+      style: 'min-width: 120px',
     },
   },
   {
     align: 'center',
     value: 'createdAt',
     text: t('storage.table.createdAt'),
+    attrs: {
+      style: 'min-width: 200px',
+    },
   },
   {
     align: 'center',
     value: 'modifiedAt',
     text: t('storage.table.modifiedAt'),
+    attrs: {
+      style: 'min-width: 200px',
+    },
   },
   {
     value: 'rowsCount',

+ 6 - 1
src/components/newtab/storage/StorageVariables.vue

@@ -6,7 +6,12 @@
       prepend-icon="riSearch2Line"
     />
     <div class="flex-grow"></div>
-    <ui-button variant="accent" @click="editState.show = true">
+    <ui-button
+      variant="accent"
+      style="min-width: 125px"
+      class="ml-4"
+      @click="editState.show = true"
+    >
       Add variable
     </ui-button>
   </div>

+ 18 - 13
src/manifest.chrome.json

@@ -1,13 +1,10 @@
 {
-  "manifest_version": 2,
+  "manifest_version": 3,
   "name": "Automa",
+  "action": {},
   "background": {
-    "scripts": ["background.bundle.js"],
-    "persistent": false
-  },
-  "browser_action": {
-    "default_popup": "popup.html",
-    "default_icon": "icon-128.png"
+    "service_worker": "background.bundle.js",
+    "type": "module"
   },
   "icons": {
     "128": "icon-128.png"
@@ -52,15 +49,23 @@
     "alarms",
     "storage",
     "debugger",
+    "scripting",
     "webNavigation",
-    "unlimitedStorage",
+    "unlimitedStorage"
+  ],
+  "host_permissions": [
     "<all_urls>"
   ],
   "web_accessible_resources": [
-    "/elementSelector.css",
-    "/Inter-roman-latin.var.woff2",
-    "/icon-128.png",
-    "/locales/*",
-    "elementSelector.bundle.js"
+    {
+      "resources": [
+        "/elementSelector.css",
+        "/Inter-roman-latin.var.woff2",
+        "/icon-128.png",
+        "/locales/*",
+        "elementSelector.bundle.js"
+      ],
+      "matches": ["<all_urls>"]
+    }
   ]
 }

+ 2 - 1
src/newtab/pages/Logs.vue

@@ -34,7 +34,7 @@
         </template>
       </shared-logs-table>
     </div>
-    <div class="flex items-center justify-between mt-4">
+    <div class="md:flex md:items-center md:justify-between mt-4">
       <div>
         {{ t('components.pagination.text1') }}
         <select v-model="pagination.perPage" class="p-1 rounded-md bg-input">
@@ -48,6 +48,7 @@
         v-model="pagination.currentPage"
         :per-page="pagination.perPage"
         :records="filteredLogs.length"
+        class="mt-4 md:mt-0"
       />
     </div>
     <ui-card

+ 23 - 10
src/newtab/pages/Packages.vue

@@ -4,7 +4,7 @@
       {{ $t('common.packages') }}
     </h1>
     <div class="mt-8 flex items-start">
-      <div class="w-60">
+      <div class="w-60 mr-8 hidden lg:block">
         <ui-button
           class="w-full"
           variant="accent"
@@ -25,15 +25,26 @@
           </ui-list-item>
         </ui-list>
       </div>
-      <div class="flex-1 ml-8">
-        <div class="flex items-center">
-          <ui-input
-            v-model="state.query"
-            prepend-icon="riSearch2Line"
-            :placeholder="t('common.search')"
-          />
+      <div class="flex-1">
+        <div class="flex items-center flex-wrap">
+          <div class="w-full flex items-center md:w-auto">
+            <ui-input
+              v-model="state.query"
+              :placeholder="t('common.search')"
+              class="flex-1"
+              prepend-icon="riSearch2Line"
+            />
+            <ui-button
+              variant="accent"
+              class="ml-4 lg:hidden"
+              @click="addState.show = true"
+            >
+              <v-remixicon name="riAddLine" class="mr-2 -ml-1" />
+              <span>{{ t('common.packages') }}</span>
+            </ui-button>
+          </div>
           <div class="flex-grow" />
-          <div class="flex items-center workflow-sort">
+          <div class="flex items-center workflow-sort mt-4 lg:mt-0">
             <ui-button
               icon
               class="rounded-r-none border-gray-300 dark:border-gray-700 border-r"
@@ -52,7 +63,9 @@
             </ui-select>
           </div>
         </div>
-        <div class="mt-8 grid gap-4 grid-cols-3 2xl:grid-cols-4">
+        <div
+          class="mt-8 grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4"
+        >
           <ui-card
             v-for="pkg in packages"
             :key="pkg.id"

+ 52 - 44
src/newtab/pages/ScheduledWorkflow.vue

@@ -10,53 +10,59 @@
         :placeholder="t('common.search')"
       />
       <div class="flex-grow" />
-      <ui-button @click="scheduleState.showModal = true">
-        <v-remixicon name="riAddLine" class="-ml mr-2" />
+      <ui-button
+        class="ml-4"
+        style="min-width: 210px"
+        @click="scheduleState.showModal = true"
+      >
+        <v-remixicon name="riAddLine" class="-ml-1 mr-2" />
         Schedule workflow
       </ui-button>
     </div>
-    <ui-table
-      :headers="tableHeaders"
-      :items="triggers"
-      item-key="id"
-      class="w-full mt-8"
-    >
-      <template #item-name="{ item }">
-        <router-link
-          v-if="item.path"
-          :to="item.path"
-          class="block h-full w-full"
-          style="min-height: 20px"
-        >
-          {{ item.name }}
-        </router-link>
-        <span v-else>
-          {{ item.name }}
-        </span>
-      </template>
-      <template #item-schedule="{ item }">
-        <p v-tooltip="{ content: item.scheduleDetail, allowHTML: true }">
-          {{ item.schedule }}
-        </p>
-      </template>
-      <template #item-active="{ item }">
-        <v-remixicon
-          v-if="item.active"
-          class="text-green-500 dark:text-green-400 inline-block"
-          name="riCheckLine"
-        />
-        <span v-else></span>
-      </template>
-      <template #item-action="{ item }">
-        <button
-          v-tooltip="t('scheduledWorkflow.refresh')"
-          class="rounded-md text-gray-600 dark:text-gray-300"
-          @click="refreshSchedule(item.id)"
-        >
-          <v-remixicon name="riRefreshLine" />
-        </button>
-      </template>
-    </ui-table>
+    <div class="overflow-x-auto w-full scroll">
+      <ui-table
+        :headers="tableHeaders"
+        :items="triggers"
+        item-key="id"
+        class="w-full mt-8"
+      >
+        <template #item-name="{ item }">
+          <router-link
+            v-if="item.path"
+            :to="item.path"
+            class="block h-full w-full"
+            style="min-height: 20px"
+          >
+            {{ item.name }}
+          </router-link>
+          <span v-else>
+            {{ item.name }}
+          </span>
+        </template>
+        <template #item-schedule="{ item }">
+          <p v-tooltip="{ content: item.scheduleDetail, allowHTML: true }">
+            {{ item.schedule }}
+          </p>
+        </template>
+        <template #item-active="{ item }">
+          <v-remixicon
+            v-if="item.active"
+            class="text-green-500 dark:text-green-400 inline-block"
+            name="riCheckLine"
+          />
+          <span v-else></span>
+        </template>
+        <template #item-action="{ item }">
+          <button
+            v-tooltip="t('scheduledWorkflow.refresh')"
+            class="rounded-md text-gray-600 dark:text-gray-300"
+            @click="refreshSchedule(item.id)"
+          >
+            <v-remixicon name="riRefreshLine" />
+          </button>
+        </template>
+      </ui-table>
+    </div>
     <ui-modal
       v-model="scheduleState.showModal"
       title="Workflow Triggers"
@@ -163,6 +169,7 @@ const tableHeaders = [
     text: t('common.name'),
     attrs: {
       class: 'w-3/12',
+      style: 'min-width: 200px',
     },
   },
   {
@@ -170,6 +177,7 @@ const tableHeaders = [
     text: t('scheduledWorkflow.schedule.title'),
     attrs: {
       class: 'w-4/12',
+      style: 'min-width: 200px',
     },
   },
   {

+ 16 - 1
src/newtab/pages/Settings.vue

@@ -2,7 +2,7 @@
   <div class="container pt-8 pb-4">
     <h1 class="text-2xl font-semibold mb-10">{{ t('common.settings') }}</h1>
     <div class="flex items-start">
-      <ui-list class="w-64 mr-12 space-y-2 sticky top-8">
+      <ui-list class="w-64 mr-12 hidden md:block space-y-2 sticky top-8">
         <router-link
           v-for="menu in menus"
           :key="menu.id"
@@ -26,6 +26,15 @@
         </router-link>
       </ui-list>
       <div class="settings-content flex-1">
+        <ui-select
+          :model-value="$route.path"
+          class="w-full mb-4 md:hidden"
+          @change="onSelectChanged"
+        >
+          <option v-for="menu in menus" :key="menu.id" :value="menu.path">
+            {{ t(`settings.menu.${menu.id}`) }}
+          </option>
+        </ui-select>
         <router-view />
       </div>
     </div>
@@ -33,8 +42,10 @@
 </template>
 <script setup>
 import { useI18n } from 'vue-i18n';
+import { useRouter } from 'vue-router';
 
 const { t } = useI18n();
+const router = useRouter();
 
 const menus = [
   { id: 'general', path: '/settings', icon: 'riSettings3Line' },
@@ -43,4 +54,8 @@ const menus = [
   { id: 'shortcuts', path: '/shortcuts', icon: 'riKeyboardLine' },
   { id: 'about', path: '/about', icon: 'riInformationLine' },
 ];
+
+function onSelectChanged(value) {
+  router.push(value);
+}
 </script>

+ 81 - 33
src/newtab/pages/Workflows.vue

@@ -4,7 +4,7 @@
       {{ t('common.workflow', 2) }}
     </h1>
     <div class="flex items-start mt-8">
-      <div class="w-60 sticky top-8">
+      <div class="w-60 sticky top-8 hidden lg:block">
         <div class="flex w-full">
           <ui-button
             :title="shortcut['action:new'].readable"
@@ -132,42 +132,90 @@
         />
       </div>
       <div
-        class="flex-1 workflows-list ml-8"
+        class="flex-1 workflows-list lg:ml-8"
         style="min-height: calc(100vh - 8rem)"
         @dblclick="clearSelectedWorkflows"
       >
-        <div class="flex items-center">
-          <ui-input
-            id="search-input"
-            v-model="state.query"
-            :placeholder="`${t(`common.search`)}... (${
-              shortcut['action:search'].readable
-            })`"
-            prepend-icon="riSearch2Line"
-          />
+        <div class="flex items-center flex-wrap">
+          <div class="flex items-center w-full md:w-auto">
+            <ui-input
+              id="search-input"
+              v-model="state.query"
+              class="flex-1 md:w-auto"
+              :placeholder="`${t(`common.search`)}... (${
+                shortcut['action:search'].readable
+              })`"
+              prepend-icon="riSearch2Line"
+            />
+            <ui-popover>
+              <template #trigger>
+                <ui-button variant="accent" class="md:hidden ml-4">
+                  <v-remixicon name="riAddLine" class="mr-2 -ml-1" />
+                  <span>{{ t('common.workflow') }}</span>
+                </ui-button>
+              </template>
+              <ui-list class="space-y-1">
+                <ui-list-item
+                  v-close-popover
+                  class="cursor-pointer"
+                  @click="addWorkflowModal.show = true"
+                >
+                  {{ t('workflow.new') }}
+                </ui-list-item>
+                <ui-list-item
+                  v-close-popover
+                  class="cursor-pointer"
+                  @click="openImportDialog"
+                >
+                  {{ t('workflow.import') }}
+                </ui-list-item>
+                <ui-list-item
+                  v-close-popover
+                  class="cursor-pointer"
+                  @click="addHostedWorkflow"
+                >
+                  {{ t('workflow.host.add') }}
+                </ui-list-item>
+              </ui-list>
+            </ui-popover>
+          </div>
           <div class="flex-grow"></div>
-          <span v-tooltip:bottom.group="t('workflow.backupCloud')" class="mr-4">
-            <ui-button tag="router-link" to="/backup" class="inline-block" icon>
-              <v-remixicon name="riUploadCloud2Line" />
-            </ui-button>
-          </span>
-          <div class="flex items-center workflow-sort">
-            <ui-button
-              icon
-              class="rounded-r-none border-gray-300 dark:border-gray-700 border-r"
-              @click="
-                state.sortOrder = state.sortOrder === 'asc' ? 'desc' : 'asc'
-              "
+          <div class="w-full md:w-auto flex items-center mt-4 md:mt-0">
+            <span
+              v-tooltip:bottom.group="t('workflow.backupCloud')"
+              class="mr-4"
             >
-              <v-remixicon
-                :name="state.sortOrder === 'asc' ? 'riSortAsc' : 'riSortDesc'"
-              />
-            </ui-button>
-            <ui-select v-model="state.sortBy" :placeholder="t('sort.sortBy')">
-              <option v-for="sort in sorts" :key="sort" :value="sort">
-                {{ t(`sort.${sort}`) }}
-              </option>
-            </ui-select>
+              <ui-button
+                tag="router-link"
+                to="/backup"
+                class="inline-block"
+                icon
+              >
+                <v-remixicon name="riUploadCloud2Line" />
+              </ui-button>
+            </span>
+            <div class="flex items-center workflow-sort flex-1">
+              <ui-button
+                icon
+                class="rounded-r-none border-gray-300 dark:border-gray-700 border-r"
+                @click="
+                  state.sortOrder = state.sortOrder === 'asc' ? 'desc' : 'asc'
+                "
+              >
+                <v-remixicon
+                  :name="state.sortOrder === 'asc' ? 'riSortAsc' : 'riSortDesc'"
+                />
+              </ui-button>
+              <ui-select
+                v-model="state.sortBy"
+                :placeholder="t('sort.sortBy')"
+                class="flex-1"
+              >
+                <option v-for="sort in sorts" :key="sort" :value="sort">
+                  {{ t(`sort.${sort}`) }}
+                </option>
+              </ui-select>
+            </div>
           </div>
         </div>
         <ui-tab-panels v-model="state.activeTab" class="flex-1 mt-6">
@@ -422,6 +470,6 @@ onMounted(() => {
   @apply rounded-l-none !important;
 }
 .workflows-container {
-  @apply grid gap-4 grid-cols-3 2xl:grid-cols-4;
+  @apply grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4;
 }
 </style>

+ 23 - 21
src/newtab/pages/storage/Tables.vue

@@ -22,15 +22,15 @@
         <v-remixicon name="riDeleteBin7Line" />
       </ui-button>
     </div>
-    <div class="flex items-center mb-4">
+    <div class="flex items-center flex-wrap mb-4">
       <ui-input
         v-model="state.query"
         :placeholder="t('common.search')"
         prepend-icon="riSearch2Line"
+        class="w-full md:w-auto mb-4 md:mb-0"
       />
       <div class="flex-grow" />
-      <div class="flex-1"></div>
-      <ui-button class="ml-4" @click="editTable">
+      <ui-button class="md:ml-4" @click="editTable">
         <v-remixicon name="riPencilLine" class="mr-2 -ml-1" />
         <span>Edit table</span>
       </ui-button>
@@ -54,27 +54,29 @@
         </ui-list>
       </ui-popover>
     </div>
-    <ui-table
-      :headers="table.header"
-      :items="rows"
-      :search="state.query"
-      item-key="id"
-      class="w-full"
-    >
-      <template #item-action="{ item }">
-        <v-remixicon
-          title="Delete row"
-          class="cursor-pointer"
-          name="riDeleteBin7Line"
-          @click="deleteRow(item)"
-        />
-      </template>
-    </ui-table>
+    <div class="overflow-x-auto w-full scroll">
+      <ui-table
+        :headers="table.header"
+        :items="rows"
+        :search="state.query"
+        item-key="id"
+        class="w-full"
+      >
+        <template #item-action="{ item }">
+          <v-remixicon
+            title="Delete row"
+            class="cursor-pointer"
+            name="riDeleteBin7Line"
+            @click="deleteRow(item)"
+          />
+        </template>
+      </ui-table>
+    </div>
     <div
       v-if="table.body && table.body.length >= 10"
-      class="flex items-center justify-between mt-4"
+      class="flex flex-col md:flex-row md:items-center md:justify-between mt-4"
     >
-      <div>
+      <div class="mb-4 md:mb-0">
         {{ t('components.pagination.text1') }}
         <select v-model="pagination.perPage" class="p-1 rounded-md bg-input">
           <option

+ 1 - 0
webpack.config.js

@@ -70,6 +70,7 @@ const options = {
   },
   chromeExtensionBoilerplate: {
     notHotReload: [
+      'background',
       'webService',
       'contentScript',
       'recordWorkflow',