1
0
Эх сурвалжийг харах

feat(newtab): add logs tab

Ahmad Kholid 3 жил өмнө
parent
commit
0e58ca0c34

+ 2 - 2
src/assets/css/tailwind.css

@@ -7,8 +7,8 @@ body {
   font-size: 16px;
   @apply bg-gray-50 dark:bg-gray-900;
 }
-table td,
-table tr {
+table th,
+table td {
   @apply p-2;
 }
 input:focus,

+ 11 - 8
src/background/index.js

@@ -12,13 +12,18 @@ function getWorkflow(workflowId) {
     });
   });
 }
+
+const runningWorkflows = {};
+
 async function executeWorkflow(workflow, tabId) {
   try {
     const engine = new WorkflowEngine(workflow, tabId);
 
+    runningWorkflows[engine.id] = engine;
+
     engine.init();
-    engine.on('destroyed', () => {
-      console.log('destroyed...');
+    engine.on('destroyed', (id) => {
+      delete runningWorkflows[id];
     });
 
     return true;
@@ -38,14 +43,12 @@ browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
 
       return tab.url.match(isRegex ? new RegExp(url, 'g') : url);
     });
-    const runningWorkflow = await workflowState.get(
-      (item) => item.state.tabId === tabId
-    );
-    console.log(runningWorkflow.length, runningWorkflow);
-    if (trigger && runningWorkflow.length === 0) {
+    const state = await workflowState.get((item) => item.state.tabId === tabId);
+    console.log(state.length, state);
+    if (trigger && state.length === 0) {
       const workflow = await getWorkflow(trigger.id);
 
-      executeWorkflow(workflow, tabId);
+      if (workflow) executeWorkflow(workflow, tabId);
     }
   }
 });

+ 1 - 1
src/background/workflow-engine.js

@@ -186,7 +186,7 @@ class WorkflowEngine {
         await browser.storage.local.set({ logs });
       }
 
-      this.dispatchEvent('destroyed', this.workflow.id);
+      this.dispatchEvent('destroyed', this.id);
     } catch (error) {
       console.error(error);
     }

+ 73 - 0
src/components/newtab/LogsTable.vue

@@ -0,0 +1,73 @@
+<template>
+  <table>
+    <tbody class="divide-y">
+      <tr v-for="log in logs" :key="log.id" class="hoverable">
+        <slot name="item-prepend" :log="log" />
+        <td class="p-2 w-6/12 text-overflow">
+          <router-link :to="`/logs/${log.id}`" class="block w-full h-full">
+            {{ log.name }}
+          </router-link>
+        </td>
+        <td class="log-time">
+          <v-remixicon
+            title="Started date"
+            name="riCalendarLine"
+            class="mr-2 inline-block align-middle"
+          />
+          <span :title="formatDate(log.startedAt, 'DD MMM YYYY, hh:mm A')">
+            {{ formatDate(log.startedAt, 'relative') }}
+          </span>
+        </td>
+        <td class="log-time" title="Duration">
+          <v-remixicon name="riTimerLine"></v-remixicon>
+          <span>{{ countDuration(log.startedAt, log.endedAt) }}</span>
+        </td>
+        <td class="p-2 text-right">
+          <span
+            :class="statusColors[log.status]"
+            class="inline-block py-1 w-16 text-center text-sm rounded-lg"
+          >
+            {{ log.status }}
+          </span>
+        </td>
+        <slot name="item-append" :log="log" />
+      </tr>
+    </tbody>
+  </table>
+</template>
+<script setup>
+import { countDuration } from '@/utils/helper';
+import dayjs from '@/lib/dayjs';
+
+defineProps({
+  logs: {
+    type: Array,
+    default: () => [],
+  },
+});
+
+const statusColors = {
+  error: 'bg-red-200',
+  success: 'bg-green-200',
+  stopped: 'bg-yellow-200',
+};
+
+function formatDate(date, format) {
+  if (format === 'relative') return dayjs(date).fromNow();
+
+  return dayjs(date).format(format);
+}
+</script>
+<style scoped>
+.log-time {
+  @apply text-gray-600 dark:text-gray-200;
+}
+.log-time svg {
+  @apply mr-2;
+}
+.log-time svg,
+.log-time span {
+  display: inline-block;
+  vertical-align: middle;
+}
+</style>

