2024-04-26 16:43:25 +08:00
|
|
|
<script setup lang="tsx">
|
2024-06-26 15:27:43 +08:00
|
|
|
import { NButton, NCard, NCollapse, NCollapseItem, NDivider, NDropdown, NEmpty, NSpin, NVirtualList } from 'naive-ui';
|
|
|
|
import { defineComponent, onBeforeUnmount, ref, watch } from 'vue';
|
2024-06-24 15:55:40 +08:00
|
|
|
import { useRouter } from 'vue-router';
|
2024-06-26 15:27:43 +08:00
|
|
|
import { fetchJobLogList, fetchRetryLogList } from '@/service/api/log';
|
|
|
|
import ButtonIcon from '@/components/custom/button-icon.vue';
|
2024-04-26 16:43:25 +08:00
|
|
|
|
|
|
|
defineOptions({
|
|
|
|
name: 'LogDrawer'
|
|
|
|
});
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
title?: string;
|
|
|
|
show?: boolean;
|
2024-05-07 15:19:49 +08:00
|
|
|
drawer?: boolean;
|
2024-06-26 15:27:43 +08:00
|
|
|
type?: 'job' | 'retry';
|
|
|
|
taskData?: Api.Job.JobTask | Api.RetryLog.RetryLog | Api.RetryTask.RetryTask;
|
2024-05-07 14:51:19 +08:00
|
|
|
modelValue?: Api.JobLog.JobMessage[];
|
2024-04-26 16:43:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
2024-06-26 15:27:43 +08:00
|
|
|
title: undefined,
|
2024-05-07 14:51:19 +08:00
|
|
|
show: false,
|
2024-05-07 15:19:49 +08:00
|
|
|
drawer: true,
|
2024-06-26 15:27:43 +08:00
|
|
|
type: 'job',
|
|
|
|
taskData: undefined,
|
2024-05-07 14:51:19 +08:00
|
|
|
modelValue: () => []
|
2024-04-26 16:43:25 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
interface Emits {
|
|
|
|
(e: 'update:show', show: boolean): void;
|
|
|
|
}
|
|
|
|
|
|
|
|
const emit = defineEmits<Emits>();
|
|
|
|
const visible = defineModel<boolean>('visible', {
|
|
|
|
default: true
|
|
|
|
});
|
|
|
|
|
2024-06-26 15:27:43 +08:00
|
|
|
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));
|
2024-06-18 17:05:56 +08:00
|
|
|
}
|
2024-06-26 15:27:43 +08:00
|
|
|
if (!finished.value) {
|
|
|
|
clearTimeout(interval.value);
|
|
|
|
interval.value = setTimeout(getLogList, syncTime.value * 1000);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
stopLog();
|
2024-04-26 16:43:25 +08:00
|
|
|
}
|
2024-06-26 15:27:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
stopLog();
|
2024-04-26 16:43:25 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
watch(
|
|
|
|
() => props.show,
|
2024-06-26 15:27:43 +08:00
|
|
|
async val => {
|
2024-04-26 16:43:25 +08:00
|
|
|
visible.value = val;
|
2024-06-26 15:27:43 +08:00
|
|
|
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();
|
|
|
|
}
|
2024-04-26 16:43:25 +08:00
|
|
|
},
|
|
|
|
{ immediate: true }
|
|
|
|
);
|
|
|
|
|
|
|
|
const onUpdateShow = (value: boolean) => {
|
|
|
|
emit('update:show', value);
|
|
|
|
};
|
|
|
|
|
|
|
|
function timestampToDate(timestamp: string): string {
|
2024-06-15 10:39:12 +08:00
|
|
|
const date = new Date(Number.parseInt(timestamp?.toString(), 10));
|
2024-04-26 16:43:25 +08:00
|
|
|
const year = date.getFullYear();
|
|
|
|
const month =
|
2024-06-15 10:39:12 +08:00
|
|
|
(date.getMonth() + 1)?.toString().length === 1 ? `0${date.getMonth() + 1}` : (date.getMonth() + 1)?.toString();
|
|
|
|
const day = date.getDate()?.toString().length === 1 ? `0${date.getDate()}` : date.getDate()?.toString();
|
|
|
|
const hours = date.getHours()?.toString().length === 1 ? `0${date.getHours()}` : date.getHours()?.toString();
|
|
|
|
const minutes = date.getMinutes()?.toString().length === 1 ? `0${date.getMinutes()}` : date.getMinutes()?.toString();
|
|
|
|
const seconds = date.getSeconds()?.toString().length === 1 ? `0${date.getSeconds()}` : date.getSeconds()?.toString();
|
2024-04-26 16:43:25 +08:00
|
|
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${date.getMilliseconds()}`;
|
|
|
|
}
|
2024-06-24 15:55:40 +08:00
|
|
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
|
|
|
function openNewTab() {
|
2024-06-26 15:27:43 +08:00
|
|
|
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 });
|
2024-06-24 15:55:40 +08:00
|
|
|
window.open(url.href);
|
|
|
|
}
|
2024-06-26 15:27:43 +08:00
|
|
|
|
|
|
|
const handleSyncSelect = async (time: number) => {
|
|
|
|
if (time === -1) {
|
2024-06-26 15:51:09 +08:00
|
|
|
if (finished.value) {
|
|
|
|
finished.value = false;
|
|
|
|
await getLogList();
|
|
|
|
}
|
|
|
|
return;
|
2024-06-26 15:27:43 +08:00
|
|
|
}
|
2024-06-26 15:51:09 +08:00
|
|
|
|
2024-06-26 15:27:43 +08:00
|
|
|
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>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
2024-04-26 16:43:25 +08:00
|
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
2024-06-25 17:53:44 +08:00
|
|
|
<NDrawer v-if="drawer" v-model:show="visible" width="100%" display-directive="if" @update:show="onUpdateShow">
|
2024-06-24 15:55:40 +08:00
|
|
|
<NDrawerContent closable>
|
|
|
|
<template #header>
|
|
|
|
<div class="flex-center">
|
2024-06-26 15:27:43 +08:00
|
|
|
<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">
|
2024-06-26 15:51:09 +08:00
|
|
|
<NTooltip placement="right">
|
2024-06-26 15:27:43 +08:00
|
|
|
<template #trigger>
|
2024-06-26 15:51:09 +08:00
|
|
|
<NButton quaternary class="ml-3px w-136px" @click="handleSyncSelect(-1)">
|
2024-06-26 15:27:43 +08:00
|
|
|
<template #icon>
|
|
|
|
<div class="flex-center gap-8px">
|
|
|
|
<icon-solar:refresh-outline class="text-18px" />
|
2024-06-26 15:51:09 +08:00
|
|
|
{{ syncOptions.filter(item => item.key === syncTime)[0].label }}
|
|
|
|
<SvgIcon icon="material-symbols:expand-more-rounded" />
|
2024-06-26 15:27:43 +08:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
</NButton>
|
2024-06-25 17:53:44 +08:00
|
|
|
</template>
|
2024-06-26 15:27:43 +08:00
|
|
|
自动刷新频率
|
|
|
|
</NTooltip>
|
|
|
|
</NDropdown>
|
2024-04-26 16:43:25 +08:00
|
|
|
</div>
|
2024-06-26 15:27:43 +08:00
|
|
|
</template>
|
|
|
|
<SnailLogComponent />
|
2024-04-26 16:43:25 +08:00
|
|
|
</NDrawerContent>
|
|
|
|
</NDrawer>
|
2024-06-26 15:27:43 +08:00
|
|
|
<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">
|
2024-06-26 15:51:09 +08:00
|
|
|
<NTooltip placement="right">
|
2024-06-26 15:27:43 +08:00
|
|
|
<template #trigger>
|
2024-06-26 15:51:09 +08:00
|
|
|
<NButton quaternary class="ml-3px w-136px" @click="handleSyncSelect(-1)">
|
2024-06-26 15:27:43 +08:00
|
|
|
<template #icon>
|
|
|
|
<div class="flex-center gap-8px">
|
|
|
|
<icon-solar:refresh-outline class="text-18px" />
|
2024-06-26 15:51:09 +08:00
|
|
|
{{ syncOptions.filter(item => item.key === syncTime)[0].label }}
|
|
|
|
<SvgIcon icon="material-symbols:expand-more-rounded" />
|
2024-06-26 15:27:43 +08:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
</NButton>
|
|
|
|
</template>
|
|
|
|
自动刷新频率
|
|
|
|
</NTooltip>
|
|
|
|
</NDropdown>
|
|
|
|
<NTooltip v-if="finished">
|
|
|
|
<template #trigger>
|
|
|
|
<icon-material-symbols:check-circle class="text-20px color-success" />
|
2024-06-25 17:53:44 +08:00
|
|
|
</template>
|
2024-06-26 15:27:43 +08:00
|
|
|
日志加载完成
|
|
|
|
</NTooltip>
|
|
|
|
<NTooltip v-else>
|
|
|
|
<template #trigger>
|
|
|
|
<NSpin size="small">
|
|
|
|
<template #icon>
|
|
|
|
<icon-nonicons:loading-16 />
|
|
|
|
</template>
|
|
|
|
</NSpin>
|
|
|
|
</template>
|
|
|
|
日志正在加载
|
|
|
|
</NTooltip>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
<SnailLogComponent />
|
|
|
|
</NCard>
|
2024-04-26 16:43:25 +08:00
|
|
|
</template>
|
|
|
|
|
2024-06-26 15:27:43 +08:00
|
|
|
<style lang="scss">
|
2024-04-26 16:43:25 +08:00
|
|
|
.snail-log {
|
2024-06-26 15:27:43 +08:00
|
|
|
padding: 0;
|
2024-04-26 16:43:25 +08:00
|
|
|
|
2024-06-25 17:53:44 +08:00
|
|
|
.virtual-list {
|
|
|
|
max-height: calc(100vh - 101px);
|
|
|
|
}
|
|
|
|
|
2024-06-26 15:27:43 +08:00
|
|
|
.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;
|
|
|
|
}
|
2024-04-26 16:43:25 +08:00
|
|
|
|
2024-06-26 15:27:43 +08:00
|
|
|
.log-hljs {
|
|
|
|
&-time {
|
|
|
|
color: #2db7f5;
|
2024-04-26 16:43:25 +08:00
|
|
|
}
|
|
|
|
|
2024-06-26 15:27:43 +08:00
|
|
|
&-level {
|
|
|
|
&-DEBUG {
|
|
|
|
color: #2647cc;
|
|
|
|
}
|
|
|
|
|
|
|
|
&-INFO {
|
|
|
|
color: #5c962c;
|
|
|
|
}
|
|
|
|
|
|
|
|
&-WARN {
|
|
|
|
color: #da9816;
|
|
|
|
}
|
|
|
|
|
|
|
|
&-ERROR {
|
|
|
|
color: #dc3f41;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
&-thread {
|
|
|
|
color: #00a3a3;
|
|
|
|
}
|
|
|
|
|
|
|
|
&-location {
|
|
|
|
color: #a771bf;
|
2024-04-26 16:43:25 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.dark {
|
|
|
|
.snail-log {
|
|
|
|
background-color: #1e1f22;
|
|
|
|
|
|
|
|
pre {
|
|
|
|
color: #ffffffe6;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-06-26 15:27:43 +08:00
|
|
|
</style>
|
2024-04-26 16:43:25 +08:00
|
|
|
|
2024-06-26 15:27:43 +08:00
|
|
|
<style scoped>
|
|
|
|
:deep(.n-spin) {
|
|
|
|
height: 18px !important;
|
|
|
|
width: 18px !important;
|
|
|
|
font-size: 18px !important;
|
|
|
|
margin-right: 6px;
|
2024-04-26 16:43:25 +08:00
|
|
|
}
|
|
|
|
</style>
|