瀏覽代碼

feat: put workflows logs in modal

Ahmad Kholid 2 年之前
父節點
當前提交
9853860070

+ 8 - 2
src/background/BackgroundEventsListeners.js

@@ -25,10 +25,16 @@ class BackgroundEventsListeners {
     BackgroundWorkflowTriggers.contextMenu(event, tab);
   }
 
-  static onNotificationClicked(notificationId) {
+  static async onNotificationClicked(notificationId) {
     if (notificationId.startsWith('logs')) {
       const { 1: logId } = notificationId.split(':');
-      BackgroundUtils.openDashboard(`/logs/${logId}`);
+
+      const [tab] = await browser.tabs.query({
+        url: browser.runtime.getURL('/newtab.html'),
+      });
+      if (!tab) await BackgroundUtils.openDashboard('');
+
+      await BackgroundUtils.sendMessageToDashboard('open-logs', { logId });
     }
   }
 

+ 58 - 0
src/components/newtab/app/AppLogs.vue

@@ -0,0 +1,58 @@
+<template>
+  <ui-modal
+    v-model="state.show"
+    custom-content
+    content-position="start"
+    @close="clearState"
+  >
+    <ui-card class="w-full mt-8" style="max-width: 1400px; min-height: 600px">
+      <app-logs-items
+        v-if="!state.logId"
+        :workflow-id="state.workflowId"
+        @select="onSelectLog"
+        @close="clearState"
+      />
+      <app-logs-item-running
+        v-else-if="state.runningWorkflow"
+        :log-id="state.logId"
+        @close="closeItemPage"
+      />
+      <app-logs-item v-else :log-id="state.logId" @close="closeItemPage" />
+    </ui-card>
+  </ui-modal>
+</template>
+<script setup>
+import { reactive } from 'vue';
+import emitter from '@/lib/mitt';
+import AppLogsItem from './AppLogsItem.vue';
+import AppLogsItems from './AppLogsItems.vue';
+import AppLogsItemRunning from './AppLogsItemRunning.vue';
+
+const state = reactive({
+  logId: '',
+  source: '',
+  show: false,
+  workflowId: '',
+  runningWorkflow: false,
+});
+
+emitter.on('ui:logs', (event = {}) => {
+  Object.assign(state, event);
+});
+
+function clearState() {
+  state.show = false;
+  state.logId = '';
+  state.source = '';
+  state.runningWorkflow = false;
+}
+function closeItemPage(closeModal = false) {
+  state.logId = '';
+
+  if (closeModal) clearState();
+}
+function onSelectLog({ id, type }) {
+  state.runningWorkflow = type === 'running';
+  state.logId = id;
+}
+</script>

+ 192 - 0
src/components/newtab/app/AppLogsItem.vue

@@ -0,0 +1,192 @@
+<template>
+  <div v-if="currentLog.id">
+    <div class="flex items-center">
+      <button
+        v-tooltip:bottom="t('workflow.blocks.go-back.name')"
+        role="button"
+        class="h-12 px-1 transition mr-2 bg-input rounded-lg dark:text-gray-300 text-gray-600"
+        @click="$emit('close')"
+      >
+        <v-remixicon name="riArrowLeftSLine" />
+      </button>
+      <div>
+        <h1 class="text-2xl max-w-md text-overflow font-semibold">
+          {{ currentLog.name }}
+        </h1>
+        <p class="text-gray-600 dark:text-gray-200">
+          {{
+            t(`log.description.text`, {
+              status: t(
+                `log.description.status.${currentLog.status || 'success'}`
+              ),
+              date: dayjs(currentLog.startedAt).format('DD MMM'),
+              duration: countDuration(currentLog.startedAt, currentLog.endedAt),
+            })
+          }}
+        </p>
+      </div>
+      <div class="flex-grow"></div>
+      <ui-button
+        v-if="state.workflowExists"
+        v-tooltip="t('log.goWorkflow')"
+        icon
+        class="mr-4"
+        @click="goToWorkflow"
+      >
+        <v-remixicon name="riExternalLinkLine" />
+      </ui-button>
+      <ui-button class="text-red-500 dark:text-red-400" @click="deleteLog">
+        {{ t('common.delete') }}
+      </ui-button>
+    </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 overflow-auto scroll px-2"
+      style="min-height: 500px; max-height: calc(100vh - 15rem)"
+    >
+      <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 { shallowReactive, shallowRef, watch } from 'vue';
+import { useRouter } from 'vue-router';
+import { useI18n } from 'vue-i18n';
+import dbLogs from '@/db/logs';
+import dayjs from '@/lib/dayjs';
+import { useWorkflowStore } from '@/stores/workflow';
+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 props = defineProps({
+  logId: {
+    type: String,
+    default: '',
+  },
+});
+const emit = defineEmits(['close']);
+
+const { t } = useI18n();
+const router = useRouter();
+const workflowStore = useWorkflowStore();
+
+const ctxData = shallowRef({});
+const parentLog = shallowRef(null);
+
+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 deleteLog() {
+  dbLogs.items
+    .where('id')
+    .equals(props.logId)
+    .delete()
+    .then(() => {
+      emit('close');
+    });
+}
+function goToWorkflow() {
+  const path = `/workflows/${currentLog.value.workflowId}`;
+
+  router.push(path);
+  emit('close', true);
+}
+function convertToTableData() {
+  const data = currentLog.value.data?.table;
+  if (!data) return;
+
+  const [header] = convertArrObjTo2DArr(data);
+
+  tableData.converted = true;
+  tableData.body = data.map((item, index) => ({ ...item, id: index + 1 }));
+  tableData.header = header.map((name) => ({
+    text: name,
+    value: name,
+    filterable: true,
+  }));
+  tableData.header.unshift({ value: 'id', text: '', sortable: false });
+}
+function onTabChange(value) {
+  if (value === 'table' && !tableData.converted) {
+    convertToTableData();
+  }
+}
+async function fetchLog() {
+  if (!props.logId) return;
+
+  const logDetail = await dbLogs.items.where('id').equals(props.logId).last();
+  if (!logDetail) return;
+
+  tableData.body = [];
+  tableData.header = [];
+  parentLog.value = null;
+  tableData.converted = false;
+
+  const [logCtxData, logHistory, logsData] = await Promise.all(
+    ['ctxData', 'histories', 'logsData'].map((key) =>
+      dbLogs[key].where('logId').equals(props.logId).last()
+    )
+  );
+
+  ctxData.value = logCtxData?.data || {};
+  currentLog.value = {
+    history: logHistory?.data || [],
+    data: logsData?.data || {},
+    ...logDetail,
+  };
+
+  state.workflowExists = Boolean(workflowStore.getById(logDetail.workflowId));
+
+  const parentLogId = logDetail.collectionLogId || logDetail.parentLog?.id;
+  if (parentLogId) {
+    parentLog.value =
+      (await dbLogs.items.where('id').equals(parentLogId).last()) || null;
+  }
+}
+
+watch(() => props.logId, fetchLog, { immediate: true });
+</script>
+<style>
+.logs-details .cm-editor {
+  max-height: calc(100vh - 15rem);
+}
+</style>

+ 24 - 11
src/newtab/pages/logs/Running.vue → src/components/newtab/app/AppLogsItemRunning.vue

@@ -1,6 +1,14 @@
 <template>
-  <div v-if="running" class="container py-8">
+  <div v-if="running">
     <div class="flex items-center">
+      <button
+        v-tooltip:bottom="t('workflow.blocks.go-back.name')"
+        role="button"
+        class="h-12 px-1 transition mr-2 bg-input rounded-lg dark:text-gray-300 text-gray-600"
+        @click="$emit('close')"
+      >
+        <v-remixicon name="riArrowLeftSLine" />
+      </button>
       <div class="flex-grow overflow-hidden">
         <h1 class="text-2xl max-w-md text-overflow font-semibold text-overflow">
           {{ running.state.name }}
@@ -21,6 +29,7 @@
     </div>
     <div class="mt-8">
       <logs-history
+        :is-running="true"
         :current-log="{
           history: running.state.logs,
           workflowId: running.workflowId,
@@ -76,7 +85,7 @@
 </template>
 <script setup>
 import { computed, watch, shallowRef, onBeforeUnmount } from 'vue';
-import { useRoute, useRouter } from 'vue-router';
+import { useRouter } from 'vue-router';
 import { useI18n } from 'vue-i18n';
 import { countDuration } from '@/utils/helper';
 import { useWorkflowStore } from '@/stores/workflow';
@@ -85,8 +94,15 @@ import dbLogs from '@/db/logs';
 import dayjs from '@/lib/dayjs';
 import LogsHistory from '@/components/newtab/logs/LogsHistory.vue';
 
+const props = defineProps({
+  logId: {
+    type: String,
+    default: '',
+  },
+});
+const emit = defineEmits(['close']);
+
 const { t } = useI18n();
-const route = useRoute();
 const router = useRouter();
 const workflowStore = useWorkflowStore();
 
@@ -96,11 +112,12 @@ const interval = setInterval(() => {
 }, 1000);
 
 const running = computed(() =>
-  workflowStore.getAllStates.find(({ id }) => id === route.params.id)
+  workflowStore.getAllStates.find(({ id }) => id === props.logId)
 );
 
 function stopWorkflow() {
   stopWorkflowExec(running.value.id);
+  emit('close');
 }
 function getBlockPath(blockId) {
   const { workflowId, teamId } = running.value;
@@ -116,16 +133,12 @@ function getBlockPath(blockId) {
 watch(
   running,
   async () => {
-    if (!route.name.startsWith('logs')) return;
-    if (!running.value && route.params.id) {
-      const log = await dbLogs.items
-        .where('id')
-        .equals(route.params.id)
-        .first();
+    if (!running.value && props.logId) {
+      const log = await dbLogs.items.where('id').equals(props.logId).first();
       let path = '/logs';
 
       if (log) {
-        path = `/logs/${route.params.id}`;
+        path = `/logs/${props.logId}`;
       }
 
       router.replace(path);

+ 105 - 6
src/newtab/pages/Logs.vue → src/components/newtab/app/AppLogsItems.vue

@@ -1,18 +1,71 @@
 <template>
-  <div class="container pt-8 pb-4 logs-list">
-    <h1 class="text-2xl font-semibold mb-6">{{ t('common.log', 2) }}</h1>
+  <div class="pb-4 pt-1 overflow-auto logs-list">
+    <div class="flex items-center mb-8">
+      <h1 class="text-2xl font-semibold flex-1">
+        {{ $t('common.log', 2) }}
+      </h1>
+      <v-remixicon
+        name="riCloseLine"
+        class="cursor-pointer text-gray-600 dark:text-gray-300"
+        @click="$emit('close')"
+      />
+    </div>
     <logs-filters
       :sorts="sortsBuilder"
       :filters="filtersBuilder"
       @clear="clearLogs"
       @updateSorts="sortsBuilder[$event.key] = $event.value"
       @updateFilters="filtersBuilder[$event.key] = $event.value"
-    />
+    >
+      <ui-popover padding="" @click="filtersBuilder.workflowQuery = ''">
+        <template #trigger>
+          <ui-button>
+            <span class="text-overflow text-left" style="max-width: 160px">
+              {{ activeWorkflowName }}
+            </span>
+            <v-remixicon name="riArrowDropDownLine" class="-mr-1 ml-2" />
+          </ui-button>
+        </template>
+        <div class="w-64">
+          <div class="p-4">
+            <ui-input
+              v-model="filtersBuilder.workflowQuery"
+              autofocus
+              placeholder="Search..."
+              class="w-full"
+              prepend-icon="riSearch2Line"
+            />
+            <div class="text-right">
+              <span
+                class="underline text-sm cursor-pointer text-gray-600 dark:text-gray-300"
+                @click="filtersBuilder.workflowId = ''"
+              >
+                Clear
+              </span>
+            </div>
+          </div>
+          <ui-list class="mb-4 px-4 space-y-1 overflow-auto max-h-96 scroll">
+            <ui-list-item
+              v-for="workflow in workflows"
+              :key="workflow.id"
+              :active="filtersBuilder.workflowId === workflow.id"
+              class="cursor-pointer"
+              @click="filtersBuilder.workflowId = workflow.id"
+            >
+              <p class="text-overflow">{{ workflow.name }}</p>
+            </ui-list-item>
+          </ui-list>
+        </div>
+      </ui-popover>
+    </logs-filters>
     <div v-if="logs" style="min-height: 320px">
       <shared-logs-table
         :logs="logs"
-        :running="workflowStore.getAllStates"
+        :modal="true"
+        :running="workflowStates"
         class="w-full"
+        style="max-height: calc(100vh - 18rem)"
+        @select="$emit('select', $event)"
       >
         <template #item-prepend="{ log }">
           <td class="w-8">
@@ -85,14 +138,24 @@ import { useI18n } from 'vue-i18n';
 import { useDialog } from '@/composable/dialog';
 import dbLogs from '@/db/logs';
 import { useWorkflowStore } from '@/stores/workflow';
+import { useHostedWorkflowStore } from '@/stores/hostedWorkflow';
 import { useLiveQuery } from '@/composable/liveQuery';
 import LogsFilters from '@/components/newtab/logs/LogsFilters.vue';
 import LogsDataViewer from '@/components/newtab/logs/LogsDataViewer.vue';
 import SharedLogsTable from '@/components/newtab/shared/SharedLogsTable.vue';
 
+const props = defineProps({
+  workflowId: {
+    type: String,
+    default: '',
+  },
+});
+defineEmits(['select', 'close']);
+
 const { t } = useI18n();
 const dialog = useDialog();
 const workflowStore = useWorkflowStore();
+const hostedWorkflows = useHostedWorkflowStore();
 const storedlogs = useLiveQuery(() => dbLogs.items.toArray());
 
 const savedSorts = JSON.parse(localStorage.getItem('logs-sorts') || '{}');
@@ -106,6 +169,8 @@ const filtersBuilder = shallowReactive({
   query: '',
   byDate: 0,
   byStatus: 'all',
+  workflowQuery: '',
+  workflowId: props.workflowId,
 });
 const sortsBuilder = shallowReactive({
   order: savedSorts.order || 'desc',
@@ -116,13 +181,47 @@ const exportDataModal = shallowReactive({
   log: {},
 });
 
+const allWorkflows = computed(() =>
+  [...hostedWorkflows.toArray, ...workflowStore.getWorkflows].sort((a, b) =>
+    a.createdAt > b.createdAt ? -1 : 1
+  )
+);
+const workflows = computed(() =>
+  allWorkflows.value.filter((workflow) =>
+    workflow.name
+      .toLocaleLowerCase()
+      .includes(filtersBuilder.workflowQuery.toLocaleLowerCase())
+  )
+);
+const activeWorkflowName = computed(() => {
+  if (!filtersBuilder.workflowId) return 'All workflows';
+
+  const workflow = allWorkflows.value.find(
+    (item) => item.id === filtersBuilder.workflowId
+  );
+
+  return workflow?.name ?? 'All workflows';
+});
+
+const workflowStates = computed(() => {
+  const states = workflowStore.getAllStates;
+  if (!filtersBuilder.workflowId) return states;
+
+  return states.filter(
+    (state) => state.workflowId === filtersBuilder.workflowId
+  );
+});
+
 const filteredLogs = computed(() => {
   if (!storedlogs.value) return [];
 
   return storedlogs.value
-    .filter(({ name, status, endedAt }) => {
+    .filter(({ name, status, endedAt, workflowId }) => {
       let dateFilter = true;
       let statusFilter = true;
+      const workflowIdFilter = filtersBuilder.workflowId
+        ? filtersBuilder.workflowId === workflowId
+        : true;
       const searchFilter = name
         .toLocaleLowerCase()
         .includes(filtersBuilder.query.toLocaleLowerCase());
@@ -137,7 +236,7 @@ const filteredLogs = computed(() => {
         dateFilter = date <= endedAt;
       }
 
-      return searchFilter && statusFilter && dateFilter;
+      return searchFilter && workflowIdFilter && statusFilter && dateFilter;
     })
     .slice()
     .sort((a, b) => {

+ 17 - 2
src/components/newtab/app/AppSidebar.vue

@@ -31,9 +31,9 @@
             }`
           "
           :class="{ 'is-active': isActive }"
-          :href="href"
+          :href="tab.id === 'log' ? '#' : href"
           class="z-10 relative w-full flex items-center justify-center tab relative"
-          @click="navigate"
+          @click="navigateLink($event, navigate, tab)"
           @mouseenter="hoverHandler"
         >
           <div class="p-2 rounded-lg transition-colors inline-block">
@@ -124,6 +124,7 @@ import { useShortcut, getShortcut } from '@/composable/shortcut';
 import { useGroupTooltip } from '@/composable/groupTooltip';
 import { communities } from '@/utils/shared';
 import { initElementSelector } from '@/newtab/utils/elementSelector';
+import emitter from '@/lib/mitt';
 
 useGroupTooltip();
 
@@ -192,10 +193,24 @@ useShortcut(
   ({ data }) => {
     if (!data) return;
 
+    if (data.includes('/logs')) {
+      emitter.emit('ui:logs', { show: true });
+      return;
+    }
+
     router.push(data);
   }
 );
 
+function navigateLink(event, navigateFn, tab) {
+  event.preventDefault();
+
+  if (tab.id === 'log') {
+    emitter.emit('ui:logs', { show: true });
+  } else {
+    navigateFn();
+  }
+}
 function hoverHandler({ target }) {
   showHoverIndicator.value = true;
   hoverIndicator.value.style.transform = `translate(-50%, ${target.offsetTop}px)`;

+ 6 - 9
src/components/newtab/logs/LogsFilters.vue

@@ -3,13 +3,12 @@
     <ui-input
       id="search-input"
       :model-value="filters.query"
-      :placeholder="`${t('common.search')}... (${
-        shortcut['action:search'].readable
-      })`"
+      :placeholder="`${t('common.search')}...`"
       prepend-icon="riSearch2Line"
       class="w-6/12 md:w-auto md:flex-1"
       @change="updateFilters('query', $event)"
     />
+    <slot />
     <div class="flex items-center workflow-sort w-5/12 ml-4 md:ml-0 md:w-auto">
       <ui-button
         icon
@@ -78,7 +77,6 @@
 </template>
 <script setup>
 import { useI18n } from 'vue-i18n';
-import { useShortcut } from '@/composable/shortcut';
 
 defineProps({
   filters: {
@@ -89,15 +87,14 @@ defineProps({
     type: Object,
     default: () => ({}),
   },
+  workflows: {
+    type: Array,
+    default: () => [],
+  },
 });
 const emit = defineEmits(['updateSorts', 'updateFilters', 'clear']);
 
 const { t } = useI18n();
-const shortcut = useShortcut('action:search', () => {
-  const searchInput = document.querySelector('#search-input input');
-
-  searchInput?.focus();
-});
 
 const filterByStatus = [
   { id: 'all', name: t('common.all') },

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

@@ -10,7 +10,7 @@
   </router-link>
   <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="rounded-lg bg-gray-900 text-gray-100 dark">
         <div
           class="border-b px-4 pt-4 flex items-center text-gray-200 pb-4 mb-4"
         >
@@ -44,7 +44,7 @@
           </div>
           <slot name="header-prepend" />
           <div class="flex-grow" />
-          <ui-popover trigger-width class="mr-4">
+          <ui-popover v-if="!isRunning" trigger-width class="mr-4">
             <template #trigger>
               <ui-button>
                 <span>
@@ -66,6 +66,7 @@
             </ui-list>
           </ui-popover>
           <ui-input
+            v-if="!isRunning"
             v-model="state.search"
             :placeholder="t('common.search')"
             prepend-icon="riSearch2Line"
@@ -73,7 +74,7 @@
         </div>
         <div
           id="log-history"
-          style="max-height: 600px"
+          style="max-height: 500px"
           class="scroll p-4 overflow-auto"
         >
           <slot name="prepend" />
@@ -209,7 +210,7 @@
     </div>
     <div
       v-if="state.itemId && activeLog"
-      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"
+      class="w-full lg:w-4/12 lg:ml-8 mb-4 lg:mb-0 rounded-lg bg-gray-900 text-gray-100 dark"
     >
       <div class="p-4 relative">
         <v-remixicon
@@ -318,6 +319,7 @@ const props = defineProps({
     type: Object,
     default: null,
   },
+  isRunning: Boolean,
 });
 
 const files = {

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

@@ -17,10 +17,17 @@
               />
             </td>
             <td class="w-4/12">
+              <p
+                v-if="modal"
+                class="log-link text-overflow"
+                @click="$emit('select', { type: 'running', id: item.id })"
+              >
+                {{ item.state.name }}
+              </p>
               <router-link
+                v-else
                 :to="`/logs/${item.id}/running`"
-                class="inline-block text-overflow w-full align-middle min-h"
-                style="min-height: 28px"
+                class="log-link text-overflow"
               >
                 {{ item.state.name }}
               </router-link>
@@ -65,10 +72,17 @@
             class="text-overflow w-4/12"
             style="min-width: 140px; max-width: 330px"
           >
+            <p
+              v-if="modal"
+              class="log-link text-overflow"
+              @click="$emit('select', { type: 'log', id: log.id })"
+            >
+              {{ log.name }}
+            </p>
             <router-link
+              v-else
               :to="`/logs/${log.id}`"
-              class="inline-block text-overflow w-full align-middle min-h"
-              style="min-height: 28px"
+              class="log-link text-overflow"
             >
               {{ log.name }}
             </router-link>
@@ -126,8 +140,10 @@ defineProps({
     type: Array,
     default: () => [],
   },
+  modal: Boolean,
   hideSelect: Boolean,
 });
+defineEmits(['select']);
 
 const { t, te } = useI18n();
 
@@ -186,4 +202,10 @@ function stopSelectedWorkflow() {
   display: inline-block;
   vertical-align: middle;
 }
+
+.log-link {
+  @apply inline-block w-full align-middle;
+  cursor: pointer;
+  min-height: 28px;
+}
 </style>

+ 14 - 2
src/components/ui/UiModal.vue

@@ -7,7 +7,8 @@
       <transition name="modal" mode="out-in">
         <div
           v-if="show"
-          class="overflow-y-auto modal-ui__content-container z-50 flex justify-center items-center"
+          :class="[positions[contentPosition]]"
+          class="overflow-y-auto modal-ui__content-container z-50 flex justify-center"
           :style="{ 'backdrop-filter': blur && 'blur(2px)' }"
         >
           <div
@@ -68,6 +69,10 @@ export default {
       type: String,
       default: 'p-4',
     },
+    contentPosition: {
+      type: String,
+      default: 'center',
+    },
     customContent: Boolean,
     persist: Boolean,
     blur: Boolean,
@@ -75,6 +80,11 @@ export default {
   },
   emits: ['close', 'update:modelValue'],
   setup(props, { emit }) {
+    const positions = {
+      center: 'items-center',
+      start: 'items-start',
+    };
+
     const show = ref(false);
     const modalContent = ref(null);
 
@@ -98,7 +108,6 @@ export default {
       () => props.modelValue,
       (value) => {
         show.value = value;
-        toggleBodyOverflow(value);
       },
       { immediate: true }
     );
@@ -106,10 +115,13 @@ export default {
     watch(show, (value) => {
       if (value) window.addEventListener('keyup', keyupHandler);
       else window.removeEventListener('keyup', keyupHandler);
+
+      toggleBodyOverflow(value);
     });
 
     return {
       show,
+      positions,
       closeModal,
       modalContent,
     };

+ 9 - 0
src/newtab/App.vue

@@ -4,6 +4,7 @@
     <main :class="{ 'pl-16': $route.name !== 'recording' }">
       <router-view />
     </main>
+    <app-logs />
     <ui-dialog>
       <template #auth>
         <div class="text-center">
@@ -76,10 +77,12 @@ import { getUserWorkflows } from '@/utils/api';
 import { getWorkflowPermissions } from '@/utils/workflowData';
 import { sendMessage } from '@/utils/message';
 import { workflowState, startWorkflowExec } from '@/workflowEngine';
+import emitter from '@/lib/mitt';
 import automa from '@business';
 import dbLogs from '@/db/logs';
 import dayjs from '@/lib/dayjs';
 import AppSurvey from '@/components/newtab/app/AppSurvey.vue';
+import AppLogs from '@/components/newtab/app/AppLogs.vue';
 import AppSidebar from '@/components/newtab/app/AppSidebar.vue';
 import dataMigration from '@/utils/dataMigration';
 import iconFirefox from '@/assets/svg/logoFirefox.svg';
@@ -204,6 +207,12 @@ const messageEvents = {
   'refresh-packages': function () {
     packageStore.loadData(true);
   },
+  'open-logs': function (data) {
+    emitter.emit('ui:logs', {
+      show: true,
+      logId: data.logId,
+    });
+  },
   'workflow:added': function (data) {
     if (data.source === 'team') {
       teamWorkflowStore.loadData().then(() => {

+ 9 - 187
src/newtab/pages/logs/[id].vue

@@ -1,198 +1,20 @@
 <template>
-  <div v-if="currentLog.id" class="container pt-8 pb-4">
-    <div class="flex items-center">
-      <button
-        v-tooltip:bottom="t('workflow.blocks.go-back.name')"
-        role="button"
-        class="h-12 px-1 transition mr-2 bg-input rounded-lg dark:text-gray-300 text-gray-600"
-        @click="goBack"
-      >
-        <v-remixicon name="riArrowLeftSLine" />
-      </button>
-      <div>
-        <h1 class="text-2xl max-w-md text-overflow font-semibold">
-          {{ currentLog.name }}
-        </h1>
-        <p class="text-gray-600 dark:text-gray-200">
-          {{
-            t(`log.description.text`, {
-              status: t(
-                `log.description.status.${currentLog.status || 'success'}`
-              ),
-              date: dayjs(currentLog.startedAt).format('DD MMM'),
-              duration: countDuration(currentLog.startedAt, currentLog.endedAt),
-            })
-          }}
-        </p>
-      </div>
-      <div class="flex-grow"></div>
-      <ui-button
-        v-if="state.workflowExists"
-        v-tooltip="t('log.goWorkflow')"
-        icon
-        class="mr-4"
-        @click="goToWorkflow"
-      >
-        <v-remixicon name="riExternalLinkLine" />
-      </ui-button>
-      <ui-button class="text-red-500 dark:text-red-400" @click="deleteLog">
-        {{ t('common.delete') }}
-      </ui-button>
-    </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>
+  <p>Hello :)</p>
 </template>
 <script setup>
-import { shallowReactive, shallowRef, watch } from 'vue';
+import { onMounted } from 'vue';
 import { useRoute, useRouter } from 'vue-router';
-import { useI18n } from 'vue-i18n';
-import dbLogs from '@/db/logs';
-import dayjs from '@/lib/dayjs';
-import { useWorkflowStore } from '@/stores/workflow';
-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';
+import emitter from '@/lib/mitt';
 
-const { t } = useI18n();
 const route = useRoute();
 const router = useRouter();
-const workflowStore = useWorkflowStore();
 
-const ctxData = shallowRef({});
-const parentLog = shallowRef(null);
+onMounted(() => {
+  emitter.emit('ui:logs', {
+    show: true,
+    logId: route.params.id,
+  });
 
-const backHistory = window.history.state.back;
-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: [],
+  router.replace('/');
 });
-const currentLog = shallowRef({
-  history: [],
-  data: {
-    table: [],
-    variables: {},
-  },
-});
-
-function goBack() {
-  router.go(-1);
-}
-function deleteLog() {
-  dbLogs.items
-    .where('id')
-    .equals(route.params.id)
-    .delete()
-    .then(() => {
-      if (backHistory?.startsWith('/workflows')) {
-        router.replace(backHistory);
-        return;
-      }
-
-      router.replace('/logs');
-    });
-}
-function goToWorkflow() {
-  let path = `/workflows/${currentLog.value.workflowId}`;
-
-  if (backHistory?.startsWith(path)) {
-    path = backHistory;
-  }
-
-  router.push(path);
-}
-function convertToTableData() {
-  const data = currentLog.value.data?.table;
-  if (!data) return;
-
-  const [header] = convertArrObjTo2DArr(data);
-
-  tableData.converted = true;
-  tableData.body = data.map((item, index) => ({ ...item, id: index + 1 }));
-  tableData.header = header.map((name) => ({
-    text: name,
-    value: name,
-    filterable: true,
-  }));
-  tableData.header.unshift({ value: 'id', text: '', sortable: false });
-}
-function onTabChange(value) {
-  if (value === 'table' && !tableData.converted) {
-    convertToTableData();
-  }
-}
-async function fetchLog() {
-  const logId = route.params.id;
-  if (!logId) return;
-
-  const logDetail = await dbLogs.items.where('id').equals(logId).last();
-  if (!logDetail) return;
-
-  tableData.body = [];
-  tableData.header = [];
-  parentLog.value = null;
-  tableData.converted = false;
-
-  const [logCtxData, logHistory, logsData] = await Promise.all(
-    ['ctxData', 'histories', 'logsData'].map((key) =>
-      dbLogs[key].where('logId').equals(logId).last()
-    )
-  );
-
-  ctxData.value = logCtxData?.data || {};
-  currentLog.value = {
-    history: logHistory?.data || [],
-    data: logsData?.data || {},
-    ...logDetail,
-  };
-
-  state.workflowExists = Boolean(workflowStore.getById(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, fetchLog, { immediate: true });
 </script>
-<style>
-.logs-details .cm-editor {
-  max-height: calc(100vh - 15rem);
-}
-</style>

+ 9 - 9
src/newtab/pages/workflows/Host.vue

@@ -30,12 +30,12 @@
         </div>
       </ui-card>
       <ui-tabs
-        v-model="state.activeTab"
+        model-value="'editor'"
         class="border-none px-2 rounded-lg h-full space-x-1 bg-white dark:bg-gray-800 ml-4"
         style="height: 48px"
       >
         <ui-tab value="editor">{{ t('common.editor') }}</ui-tab>
-        <ui-tab value="logs">
+        <ui-tab value="logs" @click="openLogs">
           {{ t('common.log', 2) }}
           <span
             v-if="workflowStates.length > 0"
@@ -101,12 +101,6 @@
           @init="onEditorInit"
         />
       </ui-tab-panel>
-      <ui-tab-panel value="logs">
-        <editor-logs
-          :workflow-id="workflowId"
-          :workflow-states="workflowStates"
-        />
-      </ui-tab-panel>
     </ui-tab-panels>
   </div>
 </template>
@@ -123,8 +117,8 @@ import { useWorkflowStore } from '@/stores/workflow';
 import { executeWorkflow } from '@/workflowEngine';
 import { useHostedWorkflowStore } from '@/stores/hostedWorkflow';
 import getTriggerText from '@/utils/triggerText';
-import EditorLogs from '@/components/newtab/workflow/editor/EditorLogs.vue';
 import WorkflowEditor from '@/components/newtab/workflow/WorkflowEditor.vue';
+import emitter from '@/lib/mitt';
 
 useGroupTooltip();
 
@@ -161,6 +155,12 @@ const workflowStates = computed(() =>
   workflowStore.getWorkflowStates(workflowId)
 );
 
+function openLogs() {
+  emitter.emit('ui:logs', {
+    workflowId,
+    show: true,
+  });
+}
 function syncWorkflow() {
   state.loadingSync = true;
   const hostId = {

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

@@ -57,8 +57,9 @@
           </div>
         </ui-card>
         <ui-tabs
-          v-model="state.activeTab"
+          :model-value="'editor'"
           class="border-none px-2 rounded-lg h-full space-x-1 bg-white dark:bg-gray-800 pointer-events-auto"
+          @change="onTabChange"
         >
           <button
             v-if="haveEditAccess"
@@ -234,12 +235,6 @@
             @duplicate="duplicateElements"
           />
         </ui-tab-panel>
-        <ui-tab-panel value="logs" class="mt-24 container">
-          <editor-logs
-            :workflow-id="route.params.id"
-            :workflow-states="workflowStates"
-          />
-        </ui-tab-panel>
       </ui-tab-panels>
     </div>
   </div>
@@ -323,6 +318,7 @@ import { useCommandManager } from '@/composable/commandManager';
 import { debounce, parseJSON, throttle } from '@/utils/helper';
 import { executeWorkflow } from '@/workflowEngine';
 import { registerWorkflowTrigger } from '@/utils/workflowTrigger';
+import emitter from '@/lib/mitt';
 import functions from '@/workflowEngine/templating/templatingFunctions';
 import browser from 'webextension-polyfill';
 import dbStorage from '@/db/storage';
@@ -340,7 +336,6 @@ import WorkflowDataTable from '@/components/newtab/workflow/WorkflowDataTable.vu
 import WorkflowGlobalData from '@/components/newtab/workflow/WorkflowGlobalData.vue';
 import WorkflowDetailsCard from '@/components/newtab/workflow/WorkflowDetailsCard.vue';
 import SharedPermissionsModal from '@/components/newtab/shared/SharedPermissionsModal.vue';
-import EditorLogs from '@/components/newtab/workflow/editor/EditorLogs.vue';
 import EditorAddPackage from '@/components/newtab/workflow/editor/EditorAddPackage.vue';
 import EditorPkgActions from '@/components/newtab/workflow/editor/EditorPkgActions.vue';
 import EditorLocalCtxMenu from '@/components/newtab/workflow/editor/EditorLocalCtxMenu.vue';
@@ -671,6 +666,16 @@ const onEdgesChange = debounce((changes) => {
   // if (command) commandManager.add(command);
 }, 250);
 
+function onTabChange(tabVal) {
+  if (tabVal !== 'logs') return;
+
+  state.activeTab = 'editor';
+
+  emitter.emit('ui:logs', {
+    workflowId,
+    show: true,
+  });
+}
 function onUpdateBlockSettings({ blockId, itemId, settings }) {
   state.dataChanged = true;
 

+ 0 - 12
src/newtab/router.js

@@ -8,9 +8,7 @@ import WorkflowShared from './pages/workflows/Shared.vue';
 import ScheduledWorkflow from './pages/ScheduledWorkflow.vue';
 import Storage from './pages/Storage.vue';
 import StorageTables from './pages/storage/Tables.vue';
-import Logs from './pages/Logs.vue';
 import LogsDetails from './pages/logs/[id].vue';
-import LogsRunning from './pages/logs/Running.vue';
 import Recording from './pages/Recording.vue';
 import Settings from './pages/Settings.vue';
 import SettingsIndex from './pages/settings/SettingsIndex.vue';
@@ -86,21 +84,11 @@ const routes = [
     path: '/storage/tables/:id',
     component: StorageTables,
   },
-  {
-    name: 'logs',
-    path: '/logs',
-    component: Logs,
-  },
   {
     name: 'logs-details',
     path: '/logs/:id',
     component: LogsDetails,
   },
-  {
-    name: 'logs-running',
-    path: '/logs/:id/running',
-    component: LogsRunning,
-  },
   {
     path: '/settings',
     component: Settings,