+ 2 - 1
src/components/newtab/workflow/WorkflowBuilder.vue

@@ -5,6 +5,7 @@
     @drop="dropHandler"
     @dragover.prevent
   >
+    <slot></slot>
     <div class="absolute z-10 p-4 bottom-0 left-0">
       <button class="p-2 rounded-lg bg-white mr-2" @click="editor.zoom_reset()">
         <v-remixicon name="riFullscreenLine" />
@@ -92,7 +93,7 @@ export default {
       const context = getCurrentInstance().appContext.app._context;
       const element = document.querySelector('#drawflow');
 
-      editor.value = drawflow(element, context);
+      editor.value = drawflow(element, { context, options: { reroute: true } });
       editor.value.start();
 
       emit('load', editor.value);

+ 0 - 0
src/components/newtab/workflow/WorkflowRunning.vue


+ 2 - 1
src/components/ui/UiTab.vue

@@ -2,6 +2,7 @@
   <button
     :aria-selected="uiTabs.modelValue.value === value"
     :class="[
+      uiTabs.small.value ? 'p-2' : 'py-3 px-2',
       uiTabs.modelValue.value === value
         ? 'border-accent text-gray-800 dark:text-white'
         : 'border-transparent',
@@ -9,7 +10,7 @@
     ]"
     :tabIndex="uiTabs.modelValue.value === value ? 0 : -1"
     aria-role="tab"
-    class="border-b-2 px-2 z-[1] py-3 ui-tab focus:ring-0"
+    class="border-b-2 transition-colors z-[1] ui-tab focus:ring-0"
     @mouseenter="uiTabs.hoverHandler"
     @click="uiTabs.updateActive(value)"
   >

+ 6 - 4
src/components/ui/UiTabs.vue

@@ -37,6 +37,7 @@ export default {
       type: [String, Number],
       default: '',
     },
+    small: Boolean,
     fill: Boolean,
   },
   emits: ['update:modelValue'],
@@ -48,10 +49,11 @@ export default {
       emit('update:modelValue', id);
     }
     function hoverHandler({ target }) {
+      const { height, width } = target.getBoundingClientRect();
+
       showHoverIndicator.value = true;
-      hoverIndicator.value.style.width = `${
-        target.getBoundingClientRect().width
-      }px`;
+      hoverIndicator.value.style.width = `${width}px`;
+      hoverIndicator.value.style.height = `${height - 11}px`;
       hoverIndicator.value.style.transform = `translateX(${target.offsetLeft}px)`;
     }
 
