1
0
Ahmad Kholid 2 жил өмнө
parent
commit
86a6d305f5

+ 1 - 0
.eslintrc.js

@@ -37,6 +37,7 @@ module.exports = {
     'no-console': ['warn', { allow: ['warn', 'error'] }],
     'no-underscore-dangle': 'off',
     'func-names': 'off',
+    'vue/v-on-event-hyphenation': 'off',
     'import/no-named-default': 'off',
     'no-restricted-syntax': 'off',
     'vue/multi-word-component-names': 'off',

+ 0 - 1
package.json

@@ -58,7 +58,6 @@
     "nanoid": "^3.2.0",
     "object-path": "^0.11.8",
     "papaparse": "^5.3.1",
-    "pinia": "^2.0.14",
     "rxjs": "^7.5.5",
     "tippy.js": "^6.3.1",
     "v-remixicon": "^0.1.1",

+ 13 - 5
src/assets/css/fonts.css

@@ -7,13 +7,21 @@
   src: url('../fonts/Inter-roman-latin.var.woff2') format("woff2");
 }
 
-/* fira-code-regular - latin */
-/* jetbrains-mono-regular - latin */
+/* source-code-pro-regular - latin */
 @font-face {
-  font-family: 'JetBrains Mono';
+  font-family: 'Source Code Pro';
   font-style: normal;
   font-weight: 400;
   src: local(''),
-       url('../fonts/jetbrains-mono-v6-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
-       url('../fonts/jetbrains-mono-v6-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
+       url('../fonts/source-code-pro-v21-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
+       url('../fonts/source-code-pro-v21-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
 }
+/* source-code-pro-600 - latin */
+@font-face {
+  font-family: 'Source Code Pro';
+  font-style: normal;
+  font-weight: 600;
+  src: local(''),
+       url('../fonts/source-code-pro-v21-latin-600.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
+       url('../fonts/source-code-pro-v21-latin-600.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
+}

BIN
src/assets/fonts/jetbrains-mono-v6-latin-regular.woff


BIN
src/assets/fonts/jetbrains-mono-v6-latin-regular.woff2


BIN
src/assets/fonts/source-code-pro-v21-latin-600.woff


BIN
src/assets/fonts/source-code-pro-v21-latin-600.woff2


BIN
src/assets/fonts/source-code-pro-v21-latin-regular.woff


BIN
src/assets/fonts/source-code-pro-v21-latin-regular.woff2


+ 2 - 0
src/background/index.js

@@ -93,6 +93,8 @@ const workflow = {
     } else {
       engine.init();
       engine.on('destroyed', ({ id, status }) => {
+        if (status === 'stopped') return;
+
         browser.permissions
           .contains({ permissions: ['notifications'] })
           .then((hasPermission) => {

+ 2 - 5
src/background/workflowEngine/engine.js

@@ -5,16 +5,13 @@ import { clearCache, sleep, parseJSON, isObject } from '@/utils/helper';
 import Worker from './worker';
 
 class WorkflowEngine {
-  constructor(
-    workflow,
-    { states, logger, blocksHandler, parentWorkflow, options }
-  ) {
+  constructor(workflow, { states, logger, blocksHandler, options }) {
     this.id = nanoid();
     this.states = states;
     this.logger = logger;
     this.workflow = workflow;
     this.blocksHandler = blocksHandler;
-    this.parentWorkflow = parentWorkflow;
+    this.parentWorkflow = options?.parentWorkflow;
     this.saveLog = workflow.settings?.saveLog ?? true;
 
     this.workerId = 0;

+ 184 - 0
src/components/newtab/logs/LogsHistory.vue

@@ -0,0 +1,184 @@
+<template>
+  <ui-list class="w-full">
+    <router-link
+      v-if="parentLog"
+      :to="currentLog.parentLog?.id || currentLog.collectionLogId"
+      class="mb-4 flex"
+    >
+      <v-remixicon name="riArrowLeftLine" class="mr-2" />
+      {{ t('log.goBack', { name: parentLog.name }) }}
+    </router-link>
+    <ui-expand
+      v-for="(item, index) in history"
+      :key="item.id || index"
+      hide-header-icon
+      class="mb-1"
+      header-active-class="bg-box-transparent rounded-b-none"
+      header-class="flex items-center px-4 py-2 hoverable rounded-lg w-full text-left history-item focus:ring-0"
+    >
+      <template #header="{ show }">
+        <v-remixicon
+          :rotate="show ? 270 : 180"
+          size="20"
+          name="riArrowLeftSLine"
+          class="transition-transform dark:text-gray-200 text-gray-600 -ml-1 mr-2"
+        />
+        <span
+          :class="logsType[item.type]?.color"
+          class="p-1 rounded-lg align-middle inline-block mr-2 dark:text-black"
+        >
+          <v-remixicon :name="logsType[item.type]?.icon" size="20" />
+        </span>
+        <div class="flex-1 line-clamp pr-2">
+          <p class="w-full text-overflow leading-tight">
+            {{ item.name }}
+            <span
+              v-show="item.description"
+              :title="item.description"
+              class="text-overflow text-gray-600 dark:text-gray-200 text-sm"
+            >
+              ({{ item.description }})
+            </span>
+          </p>
+          <p
+            v-if="item.message"
+            :title="item.message"
+            class="text-sm line-clamp text-gray-600 dark:text-gray-200"
+          >
+            {{ item.message }}
+          </p>
+        </div>
+        <router-link
+          v-if="item.logId"
+          :to="'/logs/' + item.logId"
+          class="mr-4"
+          title="Open log detail"
+        >
+          <v-remixicon name="riExternalLinkLine" />
+        </router-link>
+        <code
+          v-show="item.workerId"
+          :title="t('log.flowId')"
+          class="text-xs mr-4 bg-box-transparent rounded-lg p-1 rounded-md"
+        >
+          {{ item.workerId }}
+        </code>
+        <p class="text-gray-600 dark:text-gray-200">
+          {{ countDuration(0, item.duration || 0) }}
+        </p>
+      </template>
+      <pre
+        class="text-sm px-4 max-h-52 overflow-auto scroll bg-box-transparent pb-2 rounded-b-lg"
+        >{{ ctxData[item.id] }}</pre
+      >
+    </ui-expand>
+  </ui-list>
+  <div
+    v-if="currentLog.history.length >= 10"
+    class="flex items-center justify-between mt-4"
+  >
+    <div>
+      {{ t('components.pagination.text1') }}
+      <select v-model="pagination.perPage" class="p-1 rounded-md bg-input">
+        <option v-for="num in [10, 15, 25, 50, 100]" :key="num" :value="num">
+          {{ num }}
+        </option>
+      </select>
+      {{
+        t('components.pagination.text2', {
+          count: currentLog.history.length,
+        })
+      }}
+    </div>
+    <ui-pagination
+      v-model="pagination.currentPage"
+      :per-page="pagination.perPage"
+      :records="currentLog.history.length"
+    />
+  </div>
+</template>
+<script setup>
+/* eslint-disable no-use-before-define */
+import { computed, shallowReactive } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { countDuration } from '@/utils/helper';
+
+const props = defineProps({
+  currentLog: {
+    type: Object,
+    default: () => ({}),
+  },
+  ctxData: {
+    type: Object,
+    default: () => ({}),
+  },
+  parentLog: {
+    type: Object,
+    default: null,
+  },
+});
+
+const logsType = {
+  success: {
+    color: 'bg-green-200 dark:bg-green-300',
+    icon: 'riCheckLine',
+  },
+  stop: {
+    color: 'bg-yellow-200 dark:bg-yellow-300',
+    icon: 'riStopLine',
+  },
+  stopped: {
+    color: 'bg-yellow-200 dark:bg-yellow-300',
+    icon: 'riStopLine',
+  },
+  error: {
+    color: 'bg-red-200 dark:bg-red-300',
+    icon: 'riErrorWarningLine',
+  },
+  finish: {
+    color: 'bg-blue-200 dark:bg-blue-300',
+    icon: 'riFlagLine',
+  },
+};
+
+const { t, te } = useI18n();
+
+const pagination = shallowReactive({
+  perPage: 10,
+  currentPage: 1,
+});
+
+const history = computed(() =>
+  props.currentLog.history
+    .slice(
+      (pagination.currentPage - 1) * pagination.perPage,
+      pagination.currentPage * pagination.perPage
+    )
+    .map(translateLog)
+);
+
+function translateLog(log) {
+  const copyLog = { ...log };
+  const getTranslatation = (path, def) => {
+    const params = typeof path === 'string' ? { path } : path;
+
+    return te(params.path) ? t(params.path, params.params) : def;
+  };
+
+  if (['finish', 'stop'].includes(log.type)) {
+    copyLog.name = t(`log.types.${log.type}`);
+  } else {
+    copyLog.name = getTranslatation(
+      `workflow.blocks.${log.name}.name`,
+      log.name
+    );
+  }
+
+  copyLog.message = getTranslatation(
+    { path: `log.messages.${log.message}`, params: log },
+    log.message
+  );
+
+  return copyLog;
+}
+</script>

+ 145 - 0
src/components/newtab/logs/LogsTable.vue

@@ -0,0 +1,145 @@
+<template>
+  <div v-if="tableData.body.length === 0" class="text-center">
+    <img src="@/assets/svg/files-and-folder.svg" class="mx-auto max-w-sm" />
+    <p class="text-xl font-semibold">{{ t('message.noData') }}</p>
+  </div>
+  <template v-else>
+    <div class="flex items-center">
+      <ui-tabs
+        v-model="state.activeTab"
+        type="fill"
+        class="mb-4"
+        color=""
+        style="padding: 0"
+      >
+        <ui-tab value="table"> Table </ui-tab>
+        <ui-tab value="raw"> Raw </ui-tab>
+      </ui-tabs>
+      <div class="flex-grow"></div>
+      <ui-popover trigger-width>
+        <template #trigger>
+          <ui-button variant="accent">
+            <span>{{ t('log.exportData.title') }}</span>
+            <v-remixicon name="riArrowDropDownLine" class="ml-2 -mr-1" />
+          </ui-button>
+        </template>
+        <ui-list class="space-y-1">
+          <ui-list-item
+            v-for="type in dataExportTypes"
+            :key="type.id"
+            v-close-popover
+            class="cursor-pointer"
+            @click="exportData(type.id)"
+          >
+            {{ t(`log.exportData.types.${type.id}`) }}
+          </ui-list-item>
+        </ui-list>
+      </ui-popover>
+    </div>
+    <shared-codemirror
+      v-show="state.activeTab === 'raw'"
+      :model-value="JSON.stringify(currentLog.data.table, null, 2)"
+      readonly
+      lang="json"
+      style="max-height: 600px"
+    />
+    <table v-show="state.activeTab === 'table'" class="w-full">
+      <thead>
+        <tr>
+          <th
+            v-for="header in tableData.header"
+            :key="header"
+            class="last:rounded-r-lg first:rounded-l-lg text-left bg-box-transparent"
+          >
+            {{ header }}
+          </th>
+        </tr>
+      </thead>
+      <tbody class="divide-y">
+        <tr v-for="(row, index) in rows" :key="index">
+          <td v-for="(column, colIndex) in row" :key="index + colIndex">
+            <p class="line-clamp">
+              {{ column }}
+            </p>
+          </td>
+        </tr>
+      </tbody>
+    </table>
+    <div
+      v-if="
+        state.activeTab === 'table' &&
+        tableData.body &&
+        tableData.body.length >= 10
+      "
+      class="flex items-center justify-between mt-4"
+    >
+      <div>
+        {{ t('components.pagination.text1') }}
+        <select v-model="pagination.perPage" class="p-1 rounded-md bg-input">
+          <option
+            v-for="num in [10, 15, 25, 50, 100, 150]"
+            :key="num"
+            :value="num"
+          >
+            {{ num }}
+          </option>
+        </select>
+        {{
+          t('components.pagination.text2', {
+            count: tableData.body.length,
+          })
+        }}
+      </div>
+      <ui-pagination
+        v-model="pagination.currentPage"
+        :per-page="pagination.perPage"
+        :records="tableData.body.length"
+      />
+    </div>
+  </template>
+</template>
+<script setup>
+import { computed, shallowReactive, defineAsyncComponent } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { dataExportTypes } from '@/utils/shared';
+import dataExporter from '@/utils/dataExporter';
+
+const SharedCodemirror = defineAsyncComponent(() =>
+  import('@/components/newtab/shared/SharedCodemirror.vue')
+);
+
+const props = defineProps({
+  tableData: {
+    type: Object,
+    default: () => ({}),
+  },
+  currentLog: {
+    type: Object,
+    default: () => ({}),
+  },
+});
+
+const { t } = useI18n();
+
+const state = shallowReactive({
+  activeTab: 'table',
+});
+const pagination = shallowReactive({
+  perPage: 10,
+  currentPage: 1,
+});
+const rows = computed(() =>
+  props.tableData.body.slice(
+    (pagination.currentPage - 1) * pagination.perPage,
+    pagination.currentPage * pagination.perPage
+  )
+);
+
+function exportData(type) {
+  dataExporter(
+    props.currentLog.data.table,
+    { name: props.currentLog.name, type },
+    true
+  );
+}
+</script>

+ 71 - 0
src/components/newtab/logs/LogsVariables.vue

@@ -0,0 +1,71 @@
+<template>
+  <div v-if="Object.keys(variables).length === 0" class="text-center">
+    <img src="@/assets/svg/files-and-folder.svg" class="mx-auto max-w-sm" />
+    <p class="text-xl font-semibold">{{ t('message.noData') }}</p>
+  </div>
+  <template v-else>
+    <ui-tabs
+      v-model="state.activeTab"
+      type="fill"
+      class="mb-4"
+      color=""
+      style="padding: 0"
+    >
+      <ui-tab value="gui"> GUI </ui-tab>
+      <ui-tab value="raw"> Raw </ui-tab>
+    </ui-tabs>
+    <div v-if="state.activeTab === 'gui'" class="mt-4">
+      <ul class="space-y-2 grid grid-cols-1 md:grid-cols-2 gap-4">
+        <li
+          v-for="(varValue, varName) in variables"
+          :key="varName"
+          class="px-2 pb-2 pt-1 rounded-lg flex items-center border-2 space-x-2"
+        >
+          <ui-input
+            :model-value="varName"
+            :label="t('common.name')"
+            class="w-full"
+            placeholder="EMPTY"
+            readonly
+          />
+          <ui-input
+            :model-value="varValue"
+            label="Value"
+            class="w-full"
+            placeholder="EMPTY"
+            readonly
+          />
+        </li>
+      </ul>
+    </div>
+    <shared-codemirror
+      v-else
+      :model-value="JSON.stringify(variables, null, 2)"
+      class="mt-4"
+      lang="json"
+      readonly
+    />
+  </template>
+</template>
+<script setup>
+import { defineAsyncComponent, shallowReactive, computed } from 'vue';
+import { useI18n } from 'vue-i18n';
+
+const SharedCodemirror = defineAsyncComponent(() =>
+  import('@/components/newtab/shared/SharedCodemirror.vue')
+);
+
+const props = defineProps({
+  currentLog: {
+    type: Object,
+    default: () => ({}),
+  },
+});
+
+const { t } = useI18n();
+const state = shallowReactive({
+  activeTab: 'gui',
+});
+
+const variables = computed(() => props.currentLog.data?.variables || {});
+</script>

+ 1 - 1
src/components/newtab/shared/SharedCodemirror.vue

@@ -109,7 +109,7 @@ onBeforeUnmount(() => {
 .cm-editor .cm-gutters,
 .cm-editor .cm-content,
 .cm-tooltip.cm-tooltip-autocomplete > ul {
-  font-family: JetBrains Mono, Fira code, Fira Mono, Consolas, Menlo, Courier,
+  font-family: 'Source Code Pro', Fira code, Fira Mono, Consolas, Menlo, Courier,
     monospace !important;
 }
 </style>

+ 1 - 1
src/components/newtab/shared/SharedWysiwyg.vue

@@ -81,7 +81,7 @@ onBeforeUnmount(() => {
 <style>
 .ProseMirror pre,
 .ProseMirror code {
-  font-family: 'JetBrains Mono', monospace;
+  font-family: 'Source Code Pro', monospace;
 }
 .ProseMirror:focus {
   outline: none;

+ 2 - 2
src/components/newtab/workflow/edit/EditInsertData.vue

@@ -46,8 +46,8 @@
             </option>
           </ui-select>
         </div>
-        <div class="flex items-center">
-          <ui-input
+        <div class="flex items-start">
+          <ui-textarea
             v-model="item.value"
             placeholder="value"
             title="value"

+ 0 - 2
src/newtab/index.js

@@ -1,5 +1,4 @@
 import { createApp } from 'vue';
-import { createPinia } from 'pinia';
 import App from './App.vue';
 import router from './router';
 import store from '../store';
@@ -16,7 +15,6 @@ createApp(App)
   .use(store)
   .use(compsUi)
   .use(vueI18n)
-  .use(createPinia())
   .use(vueToastification)
   .use(vRemixicon, icons)
   .mount('#app');

+ 77 - 185
src/newtab/pages/logs/[id].vue

@@ -1,6 +1,6 @@
 <template>
   <div v-if="currentLog.id" class="container pt-8 pb-4">
-    <div class="flex items-center mb-8">
+    <div class="flex items-center">
       <div>
         <h1 class="text-2xl max-w-md text-overflow font-semibold">
           {{ currentLog.name }}
@@ -17,7 +17,7 @@
       </div>
       <div class="flex-grow"></div>
       <ui-button
-        v-if="workflowExists"
+        v-if="state.workflowExists"
         v-tooltip="t('log.goWorkflow')"
         icon
         class="mr-4"
@@ -29,205 +29,74 @@
         {{ t('common.delete') }}
       </ui-button>
     </div>
-    <div class="flex items-start">
-      <div class="w-7/12 mr-6">
-        <ui-list>
-          <router-link
-            v-if="parentLog"
-            :to="currentLog.parentLog?.id || currentLog.collectionLogId"
-            replace
-            class="mb-4 flex"
-          >
-            <v-remixicon name="riArrowLeftLine" class="mr-2" />
-            {{ t('log.goBack', { name: parentLog.name }) }}
-          </router-link>
-          <ui-expand
-            v-for="(item, index) in history"
-            :key="item.id || index"
-            hide-header-icon
-            class="mb-1"
-            header-active-class="bg-box-transparent rounded-b-none"
-            header-class="flex items-center px-4 py-2 hoverable rounded-lg w-full text-left history-item focus:ring-0"
-          >
-            <template #header="{ show }">
-              <v-remixicon
-                :rotate="show ? 270 : 180"
-                size="20"
-                name="riArrowLeftSLine"
-                class="transition-transform dark:text-gray-200 text-gray-600 -ml-1 mr-2"
-              />
-              <span
-                :class="logsType[item.type]?.color"
-                class="p-1 rounded-lg align-middle inline-block mr-2 dark:text-black"
-              >
-                <v-remixicon :name="logsType[item.type]?.icon" size="20" />
-              </span>
-              <div class="flex-1 line-clamp pr-2">
-                <p class="w-full text-overflow leading-tight">
-                  {{ item.name }}
-                  <span
-                    v-show="item.description"
-                    :title="item.description"
-                    class="text-overflow text-gray-600 dark:text-gray-200 text-sm"
-                  >
-                    ({{ item.description }})
-                  </span>
-                </p>
-                <p
-                  v-if="item.message"
-                  :title="item.message"
-                  class="text-sm line-clamp text-gray-600 dark:text-gray-200"
-                >
-                  {{ item.message }}
-                </p>
-              </div>
-              <router-link
-                v-if="item.logId"
-                :to="'/logs/' + item.logId"
-                class="mr-4"
-                title="Open log detail"
-              >
-                <v-remixicon name="riExternalLinkLine" />
-              </router-link>
-              <code
-                v-show="item.workerId"
-                :title="t('log.flowId')"
-                class="text-xs mr-4 bg-box-transparent rounded-lg p-1 rounded-md"
-              >
-                {{ item.workerId }}
-              </code>
-              <p class="text-gray-600 dark:text-gray-200">
-                {{ countDuration(0, item.duration || 0) }}
-              </p>
-            </template>
-            <pre
-              class="text-sm px-4 max-h-52 overflow-auto scroll bg-box-transparent pb-2 rounded-b-lg"
-              >{{ ctxData[item.id] }}</pre
-            >
-          </ui-expand>
-        </ui-list>
-        <div
-          v-if="currentLog.history.length >= 10"
-          class="flex items-center justify-between mt-4"
-        >
-          <div>
-            {{ t('components.pagination.text1') }}
-            <select
-              v-model="pagination.perPage"
-              class="p-1 rounded-md bg-input"
-            >
-              <option
-                v-for="num in [10, 15, 25, 50, 100]"
-                :key="num"
-                :value="num"
-              >
-                {{ num }}
-              </option>
-            </select>
-            {{
-              t('components.pagination.text2', {
-                count: currentLog.history.length,
-              })
-            }}
-          </div>
-          <ui-pagination
-            v-model="pagination.currentPage"
-            :per-page="pagination.perPage"
-            :records="currentLog.history.length"
-          />
-        </div>
-      </div>
-      <div class="w-5/12 logs-details sticky top-10">
-        <logs-data-viewer :log="currentLog" />
-      </div>
-    </div>
+    <ui-tabs v-model="state.activeTab" class="mt-4" @change="onTabChange">
+      <ui-tab v-for="tab in tabs" :key="tab.id" class="mr-4" :value="tab.id">
+        {{ tab.name }}
+      </ui-tab>
+    </ui-tabs>
+    <ui-tab-panels
+      :model-value="state.activeTab"
+      class="mt-4 pb-4"
+      style="min-height: 500px"
+    >
+      <ui-tab-panel value="logs">
+        <logs-history
+          :current-log="currentLog"
+          :ctx-data="ctxData"
+          :parent-log="parentLog"
+        />
+      </ui-tab-panel>
+      <ui-tab-panel value="table">
+        <logs-table :current-log="currentLog" :table-data="tableData" />
+      </ui-tab-panel>
+      <ui-tab-panel value="variables">
+        <logs-variables :current-log="currentLog" />
+      </ui-tab-panel>
+    </ui-tab-panels>
   </div>
 </template>
 <script setup>
-import { computed, shallowReactive, shallowRef } from 'vue';
+import { shallowReactive, shallowRef, watch } from 'vue';
 import { useRoute, useRouter } from 'vue-router';
 import { useI18n } from 'vue-i18n';
 import dbLogs from '@/db/logs';
 import Workflow from '@/models/workflow';
 import dayjs from '@/lib/dayjs';
-import { countDuration } from '@/utils/helper';
-import LogsDataViewer from '@/components/newtab/logs/LogsDataViewer.vue';
-
-const logsType = {
-  success: {
-    color: 'bg-green-200 dark:bg-green-300',
-    icon: 'riCheckLine',
-  },
-  stop: {
-    color: 'bg-yellow-200 dark:bg-yellow-300',
-    icon: 'riStopLine',
-  },
-  stopped: {
-    color: 'bg-yellow-200 dark:bg-yellow-300',
-    icon: 'riStopLine',
-  },
-  error: {
-    color: 'bg-red-200 dark:bg-red-300',
-    icon: 'riErrorWarningLine',
-  },
-  finish: {
-    color: 'bg-blue-200 dark:bg-blue-300',
-    icon: 'riFlagLine',
-  },
-};
+import { countDuration, convertArrObjTo2DArr } from '@/utils/helper';
+import LogsTable from '@/components/newtab/logs/LogsTable.vue';
+import LogsHistory from '@/components/newtab/logs/LogsHistory.vue';
+import LogsVariables from '@/components/newtab/logs/LogsVariables.vue';
 
-const { t, te } = useI18n();
+const { t } = useI18n();
 const route = useRoute();
 const router = useRouter();
 
 const ctxData = shallowRef({});
 const parentLog = shallowRef(null);
 
-const pagination = shallowReactive({
-  perPage: 10,
-  currentPage: 1,
+const tabs = [
+  { id: 'logs', name: t('common.log', 2) },
+  { id: 'table', name: t('workflow.table.title') },
+  { id: 'variables', name: t('workflow.variables.title', 2) },
+];
+
+const state = shallowReactive({
+  activeTab: 'logs',
+  workflowExists: false,
+});
+const tableData = shallowReactive({
+  converted: false,
+  body: [],
+  header: [],
 });
 const currentLog = shallowRef({
   history: [],
+  data: {
+    table: [],
+    variables: {},
+  },
 });
 
-function translateLog(log) {
-  const copyLog = { ...log };
-  const getTranslatation = (path, def) => {
-    const params = typeof path === 'string' ? { path } : path;
-
-    return te(params.path) ? t(params.path, params.params) : def;
-  };
-
-  if (['finish', 'stop'].includes(log.type)) {
-    copyLog.name = t(`log.types.${log.type}`);
-  } else {
-    copyLog.name = getTranslatation(
-      `workflow.blocks.${log.name}.name`,
-      log.name
-    );
-  }
-
-  copyLog.message = getTranslatation(
-    { path: `log.messages.${log.message}`, params: log },
-    log.message
-  );
-
-  return copyLog;
-}
-
-const history = computed(() =>
-  currentLog.value.history
-    .slice(
-      (pagination.currentPage - 1) * pagination.perPage,
-      pagination.currentPage * pagination.perPage
-    )
-    .map(translateLog)
-);
-const workflowExists = computed(() =>
-  Workflow.find(currentLog.value.workflowId)
-);
-
 function deleteLog() {
   dbLogs.items
     .where('id')
@@ -254,8 +123,22 @@ function goToWorkflow() {
 
   router.push(path);
 }
+function convertToTableData() {
+  const data = currentLog.value.data?.table;
+  if (!data) return;
+
+  const [header, ...body] = convertArrObjTo2DArr(data);
 
-(async () => {
+  tableData.body = body;
+  tableData.header = header;
+  tableData.converted = true;
+}
+function onTabChange(value) {
+  if (value === 'table' && !tableData.converted) {
+    convertToTableData();
+  }
+}
+async function fetchLog() {
   const logId = route.params.id;
   const logDetail = await dbLogs.items.where('id').equals(logId).last();
 
@@ -264,8 +147,12 @@ function goToWorkflow() {
     return;
   }
 
-  const [logCtxData, logHistory] = await Promise.all(
-    ['ctxData', 'histories'].map((key) =>
+  tableData.body = [];
+  tableData.header = [];
+  tableData.converted = false;
+
+  const [logCtxData, logHistory, logsData] = await Promise.all(
+    ['ctxData', 'histories', 'logsData'].map((key) =>
       dbLogs[key].where('logId').equals(logId).last()
     )
   );
@@ -273,15 +160,20 @@ function goToWorkflow() {
   ctxData.value = logCtxData?.data || {};
   currentLog.value = {
     history: logHistory?.data || [],
+    data: logsData?.data || {},
     ...logDetail,
   };
 
+  state.workflowExists = Boolean(Workflow.find(logDetail.workflowId));
+
   const parentLogId = logDetail.collectionLogId || logDetail.parentLog?.id;
   if (parentLogId) {
     parentLog.value =
       (await dbLogs.items.where('id').equals(parentLogId).last()) || null;
   }
-})();
+}
+
+watch(() => route.params.id, fetchLog, { immediate: true });
 </script>
 <style>
 .logs-details .cm-editor {

+ 9 - 3
src/utils/dataMigration.js

@@ -5,11 +5,13 @@ export default async function () {
   try {
     const { logs, logsCtxData, migration } = await browser.storage.local.get([
       'logs',
+      'migration',
       'logsCtxData',
     ]);
     const hasMigrated = migration || {};
+    const backupData = {};
 
-    if (!hasMigrated.logs) {
+    if (!hasMigrated.logs && logs) {
       const ids = new Set();
 
       const items = [];
@@ -17,11 +19,11 @@ export default async function () {
       const logsData = [];
       const histories = [];
 
-      for (let index = 0; index < logs.length; index += 1) {
+      for (let index = logs.length - 1; index > 0; index -= 1) {
         const { data, history, ...item } = logs[index];
         const logId = item.id;
 
-        if (!ids.has(logId)) {
+        if (!ids.has(logId) && ids.size < 500) {
           items.push(item);
           logsData.push({ logId, data });
           histories.push({ logId, data: history });
@@ -38,11 +40,15 @@ export default async function () {
         dbLogs.histories.bulkAdd(histories),
       ]);
 
+      backupData.logs = logs;
       hasMigrated.logs = true;
+
+      await browser.storage.local.remove('logs');
     }
 
     await browser.storage.local.set({
       migration: hasMigrated,
+      ...backupData,
     });
   } catch (error) {
     console.error(error);

+ 1 - 1
tailwind.config.js

@@ -25,7 +25,7 @@ module.exports = {
       },
       fontFamily: {
         sans: ['Poppins', 'sans-serif'],
-        mono: ['JetBrains Mono', 'monospace'],
+        mono: ['Source Code Pro', 'monospace'],
       },
       container: {
         center: true,

+ 0 - 13
yarn.lock

@@ -1676,11 +1676,6 @@
   resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.1.3.tgz#a44c52e8fa6d22f84db3abdcdd0be5135b7dd7cf"
   integrity sha512-79InfO2xHv+WHIrH1bHXQUiQD/wMls9qBk6WVwGCbdwP7/3zINtvqPNMtmSHXsIKjvUAHc8L0ouOj6ZQQRmcXg==
 
-"@vue/devtools-api@^6.1.4":
-  version "6.1.4"
-  resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.1.4.tgz#b4aec2f4b4599e11ba774a50c67fa378c9824e53"
-  integrity sha512-IiA0SvDrJEgXvVxjNkHPFfDx6SXw0b/TUkqMcDZWNg9fnCAHbTpoo59YfJ9QLFkwa3raau5vSlRVzMSLDnfdtQ==
-
 "@vue/reactivity-transform@3.2.37":
   version "3.2.37"
   resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.37.tgz#0caa47c4344df4ae59f5a05dde2a8758829f8eca"
@@ -5656,14 +5651,6 @@ pify@^4.0.1:
   resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
   integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==
 
-pinia@^2.0.14:
-  version "2.0.14"
-  resolved "https://registry.yarnpkg.com/pinia/-/pinia-2.0.14.tgz#0837898c20291ebac982bbfca95c8d3c6099925f"
-  integrity sha512-0nPuZR4TetT/WcLN+feMSjWJku3SQU7dBbXC6uw+R6FLQJCsg+/0pzXyD82T1FmAYe0lsx+jnEDQ1BLgkRKlxA==
-  dependencies:
-    "@vue/devtools-api" "^6.1.4"
-    vue-demi "*"
-
 pinkie-promise@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"