Browse Source

feat: add scheduled workflow page

Ahmad Kholid 3 years ago
parent
commit
50645243a3

+ 20 - 0
src/assets/css/tailwind.css

@@ -63,6 +63,26 @@ select:focus,
   overflow: hidden;
   text-overflow: ellipsis;
 }
+
+
+.custom-table thead {
+  @apply bg-box-transparent;
+}
+.custom-table thead th {
+  @apply font-semibold;
+}
+.custom-table thead th:first-child {
+  @apply rounded-l-lg;
+}
+.custom-table thead th:last-child {
+  @apply rounded-r-lg;
+}
+.custom-table tbody {
+  @apply divide-y;
+}
+
+
+
 pre {
   font-size: 15px;
 }

+ 5 - 1
src/background/index.js

@@ -320,7 +320,11 @@ browser.alarms.onAlarm.addListener(async ({ name }) => {
   const currentWorkflow = await workflow.get(name);
   if (!currentWorkflow) return;
 
-  const { data } = findTriggerBlock(JSON.parse(currentWorkflow.drawflow)) || {};
+  const drawflow =
+    typeof currentWorkflow.drawflow === 'string'
+      ? parseJSON(currentWorkflow.drawflow, {})
+      : currentWorkflow.drawflow;
+  const { data } = findTriggerBlock(drawflow) || {};
   if (data && data.type === 'interval' && data.fixedDelay) {
     const workflowState = await workflow.states.get(
       ({ workflowId }) => name === workflowId

+ 6 - 0
src/components/newtab/app/AppSidebar.vue

@@ -109,6 +109,12 @@ const tabs = [
     path: '/workflows',
     shortcut: getShortcut('page:workflows', '/workflows'),
   },
+  {
+    id: 'schedule',
+    icon: 'riTimeLine',
+    path: '/schedule',
+    shortcut: getShortcut('page:schedule', '/triggers'),
+  },
   {
     id: 'collection',
     icon: 'riFolderLine',

+ 1 - 20
src/components/newtab/shared/SharedCard.vue

@@ -56,21 +56,12 @@
     <div class="flex items-center text-gray-600 dark:text-gray-200">
       <p class="flex-1">{{ state.date }}</p>
       <slot name="footer-content" />
-      <v-remixicon
-        v-if="state.triggerText"
-        v-tooltip="state.triggerText"
-        :class="{ 'ml-2': $slots['footer-content'] }"
-        name="riFlashlightLine"
-        size="20"
-      />
     </div>
   </ui-card>
 </template>
 <script setup>
-import { onMounted, shallowReactive } from 'vue';
-import { useI18n } from 'vue-i18n';
+import { shallowReactive } from 'vue';
 import dayjs from '@/lib/dayjs';
-import triggerText from '@/utils/triggerText';
 
 const props = defineProps({
   data: {
@@ -93,18 +84,8 @@ const props = defineProps({
 
 defineEmits(['execute', 'click', 'menuSelected']);
 
-const { t } = useI18n();
-
 const state = shallowReactive({
   triggerText: null,
   date: dayjs(props.data.createdAt).fromNow(),
 });
-
-onMounted(async () => {
-  const { trigger, id } = props.data;
-
-  if (!trigger) return;
-
-  state.triggerText = await triggerText(trigger, t, id);
-});
 </script>

+ 8 - 4
src/composable/shortcut.js

@@ -10,19 +10,23 @@ const defaultShortcut = {
   },
   'page:workflows': {
     id: 'page:workflows',
-    combo: 'option+2',
+    combo: 'option+w',
+  },
+  'page:schedule': {
+    id: 'page:schedule',
+    combo: 'option+t',
   },
   'page:collections': {
     id: 'page:collections',
-    combo: 'option+3',
+    combo: 'option+c',
   },
   'page:logs': {
     id: 'page:logs',
-    combo: 'option+4',
+    combo: 'option+l',
   },
   'page:settings': {
     id: 'page:settings',
-    combo: 'option+5',
+    combo: 'option+s',
   },
   'action:search': {
     id: 'action:search',

+ 2 - 0
src/lib/vRemixicon.js

@@ -19,6 +19,7 @@ import {
   riMoreLine,
   riStopLine,
   riSortDesc,
+  riTimeLine,
   riFlagLine,
   riGroupLine,
   riGuideLine,
@@ -132,6 +133,7 @@ export const icons = {
   riMoreLine,
   riStopLine,
   riSortDesc,
+  riTimeLine,
   riFlagLine,
   riGroupLine,
   riGuideLine,

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

@@ -5,6 +5,7 @@
     "collection": "Collection | Collections",
     "log": "Log | Logs",
     "block": "Block | Blocks",
+    "schedule": "Schedule",
     "folder": "Folder | Folders",
     "new": "New",
     "docs": "Documentation",

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

@@ -8,6 +8,19 @@
     "text": "Get started by reading the documentation or browsing workflows in the Automa Marketplace.",
     "marketplace": "Marketplace"
   },
+  "scheduledWorkflow": {
+    "title": "Scheduled workflows",
+    "nextRun": "Next run",
+    "active": "Active",
+    "refresh": "Refresh",
+    "schedule":{
+      "title": "Schedule",
+      "types": {
+        "general": "Every {time}",
+        "interval": "Every {time} minutes"
+      }
+    }
+  },
   "updateMessage": {
     "text1": "Automa has been updated to v{version},",
     "text2": "see what's new."

+ 194 - 0
src/newtab/pages/ScheduledWorkflow.vue

@@ -0,0 +1,194 @@
+<template>
+  <div class="container pt-8 pb-4">
+    <h1 class="text-2xl font-semibold mb-8 capitalize">
+      {{ t('scheduledWorkflow.title', 2) }}
+    </h1>
+    <ui-input
+      v-model="state.query"
+      prepend-icon="riSearch2Line"
+      :placeholder="t('common.search')"
+    />
+    <table class="w-full mt-4 custom-table">
+      <thead>
+        <tr class="text-left font-semibold">
+          <th class="w-3/12">{{ t('common.name') }}</th>
+          <th class="w-4/12">{{ t('scheduledWorkflow.schedule.title') }}</th>
+          <th>{{ t('scheduledWorkflow.nextRun') }}</th>
+          <th class="text-center">{{ t('scheduledWorkflow.active') }}</th>
+          <th></th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr v-for="trigger in triggers" :key="trigger.id" class="hoverable">
+          <td>
+            <router-link
+              :to="`/workflows/${trigger.workflowId}`"
+              class="block h-full w-full"
+              style="min-height: 20px"
+            >
+              {{ trigger.name }}
+            </router-link>
+          </td>
+          <td v-tooltip="{ content: trigger.scheduleDetail, allowHTML: true }">
+            {{ trigger.schedule }}
+          </td>
+          <td>
+            {{ trigger.nextRun }}
+          </td>
+          <td class="text-center">
+            <v-remixicon
+              v-if="trigger.active"
+              class="text-green-500 dark:text-green-400 inline-block"
+              name="riCheckLine"
+            />
+          </td>
+          <td class="text-right">
+            <button
+              v-tooltip="t('scheduledWorkflow.refresh')"
+              class="rounded-md text-gray-600 dark:text-gray-300"
+              @click="refreshSchedule(trigger.id)"
+            >
+              <v-remixicon name="riRefreshLine" />
+            </button>
+          </td>
+        </tr>
+      </tbody>
+    </table>
+  </div>
+</template>
+<script setup>
+import { onMounted, reactive, computed } from 'vue';
+import { useI18n } from 'vue-i18n';
+import dayjs from 'dayjs';
+import browser from 'webextension-polyfill';
+import { findTriggerBlock } from '@/utils/helper';
+import { registerWorkflowTrigger } from '@/utils/workflowTrigger';
+import Workflow from '@/models/workflow';
+
+const { t } = useI18n();
+
+const triggersData = {};
+const state = reactive({
+  query: '',
+  triggers: [],
+  activeTrigger: 'scheduled',
+});
+
+let rowId = 0;
+const scheduledTypes = ['interval', 'date', 'specific-day'];
+
+const triggers = computed(() =>
+  state.triggers.filter(({ name }) =>
+    name.toLocaleLowerCase().includes(state.query.toLocaleLowerCase())
+  )
+);
+
+function scheduleText(data) {
+  const text = {
+    schedule: '',
+    scheduleDetail: '',
+  };
+
+  switch (data.type) {
+    case 'specific-day': {
+      const days = data.days.map((item) => {
+        const day = t(`workflow.blocks.trigger.days.${item.id}`);
+        text.scheduleDetail += `${day}: ${item.times.join(', ')}<br>`;
+
+        return day;
+      });
+      text.schedule = t('scheduledWorkflow.schedule.types.general', {
+        time: days.join(', '),
+      });
+      break;
+    }
+    case 'interval':
+      text.schedule = t('scheduledWorkflow.schedule.types.interval', {
+        time: data.interval,
+      });
+      break;
+    case 'data':
+      dayjs(data.date).format('DD MMM YYYY, hh:mm:ss A');
+      break;
+    default:
+  }
+
+  return text;
+}
+async function getTriggerObj(trigger, { id, name }) {
+  if (!trigger || !scheduledTypes.includes(trigger.type)) return null;
+
+  rowId += 1;
+  const triggerObj = {
+    name,
+    id: rowId,
+    nextRun: '-',
+    schedule: '',
+    active: false,
+    type: trigger.type,
+    workflowId: id,
+  };
+
+  try {
+    const alarm = await browser.alarms.get(id);
+    if (alarm) {
+      triggerObj.active = true;
+      triggerObj.nextRun = dayjs(alarm.scheduledTime).format(
+        'DD MMM YYYY, hh:mm:ss A'
+      );
+    }
+
+    triggersData[rowId] = {
+      ...trigger,
+      workflow: { id, name },
+    };
+    Object.assign(triggerObj, scheduleText(trigger));
+
+    return triggerObj;
+  } catch (error) {
+    console.error(error);
+    return null;
+  }
+}
+async function refreshSchedule(id) {
+  try {
+    const triggerData = triggersData[id];
+    if (!triggerData) return;
+
+    await registerWorkflowTrigger(triggerData.workflow.id, {
+      data: triggerData,
+    });
+
+    const triggerObj = await getTriggerObj(triggerData, triggerData.workflow);
+    if (!triggerObj) return;
+
+    const triggerIndex = state.triggers.findIndex(
+      (trigger) => trigger.id === id
+    );
+    if (triggerIndex === -1) return;
+
+    state.triggers[triggerIndex] = triggerObj;
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+onMounted(async () => {
+  const workflows = Workflow.all();
+
+  for (const workflow of workflows) {
+    let { trigger } = workflow;
+
+    if (!trigger) {
+      const drawflow =
+        typeof workflow.drawflow === 'string'
+          ? JSON.parse(workflow.drawflow)
+          : workflow.drawflow;
+      trigger = findTriggerBlock(drawflow)?.data;
+    }
+
+    const obj = await getTriggerObj(trigger, workflow);
+    if (obj) state.triggers.push(obj);
+  }
+});
+</script>

+ 6 - 0
src/newtab/router.js

@@ -3,6 +3,7 @@ import Welcome from './pages/Welcome.vue';
 import Workflows from './pages/Workflows.vue';
 import WorkflowHost from './pages/workflows/Host.vue';
 import WorkflowDetails from './pages/workflows/[id].vue';
+import ScheduledWorkflow from './pages/ScheduledWorkflow.vue';
 import Collections from './pages/Collections.vue';
 import CollectionsDetails from './pages/collections/[id].vue';
 import Logs from './pages/Logs.vue';
@@ -31,6 +32,11 @@ const routes = [
     path: '/workflows',
     component: Workflows,
   },
+  {
+    name: 'schedule',
+    path: '/schedule',
+    component: ScheduledWorkflow,
+  },
   {
     name: 'workflows-details',
     path: '/workflows/:id',