@@ -70,7 +72,7 @@ export default {
 </script>
 <style>
 .ui-tabs__indicator {
-  min-height: 38px;
+  min-height: 24px;
   min-width: 50px;
   transition-duration: 200ms;
   transition-property: transform, width;

+ 6 - 3
src/lib/drawflow.js

@@ -4,11 +4,14 @@ import '@/assets/css/drawflow.css';
 
 const blockComponents = require.context('../components/block', false, /\.vue$/);
 
-export default function (element, ctx) {
-  const editor = new Drawflow(element, { render, version: 3, h }, ctx);
+export default function (element, { context, options = {} }) {
+  const editor = new Drawflow(element, { render, version: 3, h }, context);
 
   editor.useuuid = true;
-  editor.reroute = true;
+
+  Object.entries(options).forEach(([key, value]) => {
+    editor[key] = value;
+  });
 
   blockComponents.keys().forEach((key) => {
     const name = key.replace(/(.\/)|\.vue$/g, '');

+ 2 - 33
src/newtab/pages/Home.vue

@@ -21,37 +21,7 @@
               View all
             </router-link>
           </div>
-          <table class="w-full table-fixed">
-            <tbody class="divide-y">
-              <tr v-for="log in logs" :key="log.id" class="hoverable">
-                <td class="p-2 w-6/12 text-overflow">
-                  <router-link
-                    :to="`/logs/${log.id}`"
-                    class="block w-full h-full"
-                  >
-                    {{ log.name }}
-                  </router-link>
-                </td>
-                <td class="p-2 text-gray-600 dark:text-gray-200">
-                  {{ dayjs(log.startedAt).fromNow() }}
-                </td>
-                <td class="p-2 text-right">
-                  <span
-                    :class="statusColors[log.status]"
-                    class="
-                      inline-block
-                      py-1
-                      w-16
-                      text-center text-sm
-                      rounded-lg
-                    "
-                  >
-                    {{ log.status }}
-                  </span>
-                </td>
-              </tr>
-            </tbody>
-          </table>
+          <logs-table :logs="logs" class="w-full" />
         </div>
       </div>
       <ui-card class="flex-1">
@@ -78,10 +48,9 @@
 </template>
 <script setup>
 import { computed } from 'vue';
-import { statusColors } from '@/utils/shared';
 import Workflow from '@/models/workflow';
 import Log from '@/models/log';
-import dayjs from '@/lib/dayjs';
+import LogsTable from '@/components/newtab/LogsTable.vue';
 import SharedTaskList from '@/components/shared/SharedTaskList.vue';
 import WorkflowCard from '@/components/newtab/workflow/WorkflowCard.vue';
 

+ 31 - 83
src/newtab/pages/logs.vue

@@ -7,70 +7,38 @@
       @updateSorts="sortsBuilder[$event.key] = $event.value"
       @updateFilters="filtersBuilder[$event.key] = $event.value"
     />
-    <table class="w-full logs-table">
-      <tbody>
-        <tr v-for="log in logs" :key="log.id" class="hoverable border-b">
-          <td class="w-8">
-            <ui-checkbox
-              :model-value="selectedLogs.includes(log.id)"
-              class="align-text-bottom"
-              @change="toggleSelectedLog($event, log.id)"
+    <logs-table :logs="logs" class="w-full">
+      <template #item-prepend="{ log }">
+        <td class="w-8">
+          <ui-checkbox
+            :model-value="selectedLogs.includes(log.id)"
+            class="align-text-bottom"
+            @change="toggleSelectedLog($event, log.id)"
+          />
+        </td>
+      </template>
+      <template #item-append="{ log }">
+        <td class="ml-4">
+          <div class="flex items-center justify-end space-x-4">
+            <v-remixicon
+              v-if="Object.keys(log.data).length !== 0"
+              name="riFileTextLine"
+              class="cursor-pointer"
+              @click="
+                exportDataModal.show = true;
+                exportDataModal.log = log;
+              "
             />
-          </td>
-          <td style="min-width: 150px">
-            <router-link
-              :to="`/logs/${log.id}`"
-              class="block w-full h-full text-overflow"
-            >
-              {{ log.name }}
-            </router-link>
-          </td>
-          <td>
-            <span
-              :class="statusColors[log.status]"
-              class="p-1 text-sm rounded-lg w-16 inline-block text-center"
-            >
-              {{ log.status }}
-            </span>
-          </td>
-          <td class="log-time">
             <v-remixicon
-              name="riCalendarLine"
-              title="Started date"
-            ></v-remixicon>
-            <span :title="formatDate(log.startedAt, 'DD MMM YYYY, hh:mm A')">
-              {{ formatDate(log.startedAt, 'relative') }}
-            </span>
-          </td>
-          <td class="log-time" title="Duration">
-            <v-remixicon name="riTimerLine"></v-remixicon>
-            <span>{{ countDuration(log.startedAt, log.endedAt) }}</span>
-          </td>
-          <td>
-            <div
-              v-if="log.data"
-              class="flex items-center justify-end space-x-4"
-            >
-              <v-remixicon
-                v-if="Object.keys(log.data).length !== 0"
-                name="riFileTextLine"
-                class="cursor-pointer"
-                @click="
-                  exportDataModal.show = true;
-                  exportDataModal.log = log;
-                "
-              />
-              <v-remixicon
-                name="riDeleteBin7Line"
-                class="text-red-500 cursor-pointer"
-                title="Delete log"
-                @click="deleteLog(log.id)"
-              />
-            </div>
-          </td>
-        </tr>
-      </tbody>
-    </table>
+              name="riDeleteBin7Line"
+              class="text-red-500 cursor-pointer"
+              title="Delete log"
+              @click="deleteLog(log.id)"
+            />
+          </div>
+        </td>
+      </template>
+    </logs-table>
     <ui-card
       v-if="selectedLogs.length !== 0"
       class="fixed right-0 bottom-0 m-5 shadow-xl space-x-2"
@@ -96,10 +64,8 @@
 import { shallowReactive, ref, computed } from 'vue';
 import { useStore } from 'vuex';
 import { useDialog } from '@/composable/dialog';
-import { countDuration } from '@/utils/helper';
-import { statusColors } from '@/utils/shared';
 import Log from '@/models/log';
-import dayjs from '@/lib/dayjs';
+import LogsTable from '@/components/newtab/LogsTable.vue';
 import LogsFilters from '@/components/newtab/logs/LogsFilters.vue';
 import LogsDataViewer from '@/components/newtab/logs/LogsDataViewer.vue';
 
@@ -146,11 +112,6 @@ const logs = computed(() =>
     .get()
 );
 
-function formatDate(date, format) {
-  if (format === 'relative') return dayjs(date).fromNow();
-
-  return dayjs(date).format(format);
-}
 function deleteLog(id) {
   Log.delete(id).then(() => {
     store.dispatch('saveToStorage', 'logs');
@@ -197,16 +158,3 @@ function selectAllLogs() {
   max-height: calc(100vh - 12rem);
 }
 </style>
-<style scoped>
-.log-time {
-  @apply text-gray-600 dark:text-gray-200;
-}
-.log-time svg {
-  @apply mr-2;
-}
-.log-time svg,
-.log-time span {
-  display: inline-block;
-  vertical-align: middle;
-}
-</style>

+ 13 - 6
src/newtab/pages/logs/[id].vue

@@ -20,7 +20,7 @@
         </p>
       </div>
       <div class="flex-grow"></div>
-      <ui-input prepend-icon="riSearch2Line" placeholder="Search..." />
+      <ui-button class="text-red-500" @click="deleteLog"> Delete </ui-button>
     </div>
     <div class="flex items-start">
       <ui-list class="w-7/12 mr-6">
@@ -47,8 +47,8 @@
   </div>
 </template>
 <script setup>
-import { computed } from 'vue';
-import { useRoute } from 'vue-router';
+import { computed, onMounted } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
 import Log from '@/models/log';
 import dayjs from '@/lib/dayjs';
 import { countDuration } from '@/utils/helper';
@@ -74,12 +74,19 @@ const logsType = {
 };
 
 const route = useRoute();
+const router = useRouter();
 
 const activeLog = computed(() => Log.find(route.params.id));
 
-setTimeout(() => {
-  console.log(activeLog.value);
-}, 2000);
+function deleteLog() {
+  Log.delete(route.params.id).then(() => {
+    router.replace('/logs');
+  });
+}
+
+onMounted(() => {
+  if (!activeLog.value) router.replace('/logs');
+});
 </script>
 <style>
 .logs-details .my-editor {

+ 27 - 8
src/newtab/pages/workflows/[id].vue

@@ -22,12 +22,25 @@
         @delete="deleteWorkflow"
       />
     </div>
-    <workflow-builder
-      class="flex-1"
-      :data="workflow.drawflow"
-      @load="editor = $event"
-      @deleteBlock="deleteBlock"
-    />
+    <div class="flex-1 relative">
+      <div class="absolute px-3 rounded-lg bg-white z-10 left-0 m-4 top-0">
+        <ui-tabs v-model="activeTab" class="border-none space-x-1">
+          <ui-tab value="editor">Editor</ui-tab>
+          <ui-tab value="logs">Logs</ui-tab>
+          <ui-tab value="running">Running</ui-tab>
+        </ui-tabs>
+      </div>
+      <workflow-builder
+        v-if="activeTab === 'editor'"
+        class="h-full w-full"
+        :data="workflow.drawflow"
+        @load="editor = $event"
+        @deleteBlock="deleteBlock"
+      />
+      <div v-else-if="activeTab === 'logs'" class="container mt-24 px-4">
+        <logs-table :logs="logs" class="w-full" />
+      </div>
+    </div>
   </div>
   <ui-modal v-model="state.showDataColumnsModal" content-class="max-w-xl">
     <template #header>Data columns</template>
@@ -58,12 +71,14 @@ import emitter from 'tiny-emitter/instance';
 import { sendMessage } from '@/utils/message';
 import { debounce } from '@/utils/helper';
 import { useDialog } from '@/composable/dialog';
+import Log from '@/models/log';
 import Workflow from '@/models/workflow';
 import WorkflowBuilder from '@/components/newtab/workflow/WorkflowBuilder.vue';
 import WorkflowSettings from '@/components/newtab/workflow/WorkflowSettings.vue';
 import WorkflowEditBlock from '@/components/newtab/workflow/WorkflowEditBlock.vue';
 import WorkflowDetailsCard from '@/components/newtab/workflow/WorkflowDetailsCard.vue';
 import WorkflowDataColumns from '@/components/newtab/workflow/WorkflowDataColumns.vue';
+import LogsTable from '@/components/newtab/LogsTable.vue';
 
 const route = useRoute();
 const router = useRouter();
@@ -72,6 +87,7 @@ const dialog = useDialog();
 const workflowId = route.params.id;
 
 const editor = shallowRef(null);
+const activeTab = shallowRef('editor');
 const state = reactive({
   blockData: {},
   isEditBlock: false,
@@ -80,6 +96,9 @@ const state = reactive({
   showDataColumnsModal: false,
 });
 const workflow = computed(() => Workflow.find(workflowId) || {});
+const logs = computed(() =>
+  Log.query().where('workflowId', workflowId).orderBy('startedAt', 'desc').get()
+);
 
 const updateBlockData = debounce((data) => {
   state.blockData.data = data;
@@ -109,7 +128,7 @@ function updateWorkflow(data) {
 async function handleWorkflowTrigger({ data }) {
   try {
     const workflowAlarm = await browser.alarms.get(workflowId);
-    const { visitWebTriggers = [] } = await browser.storage.local.get(
+    const { visitWebTriggers } = await browser.storage.local.get(
       'visitWebTriggers'
     );
     let visitWebTriggerIndex = visitWebTriggers.findIndex(
@@ -150,7 +169,7 @@ async function handleWorkflowTrigger({ data }) {
       };
 
       if (visitWebTriggerIndex === -1) {
-        visitWebTriggers.push(payload);
+        visitWebTriggers.unshift(payload);
       } else {
         visitWebTriggers[visitWebTriggerIndex] = payload;
       }

+ 0 - 6
src/utils/shared.js

@@ -374,9 +374,3 @@ export const dataExportTypes = [
   { name: 'CSV', id: 'csv' },
   { name: 'Plain text', id: 'plain-text' },
 ];
-
-export const statusColors = {
-  error: 'bg-red-200',
-  success: 'bg-green-200',
-  stopped: 'bg-yellow-200',
-};