feat(sj_map_reduce): 日志新增刷新频率支持
This commit is contained in:
parent
dd00ff5be0
commit
c366c22881
10
public/iconify/nonicons.json
Normal file
10
public/iconify/nonicons.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"prefix": "nonicons",
|
||||||
|
"lastModified": 1702313196,
|
||||||
|
"aliases": {},
|
||||||
|
"icons": {
|
||||||
|
"loading-16": {
|
||||||
|
"body": "<path fill=\"currentColor\" fill-rule=\"evenodd\" d=\"M7.706.29c-.222.072-.35.2-.412.409c-.035.117-.041.389-.041 1.809c0 1.881-.002 1.857.19 2.049c.257.256.857.256 1.114 0c.192-.192.19-.168.19-2.049c0-1.82-.003-1.852-.151-2.028C8.472.333 8.339.284 8.04.276a1.705 1.705 0 0 0-.334.014M2.753 2.266c-.158.072-.391.3-.472.462a.605.605 0 0 0-.012.525c.074.165 2.398 2.497 2.581 2.59c.259.133.525.068.793-.194c.264-.258.334-.538.2-.799c-.093-.183-2.425-2.507-2.59-2.581a.638.638 0 0 0-.5-.003m10.1.016c-.123.057-.333.254-1.335 1.259c-.921.923-1.202 1.221-1.247 1.319a.617.617 0 0 0 .001.518c.07.15.3.386.455.467c.157.082.39.081.553-.002c.167-.086 2.477-2.396 2.563-2.563a.648.648 0 0 0 .003-.551a1.26 1.26 0 0 0-.454-.446a.569.569 0 0 0-.539-.001M.699 7.292c-.295.093-.441.328-.441.707c.001.387.145.619.44.707c.118.035.381.041 1.81.041c1.489 0 1.688-.005 1.81-.045a.602.602 0 0 0 .384-.384c.086-.265.043-.641-.094-.827a.723.723 0 0 0-.191-.148l-.137-.076l-1.733-.006c-1.395-.004-1.756.002-1.848.031m11.046-.014a.757.757 0 0 0-.353.214c-.137.185-.18.561-.094.826c.058.18.204.326.384.384c.122.04.321.045 1.81.045c1.429 0 1.692-.006 1.81-.041c.295-.088.439-.32.44-.707c0-.385-.147-.616-.452-.708c-.103-.031-.426-.037-1.794-.035c-.918.002-1.706.012-1.751.022m-6.892 3.004c-.123.057-.333.254-1.335 1.259c-.921.923-1.202 1.221-1.247 1.319a.617.617 0 0 0 .001.518c.07.15.3.386.455.467c.157.082.39.081.553-.002c.167-.086 2.477-2.396 2.563-2.563a.648.648 0 0 0 .003-.551a1.26 1.26 0 0 0-.454-.446a.569.569 0 0 0-.539-.001m5.9-.016c-.158.072-.391.3-.472.462a.605.605 0 0 0-.012.525c.074.165 2.398 2.497 2.581 2.59c.259.133.525.068.793-.194c.264-.258.334-.538.2-.799c-.093-.183-2.425-2.507-2.59-2.581a.638.638 0 0 0-.5-.003m-3.008 1.011a.768.768 0 0 0-.353.215c-.138.186-.139.199-.139 1.997c0 1.432.006 1.695.041 1.813c.088.295.321.439.706.439c.385 0 .618-.144.706-.439c.062-.212.061-3.427-.002-3.612a.528.528 0 0 0-.284-.344c-.11-.06-.174-.075-.363-.082a1.537 1.537 0 0 0-.312.013\"/>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
public/iconify/solar.json
Normal file
12
public/iconify/solar.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"prefix": "solar",
|
||||||
|
"lastModified": 1702314058,
|
||||||
|
"aliases": {},
|
||||||
|
"width": 24,
|
||||||
|
"height": 24,
|
||||||
|
"icons": {
|
||||||
|
"refresh-outline": {
|
||||||
|
"body": "<path fill=\"currentColor\" fill-rule=\"evenodd\" d=\"M2.93 11.2c.072-4.96 4.146-8.95 9.149-8.95a9.158 9.158 0 0 1 7.814 4.357a.75.75 0 0 1-1.277.786a7.658 7.658 0 0 0-6.537-3.643c-4.185 0-7.575 3.328-7.648 7.448l.4-.397a.75.75 0 0 1 1.057 1.065l-1.68 1.666a.75.75 0 0 1-1.056 0l-1.68-1.666A.75.75 0 1 1 2.528 10.8zm16.856-.733a.75.75 0 0 1 1.055 0l1.686 1.666a.75.75 0 1 1-1.054 1.067l-.41-.405c-.07 4.965-4.161 8.955-9.18 8.955a9.197 9.197 0 0 1-7.842-4.356a.75.75 0 1 1 1.277-.788a7.697 7.697 0 0 0 6.565 3.644c4.206 0 7.61-3.333 7.68-7.453l-.408.403a.75.75 0 1 1-1.055-1.067z\" clip-rule=\"evenodd\"/>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { NCollapse, NCollapseItem } from 'naive-ui';
|
import { NButton, NCard, NCollapse, NCollapseItem, NDivider, NDropdown, NEmpty, NSpin, NVirtualList } from 'naive-ui';
|
||||||
import { defineComponent, watch } from 'vue';
|
import { defineComponent, onBeforeUnmount, ref, watch } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { $t } from '@/locales';
|
import { fetchJobLogList, fetchRetryLogList } from '@/service/api/log';
|
||||||
import { useLogStore } from '@/store/modules/log';
|
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'LogDrawer'
|
name: 'LogDrawer'
|
||||||
@ -13,13 +13,17 @@ interface Props {
|
|||||||
title?: string;
|
title?: string;
|
||||||
show?: boolean;
|
show?: boolean;
|
||||||
drawer?: boolean;
|
drawer?: boolean;
|
||||||
|
type?: 'job' | 'retry';
|
||||||
|
taskData?: Api.Job.JobTask | Api.RetryLog.RetryLog | Api.RetryTask.RetryTask;
|
||||||
modelValue?: Api.JobLog.JobMessage[];
|
modelValue?: Api.JobLog.JobMessage[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
title: $t('page.log.title'),
|
title: undefined,
|
||||||
show: false,
|
show: false,
|
||||||
drawer: true,
|
drawer: true,
|
||||||
|
type: 'job',
|
||||||
|
taskData: undefined,
|
||||||
modelValue: () => []
|
modelValue: () => []
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -32,48 +36,95 @@ const visible = defineModel<boolean>('visible', {
|
|||||||
default: true
|
default: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const ThrowableComponent = defineComponent({
|
const syncTime = ref(1);
|
||||||
props: {
|
const logList = ref<Api.JobLog.JobMessage[]>([]);
|
||||||
throwable: {
|
const interval = ref<NodeJS.Timeout>();
|
||||||
type: String,
|
const controller = new AbortController();
|
||||||
default: ''
|
const finished = ref<boolean>(false);
|
||||||
}
|
let startId = '0';
|
||||||
},
|
let fromIndex: number = 0;
|
||||||
setup(thProps) {
|
|
||||||
return () => {
|
const stopLog = () => {
|
||||||
if (!thProps.throwable) {
|
finished.value = true;
|
||||||
return <></>;
|
controller.abort();
|
||||||
}
|
clearTimeout(interval.value);
|
||||||
const firstLine = thProps.throwable.match(/^.+/m);
|
interval.value = undefined;
|
||||||
if (!firstLine) {
|
};
|
||||||
return <></>;
|
|
||||||
}
|
async function getLogList() {
|
||||||
const restOfText = thProps.throwable.replace(/^.+(\n|$)/m, '');
|
let logData = null;
|
||||||
return (
|
let logError;
|
||||||
<NCollapse>
|
if (props.type === 'job') {
|
||||||
<NCollapseItem title={firstLine[0]} name="1">
|
const taskData = props.taskData! as Api.Job.JobTask;
|
||||||
{`${restOfText}`}
|
const { data, error } = await fetchJobLogList({
|
||||||
</NCollapseItem>
|
taskBatchId: taskData.taskBatchId,
|
||||||
</NCollapse>
|
jobId: taskData.jobId,
|
||||||
);
|
taskId: taskData.id,
|
||||||
};
|
startId,
|
||||||
|
fromIndex,
|
||||||
|
size: 50
|
||||||
|
});
|
||||||
|
logData = data;
|
||||||
|
logError = error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (props.type === 'retry') {
|
||||||
|
const taskData = props.taskData! as Api.RetryLog.RetryLog | Api.RetryTask.RetryTask;
|
||||||
|
const { data, error } = await fetchRetryLogList({
|
||||||
|
groupName: taskData.groupName,
|
||||||
|
uniqueId: taskData.uniqueId!,
|
||||||
|
startId,
|
||||||
|
fromIndex,
|
||||||
|
size: 50
|
||||||
|
});
|
||||||
|
logData = data;
|
||||||
|
logError = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!logError && logData) {
|
||||||
|
finished.value = logData.finished;
|
||||||
|
startId = logData.nextStartId;
|
||||||
|
fromIndex = logData.fromIndex;
|
||||||
|
if (logData.message) {
|
||||||
|
logList.value.push(...logData.message);
|
||||||
|
logList.value.sort((a, b) => Number.parseInt(a.time_stamp, 10) - Number.parseInt(b.time_stamp, 10));
|
||||||
|
}
|
||||||
|
if (!finished.value) {
|
||||||
|
clearTimeout(interval.value);
|
||||||
|
interval.value = setTimeout(getLogList, syncTime.value * 1000);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stopLog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
stopLog();
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.show,
|
() => props.show,
|
||||||
val => {
|
async val => {
|
||||||
visible.value = val;
|
visible.value = val;
|
||||||
|
if (val) {
|
||||||
|
if (props.modelValue) {
|
||||||
|
logList.value = props.modelValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((val || !props.drawer) && props.type && props.taskData) {
|
||||||
|
finished.value = false;
|
||||||
|
await getLogList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!val && props.drawer) {
|
||||||
|
stopLog();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
const store = useLogStore();
|
|
||||||
|
|
||||||
const onUpdateShow = (value: boolean) => {
|
const onUpdateShow = (value: boolean) => {
|
||||||
if (!value) {
|
|
||||||
store.clear();
|
|
||||||
}
|
|
||||||
emit('update:show', value);
|
emit('update:show', value);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -92,10 +143,119 @@ function timestampToDate(timestamp: string): string {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
function openNewTab() {
|
function openNewTab() {
|
||||||
const url = router.resolve('/log');
|
let query;
|
||||||
store.setData(props.modelValue);
|
if (props.type === 'job') {
|
||||||
|
query = {
|
||||||
|
type: props.type,
|
||||||
|
taskBatchId: (props.taskData as Api.Job.JobTask).taskBatchId,
|
||||||
|
jobId: (props.taskData as Api.Job.JobTask).jobId,
|
||||||
|
taskId: (props.taskData as Api.Job.JobTask).id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.type === 'retry') {
|
||||||
|
query = {
|
||||||
|
type: props.type,
|
||||||
|
groupName: (props.taskData as Api.RetryLog.RetryLog | Api.RetryTask.RetryTask).groupName,
|
||||||
|
uniqueId: (props.taskData as Api.RetryLog.RetryLog | Api.RetryTask.RetryTask).uniqueId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const url = router.resolve({ path: '/log', query });
|
||||||
window.open(url.href);
|
window.open(url.href);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSyncSelect = async (time: number) => {
|
||||||
|
if (time === -1) {
|
||||||
|
finished.value = false;
|
||||||
|
await getLogList();
|
||||||
|
}
|
||||||
|
if (time === 0) {
|
||||||
|
stopLog();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
syncTime.value = time;
|
||||||
|
finished.value = false;
|
||||||
|
await getLogList();
|
||||||
|
};
|
||||||
|
|
||||||
|
const syncOptions = ref([
|
||||||
|
{
|
||||||
|
label: 'Off',
|
||||||
|
key: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Auto(1s)',
|
||||||
|
key: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '5s',
|
||||||
|
key: 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '10s',
|
||||||
|
key: 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '30s',
|
||||||
|
key: 30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '1m',
|
||||||
|
key: 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '5m',
|
||||||
|
key: 300
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const SnailLogComponent = defineComponent({
|
||||||
|
setup() {
|
||||||
|
if (finished.value && logList.value.length === 0) {
|
||||||
|
return () => <NEmpty class="h-full flex-center" size="huge" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const throwableComponent = (throwable: string) => {
|
||||||
|
if (!throwable) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
const firstLine = throwable.match(/^.+/m);
|
||||||
|
if (!firstLine) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
const restOfText = throwable.replace(/^.+(\n|$)/m, '');
|
||||||
|
return (
|
||||||
|
<NCollapse>
|
||||||
|
<NCollapseItem title={firstLine[0]} name="1">
|
||||||
|
{`${restOfText}`}
|
||||||
|
</NCollapseItem>
|
||||||
|
</NCollapse>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<code class="snail-log">
|
||||||
|
<NVirtualList class="virtual-list" itemSize={65} items={logList.value}>
|
||||||
|
{{
|
||||||
|
default: ({ item: message }: { item: Api.JobLog.JobMessage }) => (
|
||||||
|
<pre>
|
||||||
|
<span class="log-hljs-time inline-block">{timestampToDate(message.time_stamp)}</span>
|
||||||
|
<span class={`log-hljs-level-${message.level} ml-12px mr-12px inline-block`}>{`${message.level}`}</span>
|
||||||
|
<span class="log-hljs-thread mr-12px inline-block">{`[${message.host}:${message.port}]`}</span>
|
||||||
|
<span class="log-hljs-thread mr-12px inline-block">{`[${message.thread}]`}</span>
|
||||||
|
<span class="log-hljs-location">{`${message.location}: \n`}</span> -
|
||||||
|
<span class="pl-6px">{`${message.message}`}</span>
|
||||||
|
{throwableComponent(message.throwable)}
|
||||||
|
<NDivider />
|
||||||
|
</pre>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</NVirtualList>
|
||||||
|
</code>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -103,63 +263,132 @@ function openNewTab() {
|
|||||||
<NDrawerContent closable>
|
<NDrawerContent closable>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex-center">
|
<div class="flex-center">
|
||||||
<span>{{ title }}</span>
|
<NTooltip v-if="finished">
|
||||||
<ButtonIcon icon="hugeicons:share-01" tooltip-content="在新标签页打开" class="ml-3px" @click="openNewTab" />
|
<template #trigger>
|
||||||
|
<icon-material-symbols:check-circle class="text-20px color-success" />
|
||||||
|
</template>
|
||||||
|
日志加载完成
|
||||||
|
</NTooltip>
|
||||||
|
<NTooltip v-else>
|
||||||
|
<template #trigger>
|
||||||
|
<NSpin size="small">
|
||||||
|
<template #icon>
|
||||||
|
<icon-nonicons:loading-16 />
|
||||||
|
</template>
|
||||||
|
</NSpin>
|
||||||
|
</template>
|
||||||
|
日志正在加载
|
||||||
|
</NTooltip>
|
||||||
|
<span class="ml-6px">{{ title }}</span>
|
||||||
|
<ButtonIcon icon="hugeicons:share-01" tooltip-content="在新标签页打开" class="ml-6px" @click="openNewTab" />
|
||||||
|
<NDropdown trigger="hover" :options="syncOptions" @select="handleSyncSelect">
|
||||||
|
<NTooltip>
|
||||||
|
<template #trigger>
|
||||||
|
<NButton quaternary class="ml-3px" @click="handleSyncSelect(-1)">
|
||||||
|
<template #icon>
|
||||||
|
<div class="flex-center gap-8px">
|
||||||
|
<icon-solar:refresh-outline class="text-18px" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
自动刷新频率
|
||||||
|
</NTooltip>
|
||||||
|
</NDropdown>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="snail-log bg-#fafafc p-16px dark:bg-#000">
|
<SnailLogComponent />
|
||||||
<div class="snail-log-scrollbar">
|
|
||||||
<code>
|
|
||||||
<NVirtualList class="virtual-list" :item-size="42" :items="modelValue">
|
|
||||||
<template #default="{ item: message, index }">
|
|
||||||
<pre><NDivider v-if="index !== 0" /><span class="log-hljs-time inline-block">{{timestampToDate(message.time_stamp)}}</span><span :class="`log-hljs-level-${message.level}`" class="ml-12px mr-12px inline-block">{{`${message.level}`}}</span><span class="log-hljs-thread mr-12px inline-block">{{ `[${message.host}:${message.port}]` }}</span><span class="log-hljs-thread mr-12px inline-block">{{`[${message.thread}]`}}</span><span class="log-hljs-location">{{`${message.location}: \n`}}</span> -<span class="pl-6px">{{`${message.message}`}}</span><ThrowableComponent :throwable="message.throwable" /></pre>
|
|
||||||
</template>
|
|
||||||
</NVirtualList>
|
|
||||||
</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</NDrawerContent>
|
</NDrawerContent>
|
||||||
</NDrawer>
|
</NDrawer>
|
||||||
<div v-if="!drawer" class="snail-log">
|
<NCard v-else :bordered="false" :title="title" size="small" class="h-full sm:flex-1-hidden card-wrapper">
|
||||||
<div class="snail-log-scrollbar">
|
<template #header-extra>
|
||||||
<code>
|
<div class="flex items-center">
|
||||||
<NVirtualList class="virtual-list" :item-size="42" :items="modelValue">
|
<NDropdown trigger="hover" :options="syncOptions" @select="handleSyncSelect">
|
||||||
<template #default="{ item: message, index }">
|
<NTooltip>
|
||||||
<pre><NDivider v-if="index !== 0" /><span class="log-hljs-time inline-block">{{timestampToDate(message.time_stamp)}}</span><span :class="`log-hljs-level-${message.level}`" class="ml-12px mr-12px inline-block">{{`${message.level}`}}</span><span class="log-hljs-thread mr-12px inline-block">{{ `[${message.host}:${message.port}]` }}</span><span class="log-hljs-thread mr-12px inline-block">{{`[${message.thread}]`}}</span><span class="log-hljs-location">{{`${message.location}: \n`}}</span> -<span class="pl-6px">{{`${message.message}`}}</span><ThrowableComponent :throwable="message.throwable" /></pre>
|
<template #trigger>
|
||||||
|
<NButton quaternary class="ml-3px" @click="handleSyncSelect(-1)">
|
||||||
|
<template #icon>
|
||||||
|
<div class="flex-center gap-8px">
|
||||||
|
<icon-solar:refresh-outline class="text-18px" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
自动刷新频率
|
||||||
|
</NTooltip>
|
||||||
|
</NDropdown>
|
||||||
|
<NTooltip v-if="finished">
|
||||||
|
<template #trigger>
|
||||||
|
<icon-material-symbols:check-circle class="text-20px color-success" />
|
||||||
</template>
|
</template>
|
||||||
</NVirtualList>
|
日志加载完成
|
||||||
</code>
|
</NTooltip>
|
||||||
</div>
|
<NTooltip v-else>
|
||||||
</div>
|
<template #trigger>
|
||||||
|
<NSpin size="small">
|
||||||
|
<template #icon>
|
||||||
|
<icon-nonicons:loading-16 />
|
||||||
|
</template>
|
||||||
|
</NSpin>
|
||||||
|
</template>
|
||||||
|
日志正在加载
|
||||||
|
</NTooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<SnailLogComponent />
|
||||||
|
</NCard>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style lang="scss">
|
||||||
.snail-log {
|
.snail-log {
|
||||||
width: 100%;
|
padding: 0;
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.virtual-list {
|
.virtual-list {
|
||||||
max-height: calc(100vh - 101px);
|
max-height: calc(100vh - 101px);
|
||||||
}
|
}
|
||||||
|
|
||||||
&-scrollbar {
|
.n-divider:not(.n-divider--vertical) {
|
||||||
padding: 0;
|
margin-top: 6px;
|
||||||
height: 100%;
|
margin-bottom: 6px;
|
||||||
width: 100%;
|
}
|
||||||
overflow: auto;
|
|
||||||
@include scrollbar();
|
|
||||||
|
|
||||||
.n-divider:not(.n-divider--vertical) {
|
pre {
|
||||||
margin-top: 6px;
|
white-space: pre-wrap;
|
||||||
margin-bottom: 6px;
|
word-break: break-word;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333639;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-hljs {
|
||||||
|
&-time {
|
||||||
|
color: #2db7f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre {
|
&-level {
|
||||||
white-space: pre-wrap;
|
&-DEBUG {
|
||||||
word-break: break-word;
|
color: #2647cc;
|
||||||
margin: 0;
|
}
|
||||||
font-size: 16px;
|
|
||||||
color: #333639;
|
&-INFO {
|
||||||
|
color: #5c962c;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-WARN {
|
||||||
|
color: #da9816;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-ERROR {
|
||||||
|
color: #dc3f41;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-thread {
|
||||||
|
color: #00a3a3;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-location {
|
||||||
|
color: #a771bf;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -173,36 +402,13 @@ function openNewTab() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
.log-hljs {
|
<style scoped>
|
||||||
&-time {
|
:deep(.n-spin) {
|
||||||
color: #2db7f5;
|
height: 18px !important;
|
||||||
}
|
width: 18px !important;
|
||||||
|
font-size: 18px !important;
|
||||||
&-level {
|
margin-right: 6px;
|
||||||
&-DEBUG {
|
|
||||||
color: #2647cc;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-INFO {
|
|
||||||
color: #5c962c;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-WARN {
|
|
||||||
color: #da9816;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-ERROR {
|
|
||||||
color: #dc3f41;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-thread {
|
|
||||||
color: #00a3a3;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-location {
|
|
||||||
color: #a771bf;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { onBeforeUnmount, ref, watch } from 'vue';
|
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
import { fetchJobLogList } from '@/service/api/log';
|
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'FlowLogDrawer'
|
name: 'FlowLogDrawer'
|
||||||
@ -12,138 +10,17 @@ interface Props {
|
|||||||
taskData: Workflow.JobTaskType;
|
taskData: Workflow.JobTaskType;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
withDefaults(defineProps<Props>(), {
|
||||||
title: $t('workflow.node.log.title'),
|
title: $t('workflow.node.log.title')
|
||||||
modelValue: () => []
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const show = defineModel<boolean>('show', {
|
const show = defineModel<boolean>('show', {
|
||||||
default: true
|
default: true
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
|
||||||
() => show.value,
|
|
||||||
val => {
|
|
||||||
if (val) {
|
|
||||||
getLogList();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
const logList = ref<Api.JobLog.JobMessage[]>([]);
|
|
||||||
const interval = ref<NodeJS.Timeout>();
|
|
||||||
const controller = new AbortController();
|
|
||||||
const finished = ref<boolean>(false);
|
|
||||||
let startId = '0';
|
|
||||||
let fromIndex: number = 0;
|
|
||||||
|
|
||||||
async function getLogList() {
|
|
||||||
const { data: logData, error } = await fetchJobLogList({
|
|
||||||
taskBatchId: props.taskData.taskBatchId!,
|
|
||||||
jobId: props.taskData.jobId!,
|
|
||||||
taskId: props.taskData.id!,
|
|
||||||
startId,
|
|
||||||
fromIndex,
|
|
||||||
size: 50
|
|
||||||
});
|
|
||||||
if (!error) {
|
|
||||||
finished.value = logData.finished;
|
|
||||||
startId = logData.nextStartId;
|
|
||||||
fromIndex = logData.fromIndex;
|
|
||||||
if (logData.message) {
|
|
||||||
logList.value.push(...logData.message);
|
|
||||||
logList.value.sort((a, b) => Number.parseInt(a.time_stamp, 10) - Number.parseInt(b.time_stamp, 10));
|
|
||||||
}
|
|
||||||
if (!finished.value) {
|
|
||||||
clearTimeout(interval.value);
|
|
||||||
interval.value = setTimeout(getLogList, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const stopLog = () => {
|
|
||||||
finished.value = true;
|
|
||||||
controller.abort();
|
|
||||||
clearTimeout(interval.value);
|
|
||||||
interval.value = undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
stopLog();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<LogDrawer v-model="logList" v-model:show="show" :title="title" />
|
<LogDrawer v-model:show="show" :title="title" :task-data="taskData as any" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped></style>
|
||||||
.snail-log {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
&-scrollbar {
|
|
||||||
padding: 0;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
@include scrollbar();
|
|
||||||
|
|
||||||
.n-divider:not(.n-divider--vertical) {
|
|
||||||
margin-top: 6px;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-word;
|
|
||||||
margin: 0;
|
|
||||||
font-size: 16px;
|
|
||||||
color: #333639;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
.snail-log {
|
|
||||||
background-color: #1e1f22;
|
|
||||||
|
|
||||||
pre {
|
|
||||||
color: #ffffffe6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-hljs {
|
|
||||||
&-time {
|
|
||||||
color: #2db7f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-level {
|
|
||||||
&-DEBUG {
|
|
||||||
color: #2647cc;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-INFO {
|
|
||||||
color: #5c962c;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-WARN {
|
|
||||||
color: #da9816;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-ERROR {
|
|
||||||
color: #dc3f41;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-thread {
|
|
||||||
color: #00a3a3;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-location {
|
|
||||||
color: #a771bf;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
import { defineStore } from 'pinia';
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { localStg } from '@/utils/storage';
|
|
||||||
|
|
||||||
export const useLogStore = defineStore('log', () => {
|
|
||||||
const taskName = ref<string>();
|
|
||||||
const taskBatchId = ref<string>();
|
|
||||||
const data = ref<Api.JobLog.JobMessage[]>([]);
|
|
||||||
|
|
||||||
function setTaskInfo(name: string, id: string) {
|
|
||||||
taskName.value = name;
|
|
||||||
taskBatchId.value = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setData(value: Api.JobLog.JobMessage[]) {
|
|
||||||
data.value = value;
|
|
||||||
localStg.set('log', { taskName: taskName.value!, taskBatchId: taskBatchId.value!, data: data.value });
|
|
||||||
}
|
|
||||||
|
|
||||||
function clear() {
|
|
||||||
taskName.value = undefined;
|
|
||||||
taskBatchId.value = undefined;
|
|
||||||
data.value = [];
|
|
||||||
localStg.remove('log');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
taskName,
|
|
||||||
taskBatchId,
|
|
||||||
data,
|
|
||||||
clear,
|
|
||||||
setTaskInfo,
|
|
||||||
setData
|
|
||||||
};
|
|
||||||
});
|
|
@ -1,38 +1,53 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onUnmounted, ref } from 'vue';
|
import { useRoute } from 'vue-router';
|
||||||
import { useLogStore } from '@/store/modules/log';
|
import { computed, ref } from 'vue';
|
||||||
import { useRouterPush } from '@/hooks/common/router';
|
import { useRouterPush } from '@/hooks/common/router';
|
||||||
import { localStg } from '@/utils/storage';
|
import { $t } from '@/locales';
|
||||||
|
|
||||||
const store = useLogStore();
|
const route = useRoute();
|
||||||
|
|
||||||
const data = ref(localStg.get('log'));
|
|
||||||
const { routerPushByKey } = useRouterPush();
|
const { routerPushByKey } = useRouterPush();
|
||||||
|
|
||||||
|
const type = ref<'job' | 'retry'>(route.query.type as 'job' | 'retry');
|
||||||
|
const taskData = ref();
|
||||||
|
const { taskBatchId, jobId, taskId, groupName, uniqueId } = route.query as { [key: string]: string };
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
if (!data.value) {
|
if (!['job', 'retry'].includes(type.value)) {
|
||||||
routerPushByKey('404');
|
routerPushByKey('404');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type.value === 'job') {
|
||||||
|
taskData.value = { taskBatchId, jobId, id: taskId };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.value === 'retry') {
|
||||||
|
taskData.value = { groupName, uniqueId };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|
||||||
onUnmounted(() => {
|
const title = computed(() => {
|
||||||
store.clear();
|
if (type.value === 'job') {
|
||||||
|
return `${$t('common.systemTaskType.job') + $t('page.log.title')} ------ JobId: ${jobId}, TaskId: ${taskId}, TaskBatchId: ${taskBatchId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.value === 'retry') {
|
||||||
|
return `${$t('common.systemTaskType.retry') + $t('page.log.title')} ------ ${$t('page.retryLog.groupName')}: ${groupName}, ${$t('page.retryLog.UniqueId')}: ${uniqueId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $t('page.log.title');
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NCard :bordered="false" size="small" class="h-full sm:flex-1-hidden card-wrapper" header-class="view-card-header">
|
<div>
|
||||||
<template #header>
|
<LogDrawer :drawer="false" :title="title" :type="type" :task-data="taskData" />
|
||||||
<span v-if="data?.taskName">
|
</div>
|
||||||
{{
|
|
||||||
`${$t('page.log.title')} ------ ${$t('page.jobBatch.jobName')}: ${data?.taskName}, ${$t('common.batchList')} ID: ${data?.taskBatchId}`
|
|
||||||
}}
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<LogDrawer :model-value="data?.data" :drawer="false" />
|
|
||||||
</NCard>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
:deep(.virtual-list) {
|
||||||
|
max-height: calc(100vh - 66px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { onBeforeUnmount, ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { executorTypeRecord, operationReasonRecord, taskBatchStatusRecord } from '@/constants/business';
|
import { executorTypeRecord, operationReasonRecord, taskBatchStatusRecord } from '@/constants/business';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
import { tagColor } from '@/utils/common';
|
import { tagColor } from '@/utils/common';
|
||||||
import { fetchJobLogList } from '@/service/api/log';
|
|
||||||
import { useLogStore } from '@/store/modules/log';
|
|
||||||
import JobTaskListTable from './job-task-list-table.vue';
|
import JobTaskListTable from './job-task-list-table.vue';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@ -17,7 +15,7 @@ interface Props {
|
|||||||
log?: boolean;
|
log?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
withDefaults(defineProps<Props>(), {
|
||||||
log: false,
|
log: false,
|
||||||
rowData: null
|
rowData: null
|
||||||
});
|
});
|
||||||
@ -28,56 +26,11 @@ const visible = defineModel<boolean>('visible', {
|
|||||||
|
|
||||||
const taskData = ref<Api.Job.JobTask>();
|
const taskData = ref<Api.Job.JobTask>();
|
||||||
const logShow = ref(false);
|
const logShow = ref(false);
|
||||||
const store = useLogStore();
|
|
||||||
|
|
||||||
async function openLog(row: Api.Job.JobTask) {
|
async function openLog(row: Api.Job.JobTask) {
|
||||||
store.setTaskInfo(props.rowData?.jobName || '', row.taskBatchId);
|
|
||||||
logShow.value = true;
|
logShow.value = true;
|
||||||
taskData.value = row;
|
taskData.value = row;
|
||||||
await getLogList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const logList = ref<Api.JobLog.JobMessage[]>([]);
|
|
||||||
const interval = ref<NodeJS.Timeout>();
|
|
||||||
const controller = new AbortController();
|
|
||||||
const finished = ref<boolean>(false);
|
|
||||||
let startId = '0';
|
|
||||||
let fromIndex: number = 0;
|
|
||||||
|
|
||||||
async function getLogList() {
|
|
||||||
const { data: logData, error } = await fetchJobLogList({
|
|
||||||
taskBatchId: taskData.value!.taskBatchId,
|
|
||||||
jobId: taskData.value!.jobId,
|
|
||||||
taskId: taskData.value!.id,
|
|
||||||
startId,
|
|
||||||
fromIndex,
|
|
||||||
size: 50
|
|
||||||
});
|
|
||||||
if (!error) {
|
|
||||||
finished.value = logData.finished;
|
|
||||||
startId = logData.nextStartId;
|
|
||||||
fromIndex = logData.fromIndex;
|
|
||||||
if (logData.message) {
|
|
||||||
logList.value.push(...logData.message);
|
|
||||||
logList.value.sort((a, b) => Number.parseInt(a.time_stamp, 10) - Number.parseInt(b.time_stamp, 10));
|
|
||||||
}
|
|
||||||
if (!finished.value) {
|
|
||||||
clearTimeout(interval.value);
|
|
||||||
interval.value = setTimeout(getLogList, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const stopLog = () => {
|
|
||||||
finished.value = true;
|
|
||||||
controller.abort();
|
|
||||||
clearTimeout(interval.value);
|
|
||||||
interval.value = undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
stopLog();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -114,7 +67,7 @@ onBeforeUnmount(() => {
|
|||||||
</NTabPane>
|
</NTabPane>
|
||||||
</NTabs>
|
</NTabs>
|
||||||
</DetailDrawer>
|
</DetailDrawer>
|
||||||
<LogDrawer v-model="logList" v-model:show="logShow" :title="$t('page.log.title')" />
|
<LogDrawer v-model:show="logShow" :title="$t('page.log.title')" :task-data="taskData" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onBeforeUnmount, ref } from 'vue';
|
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
import { tagColor } from '@/utils/common';
|
import { tagColor } from '@/utils/common';
|
||||||
import { retryTaskStatusTypeRecord, retryTaskTypeRecord } from '@/constants/business';
|
import { retryTaskStatusTypeRecord, retryTaskTypeRecord } from '@/constants/business';
|
||||||
import { fetchRetryLogList } from '@/service/api/log';
|
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'SceneDetailDrawer'
|
name: 'SceneDetailDrawer'
|
||||||
@ -14,63 +12,16 @@ interface Props {
|
|||||||
rowData?: Api.RetryLog.RetryLog | null;
|
rowData?: Api.RetryLog.RetryLog | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
defineProps<Props>();
|
||||||
|
|
||||||
const visible = defineModel<boolean>('visible', {
|
const visible = defineModel<boolean>('visible', {
|
||||||
default: false
|
default: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const logList = ref<Api.JobLog.JobMessage[]>([]);
|
|
||||||
const interval = ref<NodeJS.Timeout>();
|
|
||||||
const controller = new AbortController();
|
|
||||||
const finished = ref<boolean>(false);
|
|
||||||
let startId = '0';
|
|
||||||
let fromIndex: number = 0;
|
|
||||||
|
|
||||||
async function getLogList() {
|
|
||||||
const { data: logData, error } = await fetchRetryLogList({
|
|
||||||
groupName: props.rowData!.groupName,
|
|
||||||
uniqueId: props.rowData!.uniqueId,
|
|
||||||
startId,
|
|
||||||
fromIndex,
|
|
||||||
size: 50
|
|
||||||
});
|
|
||||||
if (!error) {
|
|
||||||
finished.value = logData.finished;
|
|
||||||
startId = logData.nextStartId;
|
|
||||||
fromIndex = logData.fromIndex;
|
|
||||||
if (logData.message) {
|
|
||||||
logList.value.push(...logData.message);
|
|
||||||
logList.value.sort((a, b) => Number.parseInt(a.time_stamp, 10) - Number.parseInt(b.time_stamp, 10));
|
|
||||||
}
|
|
||||||
if (!finished.value) {
|
|
||||||
clearTimeout(interval.value);
|
|
||||||
interval.value = setTimeout(getLogList, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleUpdateTab = async (value: number) => {
|
|
||||||
if (value === 1 && logList.value.length === 0) {
|
|
||||||
await getLogList();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const stopLog = () => {
|
|
||||||
finished.value = true;
|
|
||||||
controller.abort();
|
|
||||||
clearTimeout(interval.value);
|
|
||||||
interval.value = undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
stopLog();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DetailDrawer v-model="visible" :title="$t('page.retryLog.detail')" :width="['50%', '90%']">
|
<DetailDrawer v-model="visible" :title="$t('page.retryLog.detail')" :width="['50%', '90%']">
|
||||||
<NTabs type="segment" animated @update:value="handleUpdateTab">
|
<NTabs type="segment" animated>
|
||||||
<NTabPane :name="0" :tab="$t('page.log.info')">
|
<NTabPane :name="0" :tab="$t('page.log.info')">
|
||||||
<NDescriptions label-placement="top" bordered :column="2">
|
<NDescriptions label-placement="top" bordered :column="2">
|
||||||
<NDescriptionsItem :label="$t('page.retryLog.UniqueId')" :span="2">
|
<NDescriptionsItem :label="$t('page.retryLog.UniqueId')" :span="2">
|
||||||
@ -102,8 +53,7 @@ onBeforeUnmount(() => {
|
|||||||
</NDescriptions>
|
</NDescriptions>
|
||||||
</NTabPane>
|
</NTabPane>
|
||||||
<NTabPane :name="1" :tab="$t('page.log.title')" display-directive="if">
|
<NTabPane :name="1" :tab="$t('page.log.title')" display-directive="if">
|
||||||
<LogDrawer v-if="logList.length > 0" v-model="logList" :drawer="false" />
|
<LogDrawer :drawer="false" type="retry" :task-data="rowData!" />
|
||||||
<NEmpty v-else class="h-full" />
|
|
||||||
</NTabPane>
|
</NTabPane>
|
||||||
</NTabs>
|
</NTabs>
|
||||||
</DetailDrawer>
|
</DetailDrawer>
|
||||||
|
@ -109,8 +109,7 @@ onBeforeUnmount(() => {
|
|||||||
</NDescriptions>
|
</NDescriptions>
|
||||||
</NTabPane>
|
</NTabPane>
|
||||||
<NTabPane :name="1" :tab="$t('page.log.title')" display-directive="if">
|
<NTabPane :name="1" :tab="$t('page.log.title')" display-directive="if">
|
||||||
<LogDrawer v-if="logList.length > 0" v-model="logList" :drawer="false" />
|
<LogDrawer :drawer="false" type="retry" :task-data="rowData!" />
|
||||||
<NEmpty v-else class="h-full" />
|
|
||||||
</NTabPane>
|
</NTabPane>
|
||||||
</NTabs>
|
</NTabs>
|
||||||
</OperateDrawer>
|
</OperateDrawer>
|
||||||
|
Loading…
Reference in New Issue
Block a user