feat(1.5.0)-beta1): 完成日志接入ws
This commit is contained in:
parent
2408463d7c
commit
d7fd6c040c
@ -1,447 +0,0 @@
|
||||
<script setup lang="tsx">
|
||||
import {
|
||||
NCard,
|
||||
NCollapse,
|
||||
NCollapseItem,
|
||||
NDivider,
|
||||
NEmpty,
|
||||
NScrollbar,
|
||||
NSpin,
|
||||
NVirtualList,
|
||||
type VirtualListInst
|
||||
} from 'naive-ui';
|
||||
import { defineComponent, nextTick, onBeforeUnmount, ref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||
import { initWebSocket } from '@/utils/websocket';
|
||||
const terminalSocket = ref<WebSocket | undefined>();
|
||||
|
||||
defineOptions({
|
||||
name: 'LogWsDrawer'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
drawer?: boolean;
|
||||
type?: 'job' | 'retry';
|
||||
taskData?: Api.Job.JobTask | Api.RetryTask.RetryTask;
|
||||
modelValue?: Api.JobLog.JobMessage[];
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title: undefined,
|
||||
drawer: true,
|
||||
type: 'job',
|
||||
taskData: undefined,
|
||||
modelValue: () => []
|
||||
});
|
||||
|
||||
const visible = defineModel<boolean>('show', {
|
||||
default: false
|
||||
});
|
||||
|
||||
const isFullscreen = ref(true);
|
||||
const expandedNames = ref<string[]>([]);
|
||||
const virtualListInst = ref<VirtualListInst>();
|
||||
const logList = ref<Api.JobLog.JobMessage[]>([]);
|
||||
const interval = ref<NodeJS.Timeout>();
|
||||
const finished = ref<boolean>(true);
|
||||
|
||||
const pauseLog = () => {
|
||||
finished.value = true;
|
||||
clearTimeout(interval.value);
|
||||
interval.value = undefined;
|
||||
};
|
||||
|
||||
const stopLog = () => {
|
||||
pauseLog();
|
||||
logList.value = [];
|
||||
};
|
||||
|
||||
async function getLogList() {
|
||||
if (terminalSocket.value) {
|
||||
terminalSocket.value.close();
|
||||
}
|
||||
|
||||
if (props.type === 'job') {
|
||||
const newSocket = initWebSocket('JOB_LOG_SCENE');
|
||||
terminalSocket.value = newSocket ?? undefined;
|
||||
terminalSocket!.value!.onopen = () => {
|
||||
const taskData = props.taskData! as Api.Job.JobTask;
|
||||
const msg = {
|
||||
taskBatchId: taskData.taskBatchId,
|
||||
taskId: taskData.id
|
||||
};
|
||||
terminalSocket!.value?.send(JSON.stringify(msg));
|
||||
};
|
||||
}
|
||||
|
||||
terminalSocket!.value!.onmessage = (event: any) => {
|
||||
if (event.data === 'END') {
|
||||
finished.value = true;
|
||||
} else {
|
||||
logList.value.push(JSON.parse(event.data));
|
||||
virtualListInst.value?.scrollTo({ position: 'bottom', debounce: false });
|
||||
nextTick(() => {
|
||||
virtualListInst.value?.scrollTo({ position: 'bottom', debounce: false });
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopLog();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => visible.value,
|
||||
async val => {
|
||||
if (val) {
|
||||
if (props.modelValue) {
|
||||
logList.value = [...props.modelValue];
|
||||
}
|
||||
} else {
|
||||
terminalSocket?.value?.close();
|
||||
}
|
||||
|
||||
if ((val || !props.drawer) && props.type && props.taskData) {
|
||||
finished.value = false;
|
||||
await getLogList();
|
||||
}
|
||||
|
||||
if (!val && props.drawer) {
|
||||
stopLog();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function timestampToDate(timestamp: string): string {
|
||||
const date = new Date(Number.parseInt(timestamp?.toString(), 10));
|
||||
const year = date.getFullYear();
|
||||
const month =
|
||||
(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();
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${date.getMilliseconds()}`;
|
||||
}
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
function openNewTab() {
|
||||
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.RetryTask.RetryTask | Api.Retry.Retry).groupName,
|
||||
retryTaskId: (props.taskData as Api.RetryTask.RetryTask).id
|
||||
};
|
||||
}
|
||||
const url = router.resolve({ path: '/log', query });
|
||||
window.open(url.href);
|
||||
}
|
||||
|
||||
const SnailLogComponent = defineComponent({
|
||||
setup() {
|
||||
if (finished.value && logList.value.length === 0) {
|
||||
return () => <NEmpty class="h-full flex-center" size="huge" />;
|
||||
}
|
||||
|
||||
const throwableComponent = (message: Api.JobLog.JobMessage) => {
|
||||
const throwable = message.throwable;
|
||||
if (!throwable) {
|
||||
return <></>;
|
||||
}
|
||||
const firstLine = throwable.match(/^.+/m);
|
||||
if (!firstLine) {
|
||||
return <></>;
|
||||
}
|
||||
const restOfText = throwable.replace(/^.+(\n|$)/m, '');
|
||||
return (
|
||||
<NCollapseItem title={firstLine[0]} name={`throwable-${message.index}`}>
|
||||
<NScrollbar content-class="p-8px" class="message-scroll-body">{`${restOfText}`}</NScrollbar>
|
||||
</NCollapseItem>
|
||||
);
|
||||
};
|
||||
|
||||
const messageComponent = (message: Api.JobLog.JobMessage) => {
|
||||
const msg = message.message;
|
||||
if (!msg) {
|
||||
return <></>;
|
||||
}
|
||||
const firstLine = msg.match(/^.+/m);
|
||||
if (!firstLine) {
|
||||
return <></>;
|
||||
}
|
||||
const restOfText = msg.replace(/^.+(\n|$)/m, '').replaceAll('\n', '\n - ');
|
||||
if (restOfText) {
|
||||
return (
|
||||
<NCollapseItem title={firstLine[0]} name={`message-${message.index}`}>
|
||||
<NScrollbar content-class="p-8px" class="message-scroll-body">{` - ${restOfText}`}</NScrollbar>
|
||||
</NCollapseItem>
|
||||
);
|
||||
}
|
||||
return <div class="pl-6px">- {`${msg}`}</div>;
|
||||
};
|
||||
|
||||
const handleUpdateExpanded = (val: string[]) => {
|
||||
expandedNames.value = val;
|
||||
};
|
||||
|
||||
const handleResize = (_: ResizeObserverEntry) => {
|
||||
expandedNames.value = [];
|
||||
};
|
||||
|
||||
return () => (
|
||||
<code class="snail-log">
|
||||
<NCollapse
|
||||
accordion
|
||||
v-model:expanded-names={expandedNames.value}
|
||||
on-update:expanded-names={handleUpdateExpanded}
|
||||
>
|
||||
<NVirtualList
|
||||
ref={virtualListInst}
|
||||
class="virtual-list"
|
||||
itemSize={85}
|
||||
paddingBottom={16}
|
||||
items={logList.value}
|
||||
scrollbarProps={{ xScrollable: true }}
|
||||
onResize={handleResize}
|
||||
>
|
||||
{{
|
||||
default: ({ item: message }: { item: Api.JobLog.JobMessage }) => (
|
||||
<pre key={message.index} class="min-h-85px min-w-full">
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
<div class="log-hljs-location">{`${message.location}: `}</div>
|
||||
<div>{messageComponent(message)}</div>
|
||||
<div>{throwableComponent(message)}</div>
|
||||
<NDivider />
|
||||
</pre>
|
||||
)
|
||||
}}
|
||||
</NVirtualList>
|
||||
</NCollapse>
|
||||
</code>
|
||||
);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer
|
||||
v-if="drawer"
|
||||
v-model:show="visible"
|
||||
:width="isFullscreen ? '100%' : '50%'"
|
||||
display-directive="if"
|
||||
:auto-focus="false"
|
||||
>
|
||||
<NDrawerContent closable>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between" :class="`tool-header${isFullscreen ? '-full' : ''}`">
|
||||
<div class="flex-center">
|
||||
<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>
|
||||
</div>
|
||||
<div class="flex-center">
|
||||
<ButtonIcon
|
||||
size="tiny"
|
||||
icon="hugeicons:share-01"
|
||||
tooltip-content="在新标签页打开"
|
||||
class="ml-6px"
|
||||
@click="openNewTab"
|
||||
/>
|
||||
<ButtonIcon
|
||||
size="tiny"
|
||||
:tooltip-content="isFullscreen ? '半屏' : '全屏'"
|
||||
@click="() => (isFullscreen = !isFullscreen)"
|
||||
>
|
||||
<icon-material-symbols:close-fullscreen-rounded v-if="isFullscreen" />
|
||||
<icon-material-symbols:open-in-full-rounded v-else />
|
||||
</ButtonIcon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="logList.length === 0" class="empty-height flex-center">
|
||||
<NEmpty v-if="logList.length === 0 && finished" />
|
||||
<NSpin v-if="logList.length === 0 && !finished" />
|
||||
</div>
|
||||
<SnailLogComponent v-if="logList.length > 0" />
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
<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">
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="logList.length === 0" class="h-full flex-center">
|
||||
<NEmpty v-if="logList.length === 0 && finished" />
|
||||
<NSpin v-if="logList.length === 0 && !finished" />
|
||||
</div>
|
||||
<SnailLogComponent />
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.snail-log {
|
||||
padding: 0;
|
||||
|
||||
.virtual-list {
|
||||
min-height: calc(100vh - 101px);
|
||||
max-height: calc(100vh - 101px);
|
||||
}
|
||||
|
||||
.v-vl {
|
||||
min-height: calc(100vh - 101px);
|
||||
}
|
||||
|
||||
.v-vl-items {
|
||||
min-height: calc(100vh - 101px - 16px) !important;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
&-level {
|
||||
&-DEBUG {
|
||||
color: #2647cc;
|
||||
}
|
||||
|
||||
&-INFO {
|
||||
color: #5c962c;
|
||||
}
|
||||
|
||||
&-WARN {
|
||||
color: #da9816;
|
||||
}
|
||||
|
||||
&-ERROR {
|
||||
color: #dc3f41;
|
||||
}
|
||||
}
|
||||
|
||||
&-thread {
|
||||
color: #00a3a3;
|
||||
}
|
||||
|
||||
&-location {
|
||||
color: #a771bf;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
.snail-log {
|
||||
background-color: #1e1f22;
|
||||
|
||||
pre {
|
||||
color: #ffffffe6;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
:deep(.n-spin) {
|
||||
height: 18px !important;
|
||||
width: 18px !important;
|
||||
font-size: 18px !important;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.tool-header-full {
|
||||
width: calc(100vw - 72px);
|
||||
}
|
||||
|
||||
.tool-header {
|
||||
width: calc(50vw - 72px);
|
||||
}
|
||||
|
||||
.empty-height {
|
||||
min-height: calc(100vh - 101px);
|
||||
max-height: calc(100vh - 101px);
|
||||
}
|
||||
|
||||
:deep(.n-collapse-item__content-inner) {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.v-vl-items) {
|
||||
display: inline-block !important;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
:deep(.message-scroll-body) {
|
||||
margin-top: 6px;
|
||||
max-height: 150px;
|
||||
border: 1px solid rgb(239, 239, 245);
|
||||
border-radius: var(--n-border-radius);
|
||||
}
|
||||
|
||||
:deep(.dark .message-scroll-body) {
|
||||
border: 1px solid rgba(255, 255, 255, 0.09) !important;
|
||||
}
|
||||
</style>
|
@ -14,8 +14,12 @@ import {
|
||||
} from 'naive-ui';
|
||||
import { defineComponent, nextTick, onBeforeUnmount, ref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import type { UseWebSocketReturn } from '@vueuse/core';
|
||||
import { useWebSocket } from '@vueuse/core';
|
||||
import { fetchJobLogList, fetchRetryLogList } from '@/service/api/log';
|
||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||
import { initWebSocketUrl } from '@/utils/websocket';
|
||||
import { generateRandomString } from '@/utils/common';
|
||||
|
||||
defineOptions({
|
||||
name: 'LogDrawer'
|
||||
@ -25,6 +29,7 @@ interface Props {
|
||||
title?: string;
|
||||
drawer?: boolean;
|
||||
type?: 'job' | 'retry';
|
||||
fetchType?: 'ws' | 'http';
|
||||
taskData?: Api.Job.JobTask | Api.RetryTask.RetryTask;
|
||||
modelValue?: Api.JobLog.JobMessage[];
|
||||
}
|
||||
@ -33,6 +38,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
title: undefined,
|
||||
drawer: true,
|
||||
type: 'job',
|
||||
fetchType: 'ws',
|
||||
taskData: undefined,
|
||||
modelValue: () => []
|
||||
});
|
||||
@ -47,6 +53,7 @@ const expandedNames = ref<string[]>([]);
|
||||
const virtualListInst = ref<VirtualListInst>();
|
||||
const syncTime = ref(1);
|
||||
const logList = ref<Api.JobLog.JobMessage[]>([]);
|
||||
const websocket = ref<UseWebSocketReturn<any>>();
|
||||
const interval = ref<NodeJS.Timeout>();
|
||||
let controller = new AbortController();
|
||||
const finished = ref<boolean>(true);
|
||||
@ -59,7 +66,11 @@ const pauseLog = () => {
|
||||
interval.value = undefined;
|
||||
};
|
||||
|
||||
const stopLog = () => {
|
||||
const stopLogByWs = () => {
|
||||
websocket.value?.close();
|
||||
};
|
||||
|
||||
const stopLogByHttp = async () => {
|
||||
if (!finished.value) controller.abort();
|
||||
pauseLog();
|
||||
startId = '0';
|
||||
@ -67,7 +78,46 @@ const stopLog = () => {
|
||||
logList.value = [];
|
||||
};
|
||||
|
||||
const stopLog = () => {
|
||||
if (props.fetchType === 'http') {
|
||||
stopLogByHttp();
|
||||
return;
|
||||
}
|
||||
stopLogByWs();
|
||||
};
|
||||
|
||||
async function getLogList() {
|
||||
if (props.fetchType === 'http') {
|
||||
await getLogListByHttp();
|
||||
return;
|
||||
}
|
||||
getLogListByWs();
|
||||
}
|
||||
|
||||
function getLogListByWs() {
|
||||
finished.value = false;
|
||||
logList.value = [];
|
||||
websocket.value?.open();
|
||||
if (props.type === 'job') {
|
||||
const taskData = props.taskData! as Api.Job.JobTask;
|
||||
const msg = {
|
||||
taskBatchId: taskData.taskBatchId,
|
||||
taskId: taskData.id
|
||||
};
|
||||
websocket.value?.send(JSON.stringify(msg));
|
||||
}
|
||||
|
||||
if (props.type === 'retry') {
|
||||
const taskData = props.taskData! as Api.RetryTask.RetryTask;
|
||||
const msg = {
|
||||
groupName: taskData.groupName,
|
||||
retryTaskId: taskData.id
|
||||
};
|
||||
websocket.value?.send(JSON.stringify(msg));
|
||||
}
|
||||
}
|
||||
|
||||
async function getLogListByHttp() {
|
||||
clearTimeout(interval.value);
|
||||
let logData = null;
|
||||
let logError;
|
||||
@ -110,7 +160,12 @@ async function getLogList() {
|
||||
logList.value.push(...logData.message);
|
||||
logList.value
|
||||
.sort((a, b) => Number.parseInt(a.time_stamp, 10) - Number.parseInt(b.time_stamp, 10))
|
||||
.forEach((item, index) => (item.index = index));
|
||||
.forEach((item, index) => {
|
||||
item.index = index;
|
||||
if (!item.key) {
|
||||
item.key = `${item.time_stamp}-${generateRandomString(16)}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
nextTick(() => {
|
||||
if (isAutoScroll.value) virtualListInst.value?.scrollTo({ position: 'bottom', debounce: true });
|
||||
@ -162,22 +217,58 @@ watch(
|
||||
() => visible.value,
|
||||
async val => {
|
||||
if (val) {
|
||||
logList.value = [];
|
||||
if (props.modelValue) {
|
||||
logList.value = [...props.modelValue];
|
||||
}
|
||||
}
|
||||
|
||||
if ((val || !props.drawer) && props.type && props.taskData) {
|
||||
finished.value = false;
|
||||
controller = new AbortController();
|
||||
await getLogList();
|
||||
}
|
||||
|
||||
if (!val && props.drawer) {
|
||||
stopLog();
|
||||
return;
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
|
||||
if (((val && props.drawer) || !props.drawer) && props.type && props.taskData) {
|
||||
finished.value = false;
|
||||
controller = new AbortController();
|
||||
if (props.fetchType === 'ws') {
|
||||
const url = initWebSocketUrl('JOB_LOG_SCENE', props.taskData.id);
|
||||
if (!url) {
|
||||
window.$message?.error('Token 失效');
|
||||
visible.value = false;
|
||||
return;
|
||||
}
|
||||
websocket.value = useWebSocket(url, {
|
||||
immediate: false,
|
||||
autoConnect: false,
|
||||
autoReconnect: {
|
||||
// 重连最大次数
|
||||
retries: 3,
|
||||
// 重连间隔
|
||||
delay: 1000,
|
||||
onFailed() {
|
||||
window.$message?.error('websocket 连接失败');
|
||||
visible.value = false;
|
||||
}
|
||||
},
|
||||
onMessage: (_, e) => {
|
||||
if (e.data !== 'END') {
|
||||
const data = JSON.parse(e.data) as Api.JobLog.JobMessage;
|
||||
data.key = `${data.time_stamp}-${generateRandomString(16)}`;
|
||||
logList.value.push(data);
|
||||
} else {
|
||||
finished.value = true;
|
||||
stopLogByWs();
|
||||
}
|
||||
}
|
||||
});
|
||||
getLogListByWs();
|
||||
return;
|
||||
}
|
||||
|
||||
await getLogList();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function timestampToDate(timestamp: string): string {
|
||||
@ -284,7 +375,7 @@ const SnailLogComponent = defineComponent({
|
||||
}
|
||||
const restOfText = throwable.replace(/^.+(\n|$)/m, '');
|
||||
return (
|
||||
<NCollapseItem title={firstLine[0]} name={`throwable-${message.index}`}>
|
||||
<NCollapseItem title={firstLine[0]} name={`throwable-${message.key}`}>
|
||||
<NScrollbar content-class="p-8px" class="message-scroll-body">{`${restOfText}`}</NScrollbar>
|
||||
</NCollapseItem>
|
||||
);
|
||||
@ -302,7 +393,7 @@ const SnailLogComponent = defineComponent({
|
||||
const restOfText = msg.replace(/^.+(\n|$)/m, '').replaceAll('\n', '\n - ');
|
||||
if (restOfText) {
|
||||
return (
|
||||
<NCollapseItem title={firstLine[0]} name={`message-${message.index}`}>
|
||||
<NCollapseItem title={firstLine[0]} name={`message-${message.key}`}>
|
||||
<NScrollbar content-class="p-8px" class="message-scroll-body">{` - ${restOfText}`}</NScrollbar>
|
||||
</NCollapseItem>
|
||||
);
|
||||
@ -336,8 +427,8 @@ const SnailLogComponent = defineComponent({
|
||||
onResize={handleResize}
|
||||
>
|
||||
{{
|
||||
default: ({ item: message }: { item: Api.JobLog.JobMessage }) => (
|
||||
<pre key={message.index} class="min-h-85px min-w-full">
|
||||
default: ({ item: message }: { item: Api.JobLog.JobMessage; index: number }) => (
|
||||
<pre key={message.key} class="min-h-85px min-w-full">
|
||||
<div>
|
||||
<span class="log-hljs-time inline-block">{timestampToDate(message.time_stamp)}</span>
|
||||
<span
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { contentTypeRecord } from '@/constants/business';
|
||||
import { contentTypeRecord, workFlowNodeStatusRecord } from '@/constants/business';
|
||||
|
||||
defineOptions({
|
||||
name: 'CallbackDetail'
|
||||
@ -48,6 +48,9 @@ const onClose = () => {
|
||||
<NDescriptionsItem label="密钥">
|
||||
{{ modelValue.callback?.secret }}
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem label="回调通知状态">
|
||||
{{ $t(workFlowNodeStatusRecord[modelValue.workflowNodeStatus!]) }}
|
||||
</NDescriptionsItem>
|
||||
</NDescriptions>
|
||||
</DetailDrawer>
|
||||
</template>
|
||||
|
@ -54,7 +54,7 @@ const getTaskName = (id: string) => {
|
||||
<NDescriptionsItem label="失败策略">
|
||||
{{ $t(failStrategyRecord[modelValue.failStrategy!]) }}
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem label="工作流状态">
|
||||
<NDescriptionsItem label="任务状态">
|
||||
{{ $t(workFlowNodeStatusRecord[modelValue.workflowNodeStatus!]) }}
|
||||
</NDescriptionsItem>
|
||||
</NDescriptions>
|
||||
|
@ -98,8 +98,8 @@ const rules: FormRules = {
|
||||
</NFormItem>
|
||||
<NFormItem
|
||||
name="workflowNodeStatus"
|
||||
label="工作流状态"
|
||||
:rules="[{ required: true, message: '请选择工作流状态', trigger: 'change' }]"
|
||||
label="回调通知状态"
|
||||
:rules="[{ required: true, message: '请选择回调通知状态', trigger: 'change' }]"
|
||||
>
|
||||
<NRadioGroup v-model:value="form.workflowNodeStatus">
|
||||
<NSpace>
|
||||
|
@ -141,7 +141,7 @@ const jobTaskChange = (_: string, option: { label: string; value: number }) => {
|
||||
</NSpace>
|
||||
</NRadioGroup>
|
||||
</NFormItem>
|
||||
<NFormItem path="workflowNodeStatus" label="节点状态">
|
||||
<NFormItem path="workflowNodeStatus" label="任务状态">
|
||||
<NRadioGroup v-model:value="form.workflowNodeStatus">
|
||||
<NSpace>
|
||||
<NRadio
|
||||
|
1
src/typings/api.d.ts
vendored
1
src/typings/api.d.ts
vendored
@ -1350,6 +1350,7 @@ declare namespace Api {
|
||||
|
||||
type JobMessage = {
|
||||
index: number;
|
||||
key: string;
|
||||
level: JobLevel;
|
||||
host: string;
|
||||
port: string;
|
||||
|
@ -274,3 +274,19 @@ export function debounce(func: () => any, wait: number, immediate?: boolean) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 随机数
|
||||
*
|
||||
* @param length - 长度
|
||||
* @returns 随机数
|
||||
*/
|
||||
export function generateRandomString(length: number) {
|
||||
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
let randomString = '';
|
||||
for (let i = 0; i < length; i += 1) {
|
||||
const randomNumber = Math.floor(Math.random() * chars.length);
|
||||
randomString += chars.substring(randomNumber, randomNumber + 1);
|
||||
}
|
||||
return randomString;
|
||||
}
|
||||
|
@ -1,28 +1,24 @@
|
||||
/** socket 通信 */
|
||||
import { getServiceBaseURL } from '@/utils/service';
|
||||
import { localStg } from './storage';
|
||||
const { baseURL } = getServiceBaseURL(import.meta.env, false);
|
||||
const url = baseURL.replace('http://', 'ws://');
|
||||
import { generateRandomString } from './common';
|
||||
|
||||
/** 生成 token */
|
||||
function generateToken(length: number) {
|
||||
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
let token = 'SID_';
|
||||
for (let i = 0; i < length; i += 1) {
|
||||
const randomNumber = Math.floor(Math.random() * chars.length);
|
||||
token += chars.substring(randomNumber, randomNumber + 1);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
// 初始化socket
|
||||
export function initWebSocket(scene: string) {
|
||||
/**
|
||||
* 初始化 websocket
|
||||
*
|
||||
* @param scene - 场景
|
||||
* @param sid - 会话 id
|
||||
* @returns
|
||||
*/
|
||||
export function initWebSocketUrl(scene: string, sid?: string) {
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
|
||||
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
|
||||
const { baseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
|
||||
const url =
|
||||
import.meta.env.MODE === 'test' ? import.meta.env.VITE_SERVICE_BASE_URL : protocol + window.location.host + baseURL;
|
||||
const token = localStg.get('token');
|
||||
if (import.meta.env.VITE_APP_WEBSOCKET === 'N' || !token) {
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sid = generateToken(32);
|
||||
// 初始化 websocket
|
||||
return new WebSocket(`${url}/websocket?Snail-Job-Auth=${token}&sid=${sid}&scene=${scene}`);
|
||||
return `${url}/websocket?Snail-Job-Auth=${token}&sid=${sid ?? generateRandomString(32)}&scene=${scene}`;
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ const title = computed(() => {
|
||||
|
||||
<template>
|
||||
<div class="h-full">
|
||||
<LogDrawerWs :drawer="false" :title="title" :type="type" :task-data="taskData" />
|
||||
<LogDrawer :drawer="false" :title="title" :type="type" :task-data="taskData" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -75,7 +75,7 @@ async function retry() {
|
||||
</NTabs>
|
||||
</DetailDrawer>
|
||||
<!-- <LogDrawer v-model:show="logShow" :title="$t('page.log.title')" :task-data="taskData" />-->
|
||||
<LogDrawerWs v-model:show="logShow" :title="$t('page.log.title')" :task-data="taskData" />
|
||||
<LogDrawer v-model:show="logShow" :title="$t('page.log.title')" :task-data="taskData" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
Loading…
Reference in New Issue
Block a user