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">
|
||||
import { NCollapse, NCollapseItem } from 'naive-ui';
|
||||
import { defineComponent, watch } from 'vue';
|
||||
import { NButton, NCard, NCollapse, NCollapseItem, NDivider, NDropdown, NEmpty, NSpin, NVirtualList } from 'naive-ui';
|
||||
import { defineComponent, onBeforeUnmount, ref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { $t } from '@/locales';
|
||||
import { useLogStore } from '@/store/modules/log';
|
||||
import { fetchJobLogList, fetchRetryLogList } from '@/service/api/log';
|
||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'LogDrawer'
|
||||
@ -13,13 +13,17 @@ interface Props {
|
||||
title?: string;
|
||||
show?: boolean;
|
||||
drawer?: boolean;
|
||||
type?: 'job' | 'retry';
|
||||
taskData?: Api.Job.JobTask | Api.RetryLog.RetryLog | Api.RetryTask.RetryTask;
|
||||
modelValue?: Api.JobLog.JobMessage[];
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title: $t('page.log.title'),
|
||||
title: undefined,
|
||||
show: false,
|
||||
drawer: true,
|
||||
type: 'job',
|
||||
taskData: undefined,
|
||||
modelValue: () => []
|
||||
});
|
||||
|
||||
@ -32,48 +36,95 @@ const visible = defineModel<boolean>('visible', {
|
||||
default: true
|
||||
});
|
||||
|
||||
const ThrowableComponent = defineComponent({
|
||||
props: {
|
||||
throwable: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
setup(thProps) {
|
||||
return () => {
|
||||
if (!thProps.throwable) {
|
||||
return <></>;
|
||||
}
|
||||
const firstLine = thProps.throwable.match(/^.+/m);
|
||||
if (!firstLine) {
|
||||
return <></>;
|
||||
}
|
||||
const restOfText = thProps.throwable.replace(/^.+(\n|$)/m, '');
|
||||
return (
|
||||
<NCollapse>
|
||||
<NCollapseItem title={firstLine[0]} name="1">
|
||||
{`${restOfText}`}
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
);
|
||||
};
|
||||
const syncTime = ref(1);
|
||||
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;
|
||||
|
||||
const stopLog = () => {
|
||||
finished.value = true;
|
||||
controller.abort();
|
||||
clearTimeout(interval.value);
|
||||
interval.value = undefined;
|
||||
};
|
||||
|
||||
async function getLogList() {
|
||||
let logData = null;
|
||||
let logError;
|
||||
if (props.type === 'job') {
|
||||
const taskData = props.taskData! as Api.Job.JobTask;
|
||||
const { data, error } = await fetchJobLogList({
|
||||
taskBatchId: taskData.taskBatchId,
|
||||
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(
|
||||
() => props.show,
|
||||
val => {
|
||||
async 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 }
|
||||
);
|
||||
|
||||
const store = useLogStore();
|
||||
|
||||
const onUpdateShow = (value: boolean) => {
|
||||
if (!value) {
|
||||
store.clear();
|
||||
}
|
||||
emit('update:show', value);
|
||||
};
|
||||
|
||||
@ -92,10 +143,119 @@ function timestampToDate(timestamp: string): string {
|
||||
const router = useRouter();
|
||||
|
||||
function openNewTab() {
|
||||
const url = router.resolve('/log');
|
||||
store.setData(props.modelValue);
|
||||
let query;
|
||||
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);
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
@ -103,63 +263,132 @@ function openNewTab() {
|
||||
<NDrawerContent closable>
|
||||
<template #header>
|
||||
<div class="flex-center">
|
||||
<span>{{ title }}</span>
|
||||
<ButtonIcon icon="hugeicons:share-01" tooltip-content="在新标签页打开" class="ml-3px" @click="openNewTab" />
|
||||
<NTooltip v-if="finished">
|
||||
<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>
|
||||
</template>
|
||||
<div class="snail-log bg-#fafafc p-16px dark:bg-#000">
|
||||
<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>
|
||||
<SnailLogComponent />
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
<div v-if="!drawer" class="snail-log">
|
||||
<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>
|
||||
<NCard v-else :bordered="false" :title="title" size="small" class="h-full sm:flex-1-hidden card-wrapper">
|
||||
<template #header-extra>
|
||||
<div class="flex items-center">
|
||||
<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>
|
||||
<NTooltip v-if="finished">
|
||||
<template #trigger>
|
||||
<icon-material-symbols:check-circle class="text-20px color-success" />
|
||||
</template>
|
||||
</NVirtualList>
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
日志加载完成
|
||||
</NTooltip>
|
||||
<NTooltip v-else>
|
||||
<template #trigger>
|
||||
<NSpin size="small">
|
||||
<template #icon>
|
||||
<icon-nonicons:loading-16 />
|
||||
</template>
|
||||
</NSpin>
|
||||
</template>
|
||||
日志正在加载
|
||||
</NTooltip>
|
||||
</div>
|
||||
</template>
|
||||
<SnailLogComponent />
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
<style lang="scss">
|
||||
.snail-log {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
|
||||
.virtual-list {
|
||||
max-height: calc(100vh - 101px);
|
||||
}
|
||||
|
||||
&-scrollbar {
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
@include scrollbar();
|
||||
.n-divider:not(.n-divider--vertical) {
|
||||
margin-top: 6px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.log-hljs {
|
||||
&-time {
|
||||
color: #2db7f5;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
color: #333639;
|
||||
&-level {
|
||||
&-DEBUG {
|
||||
color: #2647cc;
|
||||
}
|
||||
|
||||
&-INFO {
|
||||
color: #5c962c;
|
||||
}
|
||||
|
||||
&-WARN {
|
||||
color: #da9816;
|
||||
}
|
||||
|
||||
&-ERROR {
|
||||
color: #dc3f41;
|
||||
}
|
||||
}
|
||||
|
||||
&-thread {
|
||||
color: #00a3a3;
|
||||
}
|
||||
|
||||
&-location {
|
||||
color: #a771bf;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -173,36 +402,13 @@ function openNewTab() {
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
.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 scoped>
|
||||
:deep(.n-spin) {
|
||||
height: 18px !important;
|
||||
width: 18px !important;
|
||||
font-size: 18px !important;
|
||||
margin-right: 6px;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,7 +1,5 @@
|
||||
<script setup lang="tsx">
|
||||
import { onBeforeUnmount, ref, watch } from 'vue';
|
||||
import { $t } from '@/locales';
|
||||
import { fetchJobLogList } from '@/service/api/log';
|
||||
|
||||
defineOptions({
|
||||
name: 'FlowLogDrawer'
|
||||
@ -12,138 +10,17 @@ interface Props {
|
||||
taskData: Workflow.JobTaskType;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title: $t('workflow.node.log.title'),
|
||||
modelValue: () => []
|
||||
withDefaults(defineProps<Props>(), {
|
||||
title: $t('workflow.node.log.title')
|
||||
});
|
||||
|
||||
const show = defineModel<boolean>('show', {
|
||||
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>
|
||||
|
||||
<template>
|
||||
<LogDrawer v-model="logList" v-model:show="show" :title="title" />
|
||||
<LogDrawer v-model:show="show" :title="title" :task-data="taskData as any" />
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.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>
|
||||
<style scoped></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">
|
||||
import { onUnmounted, ref } from 'vue';
|
||||
import { useLogStore } from '@/store/modules/log';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
import { localStg } from '@/utils/storage';
|
||||
import { $t } from '@/locales';
|
||||
|
||||
const store = useLogStore();
|
||||
|
||||
const data = ref(localStg.get('log'));
|
||||
const route = useRoute();
|
||||
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() {
|
||||
if (!data.value) {
|
||||
if (!['job', 'retry'].includes(type.value)) {
|
||||
routerPushByKey('404');
|
||||
}
|
||||
|
||||
if (type.value === 'job') {
|
||||
taskData.value = { taskBatchId, jobId, id: taskId };
|
||||
}
|
||||
|
||||
if (type.value === 'retry') {
|
||||
taskData.value = { groupName, uniqueId };
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
onUnmounted(() => {
|
||||
store.clear();
|
||||
const title = computed(() => {
|
||||
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>
|
||||
|
||||
<template>
|
||||
<NCard :bordered="false" size="small" class="h-full sm:flex-1-hidden card-wrapper" header-class="view-card-header">
|
||||
<template #header>
|
||||
<span v-if="data?.taskName">
|
||||
{{
|
||||
`${$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>
|
||||
<div>
|
||||
<LogDrawer :drawer="false" :title="title" :type="type" :task-data="taskData" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped>
|
||||
:deep(.virtual-list) {
|
||||
max-height: calc(100vh - 66px);
|
||||
}
|
||||
</style>
|
||||
|
@ -1,10 +1,8 @@
|
||||
<script setup lang="tsx">
|
||||
import { onBeforeUnmount, ref } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import { executorTypeRecord, operationReasonRecord, taskBatchStatusRecord } from '@/constants/business';
|
||||
import { $t } from '@/locales';
|
||||
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';
|
||||
|
||||
defineOptions({
|
||||
@ -17,7 +15,7 @@ interface Props {
|
||||
log?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
withDefaults(defineProps<Props>(), {
|
||||
log: false,
|
||||
rowData: null
|
||||
});
|
||||
@ -28,56 +26,11 @@ const visible = defineModel<boolean>('visible', {
|
||||
|
||||
const taskData = ref<Api.Job.JobTask>();
|
||||
const logShow = ref(false);
|
||||
const store = useLogStore();
|
||||
|
||||
async function openLog(row: Api.Job.JobTask) {
|
||||
store.setTaskInfo(props.rowData?.jobName || '', row.taskBatchId);
|
||||
logShow.value = true;
|
||||
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>
|
||||
|
||||
<template>
|
||||
@ -114,7 +67,7 @@ onBeforeUnmount(() => {
|
||||
</NTabPane>
|
||||
</NTabs>
|
||||
</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>
|
||||
|
||||
<style scoped></style>
|
||||
|
@ -1,9 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, ref } from 'vue';
|
||||
import { $t } from '@/locales';
|
||||
import { tagColor } from '@/utils/common';
|
||||
import { retryTaskStatusTypeRecord, retryTaskTypeRecord } from '@/constants/business';
|
||||
import { fetchRetryLogList } from '@/service/api/log';
|
||||
|
||||
defineOptions({
|
||||
name: 'SceneDetailDrawer'
|
||||
@ -14,63 +12,16 @@ interface Props {
|
||||
rowData?: Api.RetryLog.RetryLog | null;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
defineProps<Props>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
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>
|
||||
|
||||
<template>
|
||||
<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')">
|
||||
<NDescriptions label-placement="top" bordered :column="2">
|
||||
<NDescriptionsItem :label="$t('page.retryLog.UniqueId')" :span="2">
|
||||
@ -102,8 +53,7 @@ onBeforeUnmount(() => {
|
||||
</NDescriptions>
|
||||
</NTabPane>
|
||||
<NTabPane :name="1" :tab="$t('page.log.title')" display-directive="if">
|
||||
<LogDrawer v-if="logList.length > 0" v-model="logList" :drawer="false" />
|
||||
<NEmpty v-else class="h-full" />
|
||||
<LogDrawer :drawer="false" type="retry" :task-data="rowData!" />
|
||||
</NTabPane>
|
||||
</NTabs>
|
||||
</DetailDrawer>
|
||||
|
@ -109,8 +109,7 @@ onBeforeUnmount(() => {
|
||||
</NDescriptions>
|
||||
</NTabPane>
|
||||
<NTabPane :name="1" :tab="$t('page.log.title')" display-directive="if">
|
||||
<LogDrawer v-if="logList.length > 0" v-model="logList" :drawer="false" />
|
||||
<NEmpty v-else class="h-full" />
|
||||
<LogDrawer :drawer="false" type="retry" :task-data="rowData!" />
|
||||
</NTabPane>
|
||||
</NTabs>
|
||||
</OperateDrawer>
|
||||
|
Loading…
Reference in New Issue
Block a user