feat: 新增批次详情卡片
This commit is contained in:
parent
2a913e4edd
commit
e82682b9c8
@ -10,6 +10,36 @@ export function fetchJobList(groupName: string) {
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchJobLogList(params?: FlowApi.JobLog.JobLogSearchParams) {
|
||||
return request<FlowApi.JobLog.JobLogList>({
|
||||
url: '/job/log/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchJobDetail(id: string) {
|
||||
return request<Flow.JobTaskType>({
|
||||
url: `/job/${id}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchBatchDetail(id: string) {
|
||||
return request<Flow.JobBatchType>({
|
||||
url: `/job/batch/${id}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchTaskList(params: { groupName: string; taskBatchId: string; page?: number; pageSize?: number }) {
|
||||
return request<Flow.JobBatchPage>({
|
||||
url: '/job/task/list',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchNodeRetry(nodeId: string, taskBatchId: string) {
|
||||
return request<null>({
|
||||
url: `/workflow/node/retry/${nodeId}/${taskBatchId}`,
|
||||
@ -60,3 +90,10 @@ export function fetchWorkflowBatchInfo(id: string) {
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchWorkflowNodeRetry(id: string, workflowNodeId: number) {
|
||||
return request<null>({
|
||||
url: `/workflow/node/retry/${workflowNodeId}/${id}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
376
packages/work-flow/src/components/detail-card.vue
Normal file
376
packages/work-flow/src/components/detail-card.vue
Normal file
@ -0,0 +1,376 @@
|
||||
<script setup lang="tsx">
|
||||
import { nextTick, ref, useSlots, watch } from 'vue';
|
||||
import type { DataTableColumn } from 'naive-ui';
|
||||
import { NButton, NTag } from 'naive-ui';
|
||||
import { useFlowStore } from '../stores';
|
||||
import { fetchBatchDetail, fetchJobDetail, fetchTaskList, fetchWorkflowNodeRetry } from '../api';
|
||||
import { executorTypeRecord, operationReasonRecord, taskBatchStatusRecord } from '../constants/business';
|
||||
import { $t } from '../locales';
|
||||
import LogDrawer from './log-drawer.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'DetailCard'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
id?: string;
|
||||
ids?: string[];
|
||||
show?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
show: false,
|
||||
ids: () => []
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:show', show: boolean): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const slots = useSlots();
|
||||
|
||||
const store = useFlowStore();
|
||||
const visible = ref(false);
|
||||
const logOpen = ref(false);
|
||||
const spinning = ref(false);
|
||||
const loading = ref(false);
|
||||
const currentIndex = ref(1);
|
||||
const jobData = ref<Flow.JobTaskType>({});
|
||||
const dataSource = ref<Flow.JobBatchType[]>([]);
|
||||
|
||||
const pagination = ref({
|
||||
page: 1,
|
||||
pageCount: 0,
|
||||
pageSize: 10,
|
||||
showSizePicker: true,
|
||||
pageSizes: [10, 15, 20, 25, 30],
|
||||
onUpdatePage: async (page: number) => {
|
||||
pagination.value.page = page;
|
||||
const id = props.ids[currentIndex.value - 1];
|
||||
getBatchDetail(id);
|
||||
getRows(id, page);
|
||||
},
|
||||
onUpdatePageSize: async (pageSize: number) => {
|
||||
pagination.value.pageSize = pageSize;
|
||||
const id = props.ids[currentIndex.value - 1];
|
||||
getBatchDetail(id);
|
||||
getRows(id, pagination.value.page);
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.show,
|
||||
val => {
|
||||
visible.value = val;
|
||||
if (val) {
|
||||
onLoad();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const onUpdateShow = () => {
|
||||
emit('update:show', false);
|
||||
};
|
||||
|
||||
async function getDetail(id: string) {
|
||||
spinning.value = true;
|
||||
const { data, error } = await fetchJobDetail(id);
|
||||
if (!error) {
|
||||
jobData.value = data;
|
||||
spinning.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function getBatchDetail(id: string) {
|
||||
spinning.value = true;
|
||||
const { data, error } = await fetchBatchDetail(id);
|
||||
if (!error) {
|
||||
jobData.value = data;
|
||||
spinning.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function getRows(id: string, page: number = 1) {
|
||||
loading.value = true;
|
||||
const { data, error } = await fetchTaskList({
|
||||
groupName: store.groupName!,
|
||||
taskBatchId: id ?? '0',
|
||||
page,
|
||||
pageSize: pagination.value.pageSize
|
||||
});
|
||||
if (!error) {
|
||||
pagination.value.pageCount = data.total;
|
||||
dataSource.value = data.data;
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const idList = ref<string[]>([]);
|
||||
|
||||
function onLoad() {
|
||||
idList.value = props.ids;
|
||||
|
||||
nextTick(() => {
|
||||
if (props.ids.length > 0) {
|
||||
getBatchDetail(props.ids[0]);
|
||||
getRows(props.ids[0]);
|
||||
} else if (props.id) {
|
||||
idList.value = [jobData.value.taskBatchId!];
|
||||
getDetail(props.id);
|
||||
getRows(jobData.value.taskBatchId!);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const record = ref<Flow.JobTaskType>({});
|
||||
|
||||
const getLogRows = (task: Flow.JobTaskType) => {
|
||||
record.value = task;
|
||||
logOpen.value = true;
|
||||
};
|
||||
|
||||
const retry = async (item: Flow.JobTaskType) => {
|
||||
const { error } = await fetchWorkflowNodeRetry(store.id!, item.workflowNodeId!);
|
||||
if (!error) {
|
||||
window.$message?.success('执行重试成功');
|
||||
}
|
||||
};
|
||||
|
||||
const isRetry = (taskBatchStatus: number) => {
|
||||
return taskBatchStatus === 4 || taskBatchStatus === 5 || taskBatchStatus === 6;
|
||||
};
|
||||
|
||||
type ThemeColor = 'default' | 'error' | 'primary' | 'info' | 'success' | 'warning';
|
||||
|
||||
const columns = ref<DataTableColumn<Flow.JobBatchType>[]>([
|
||||
{
|
||||
key: 'index',
|
||||
title: '日志',
|
||||
align: 'center',
|
||||
width: 64,
|
||||
render: row => {
|
||||
return (
|
||||
<NButton type="info" text onClick={() => getLogRows(row)}>
|
||||
<span class="w-28px ws-break-spaces">{`查看\n日志`}</span>
|
||||
</NButton>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'id',
|
||||
title: $t('snail.jobBatch.jobTask.id'),
|
||||
align: 'left',
|
||||
minWidth: 64
|
||||
},
|
||||
{
|
||||
key: 'groupName',
|
||||
title: $t('snail.jobBatch.jobTask.groupName'),
|
||||
align: 'left',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'taskStatus',
|
||||
title: $t('snail.jobBatch.jobTask.taskStatus'),
|
||||
align: 'left',
|
||||
minWidth: 80,
|
||||
render: row => {
|
||||
if (row.taskStatus === null) {
|
||||
return undefined;
|
||||
}
|
||||
const label = $t(taskBatchStatusRecord[row.taskStatus!]);
|
||||
const tagMap: Record<number, ThemeColor> = {
|
||||
1: 'info',
|
||||
2: 'info',
|
||||
3: 'info',
|
||||
4: 'error',
|
||||
5: 'error',
|
||||
6: 'error'
|
||||
};
|
||||
return <NTag type={tagMap[row.taskStatus!]}>{label}</NTag>;
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'clientInfo',
|
||||
title: $t('snail.jobBatch.jobTask.clientInfo'),
|
||||
align: 'left',
|
||||
minWidth: 120,
|
||||
render: row => {
|
||||
if (row.clientInfo) {
|
||||
const parts = row.clientInfo?.split('@');
|
||||
const result = parts.length > 1 ? parts[1] : '';
|
||||
return <div>{result}</div>;
|
||||
}
|
||||
return <div>{row.clientInfo}</div>;
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'argsStr',
|
||||
title: $t('snail.jobBatch.jobTask.argsStr'),
|
||||
align: 'left',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'resultMessage',
|
||||
title: $t('snail.jobBatch.jobTask.resultMessage'),
|
||||
align: 'left',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
key: 'retryCount',
|
||||
title: $t('snail.jobBatch.jobTask.retryCount'),
|
||||
align: 'left',
|
||||
minWidth: 64
|
||||
},
|
||||
{
|
||||
key: 'createDt',
|
||||
title: $t('snail.jobBatch.jobTask.createDt'),
|
||||
align: 'left',
|
||||
minWidth: 120
|
||||
}
|
||||
]);
|
||||
|
||||
function tagColor(tagIndex: number) {
|
||||
const tagMap: Record<number, ThemeColor> = {
|
||||
0: 'error',
|
||||
1: 'info',
|
||||
2: 'success',
|
||||
3: 'warning',
|
||||
4: 'primary'
|
||||
};
|
||||
|
||||
if (tagIndex === null || tagIndex < 0) {
|
||||
return tagMap[1];
|
||||
}
|
||||
|
||||
return tagMap[tagIndex % 5];
|
||||
}
|
||||
|
||||
const onUpdatePage = (page: number) => {
|
||||
currentIndex.value = page;
|
||||
const id = props.ids[page - 1];
|
||||
getBatchDetail(id);
|
||||
getRows(id, page);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer v-model:show="visible" :width="800" display-directive="if" @update:show="onUpdateShow">
|
||||
<NDrawerContent title="任务批次详情" closable>
|
||||
<NTabs v-if="idList && idList.length > 0" v-model:value="currentIndex" type="segment" animated>
|
||||
<NTabPane v-for="(item, index) in idList" :key="index" :name="index + 1" :tab="item">
|
||||
<NDescriptions label-placement="top" bordered :column="2">
|
||||
<NDescriptionsItem :label="$t('snail.jobBatch.groupName')">{{ jobData?.groupName }}</NDescriptionsItem>
|
||||
|
||||
<NDescriptionsItem :label="$t('snail.jobBatch.jobName')">{{ jobData?.jobName }}</NDescriptionsItem>
|
||||
|
||||
<NDescriptionsItem :label="$t('snail.jobBatch.taskBatchStatus')">
|
||||
<NTag v-if="jobData.taskBatchStatus" :type="tagColor(jobData?.taskBatchStatus!)">
|
||||
{{ $t(taskBatchStatusRecord[jobData?.taskBatchStatus]) }}
|
||||
</NTag>
|
||||
</NDescriptionsItem>
|
||||
|
||||
<NDescriptionsItem :label="$t('snail.jobBatch.executionAt')">
|
||||
{{ jobData?.executionAt }}
|
||||
</NDescriptionsItem>
|
||||
|
||||
<NDescriptionsItem :label="$t('snail.jobBatch.operationReason')">
|
||||
<NTag v-if="jobData.operationReason" :type="tagColor(jobData?.operationReason!)">
|
||||
{{ $t(operationReasonRecord[jobData.operationReason]) }}
|
||||
</NTag>
|
||||
</NDescriptionsItem>
|
||||
|
||||
<NDescriptionsItem v-if="!slots.default" :label="$t('snail.jobBatch.executorType')">
|
||||
<NTag v-if="jobData.executorType" :type="tagColor(jobData?.executorType!)">
|
||||
{{ $t(executorTypeRecord[jobData?.executorType!]) }}
|
||||
</NTag>
|
||||
</NDescriptionsItem>
|
||||
|
||||
<NDescriptionsItem :label="$t('snail.jobBatch.executorInfo')" :span="2">
|
||||
{{ jobData?.executorInfo }}
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem :label="$t('snail.jobBatch.createDt')" :span="2">
|
||||
{{ jobData?.createDt }}
|
||||
</NDescriptionsItem>
|
||||
</NDescriptions>
|
||||
<slot></slot>
|
||||
<NCard
|
||||
:bordered="false"
|
||||
size="small"
|
||||
class="sm:flex-1-hidden card-wrapper"
|
||||
:content-style="{ padding: 0 }"
|
||||
:header-style="{ padding: 0 }"
|
||||
>
|
||||
<template #header>
|
||||
<div class="header-border"><span class="pl-12px">任务项列表</span></div>
|
||||
</template>
|
||||
<template #header-extra>
|
||||
<NTooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<NButton text @click="getRows(item)">
|
||||
<icon-ant-design:sync-outlined class="mr-16px text-20px font-bold" />
|
||||
</NButton>
|
||||
</template>
|
||||
刷新
|
||||
</NTooltip>
|
||||
<NTooltip v-if="isRetry(jobData.taskBatchStatus!)" trigger="hover">
|
||||
<template #trigger>
|
||||
<NButton text>
|
||||
<icon-ant-design:redo-outlined class="text-20px font-bold" @click="retry(jobData)" />
|
||||
</NButton>
|
||||
</template>
|
||||
重试
|
||||
</NTooltip>
|
||||
</template>
|
||||
<NDataTable
|
||||
:columns="columns"
|
||||
:data="dataSource"
|
||||
:loading="loading"
|
||||
:scroll="{ x: 1200 }"
|
||||
remote
|
||||
:row-key="row => row.id"
|
||||
:pagination="pagination"
|
||||
class="sm:h-full"
|
||||
/>
|
||||
</NCard>
|
||||
</NTabPane>
|
||||
</NTabs>
|
||||
<template v-if="ids && ids.length > 1" #footer>
|
||||
<NPagination
|
||||
v-model:page="currentIndex"
|
||||
class="text-center"
|
||||
:page-size="1"
|
||||
:page-count="ids.length"
|
||||
@update:page="onUpdatePage"
|
||||
/>
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
<LogDrawer v-model:show="logOpen" title="日志详情" :task-data="record" />
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.empty {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: calc(100% - 88px);
|
||||
}
|
||||
|
||||
.header-border {
|
||||
margin: 20px 0;
|
||||
border-left: #1366ff 5px solid;
|
||||
font-size: medium;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
:deep(.n-tabs-nav) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(.n-tab-pane) {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
</style>
|
213
packages/work-flow/src/components/log-drawer.vue
Normal file
213
packages/work-flow/src/components/log-drawer.vue
Normal file
@ -0,0 +1,213 @@
|
||||
<script setup lang="tsx">
|
||||
import { NCollapse, NCollapseItem } from 'naive-ui';
|
||||
import { defineComponent, onBeforeUnmount, ref, watch } from 'vue';
|
||||
import { $t } from '../locales';
|
||||
import { fetchJobLogList } from '../api';
|
||||
|
||||
defineOptions({
|
||||
name: 'LogDrawer'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
show?: boolean;
|
||||
taskData: Flow.JobTaskType;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title: $t('node.log.title'),
|
||||
show: false,
|
||||
modelValue: () => []
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:show', show: boolean): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: true
|
||||
});
|
||||
|
||||
const ThrowableComponent = defineComponent({
|
||||
props: {
|
||||
throwable: String
|
||||
},
|
||||
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>
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.show,
|
||||
val => {
|
||||
visible.value = val;
|
||||
if (val) {
|
||||
getLogList();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const onUpdateShow = (value: boolean) => {
|
||||
emit('update:show', value);
|
||||
};
|
||||
|
||||
const logList = ref<FlowApi.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();
|
||||
});
|
||||
|
||||
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()}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer v-model:show="visible" width="100%" display-directive="if" @update:show="onUpdateShow">
|
||||
<NDrawerContent :title="title" closable>
|
||||
<div class="snail-log bg-#fafafc p-16px dark:bg-#000">
|
||||
<div class="snail-log-scrollbar">
|
||||
<code>
|
||||
<pre
|
||||
v-for="(message, index) in logList"
|
||||
:key="index"
|
||||
><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>
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</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>
|
@ -113,3 +113,28 @@ export const taskBatchStatusRecord: Record<Flow.TaskBatchStatus, FlowI18n.I18nKe
|
||||
};
|
||||
|
||||
export const taskBatchStatusOptions = transformRecordToOption(taskBatchStatusRecord);
|
||||
|
||||
export const operationReasonRecord: Record<Flow.OperationReason, FlowI18n.I18nKey> = {
|
||||
0: 'snail.enum.jobOperationReason.none',
|
||||
1: 'snail.enum.jobOperationReason.taskExecutionTimeout',
|
||||
2: 'snail.enum.jobOperationReason.notClient',
|
||||
3: 'snail.enum.jobOperationReason.closed',
|
||||
4: 'snail.enum.jobOperationReason.discard',
|
||||
5: 'snail.enum.jobOperationReason.overlay',
|
||||
6: 'snail.enum.jobOperationReason.notExecutionTask',
|
||||
7: 'snail.enum.jobOperationReason.taskExecutionError',
|
||||
8: 'snail.enum.jobOperationReason.mannerStop',
|
||||
9: 'snail.enum.jobOperationReason.workflowConditionNodeExecutionError',
|
||||
10: 'snail.enum.jobOperationReason.jobTaskInterrupted',
|
||||
11: 'snail.enum.jobOperationReason.workflowCallbackNodeExecutionError',
|
||||
12: 'snail.enum.jobOperationReason.workflowNodeNoRequired',
|
||||
13: 'snail.enum.jobOperationReason.workflowNodeClosedSkipExecution',
|
||||
14: 'snail.enum.jobOperationReason.workflowDecisionFailed'
|
||||
};
|
||||
export const operationReasonOptions = transformRecordToOption(operationReasonRecord);
|
||||
|
||||
export const executorTypeRecord: Record<Flow.ExecutorType, FlowI18n.I18nKey> = {
|
||||
1: 'snail.enum.executorType.java'
|
||||
};
|
||||
|
||||
export const executorTypeRecordOptions = transformRecordToOption(executorTypeRecord);
|
||||
|
@ -7,6 +7,7 @@ const local: FlowI18n.Schema = {
|
||||
retry: 'Retry',
|
||||
ignore: 'Ignore',
|
||||
stop: 'Stop',
|
||||
refresh: 'Refresh',
|
||||
form: {
|
||||
groupName: 'Please select group',
|
||||
workflowTip: 'Please configure workflow',
|
||||
@ -14,6 +15,26 @@ const local: FlowI18n.Schema = {
|
||||
stopMessage: 'Stop mission successful',
|
||||
taskTip: 'Please select task'
|
||||
},
|
||||
jobBatch: {
|
||||
groupName: 'Group name',
|
||||
jobName: 'Job name',
|
||||
executorInfo: 'Executor Name',
|
||||
executorType: 'Executor type',
|
||||
executionAt: 'Start execution time',
|
||||
taskBatchStatus: 'Task Batch Status',
|
||||
operationReason: 'Reason for operation',
|
||||
createDt: 'Create time',
|
||||
jobTask: {
|
||||
id: 'ID',
|
||||
groupName: 'Group name',
|
||||
taskStatus: 'Status',
|
||||
clientInfo: 'Client address',
|
||||
argsStr: 'Argument string',
|
||||
resultMessage: 'Result message',
|
||||
retryCount: 'Number of retries',
|
||||
createDt: 'Create time'
|
||||
}
|
||||
},
|
||||
enum: {
|
||||
failStrategy: {
|
||||
skip: 'Skip',
|
||||
@ -41,6 +62,26 @@ const local: FlowI18n.Schema = {
|
||||
triggerType: {
|
||||
time: 'Fixed Time',
|
||||
cron: 'CRON Expressions'
|
||||
},
|
||||
jobOperationReason: {
|
||||
none: 'None',
|
||||
taskExecutionTimeout: 'Task execution timeout',
|
||||
notClient: 'No client',
|
||||
closed: 'Job closed',
|
||||
discard: 'Job discard',
|
||||
overlay: 'Job overlapped',
|
||||
notExecutionTask: 'No execution task',
|
||||
taskExecutionError: 'Execution error',
|
||||
mannerStop: 'Manual stop',
|
||||
workflowConditionNodeExecutionError: 'Condition node execution error',
|
||||
jobTaskInterrupted: 'Job interrupted',
|
||||
workflowCallbackNodeExecutionError: 'Callback node execution error',
|
||||
workflowNodeNoRequired: 'No process required',
|
||||
workflowNodeClosedSkipExecution: 'Node closed, skip execution',
|
||||
workflowDecisionFailed: 'Workflow decision failed'
|
||||
},
|
||||
executorType: {
|
||||
java: 'Java'
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -79,7 +120,10 @@ const local: FlowI18n.Schema = {
|
||||
webhookTip: 'Please configure callback notifications'
|
||||
}
|
||||
},
|
||||
endNode: 'End Node'
|
||||
endNode: 'End Node',
|
||||
log: {
|
||||
title: 'Log Detail'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -7,6 +7,7 @@ const local: FlowI18n.Schema = {
|
||||
retry: '重试',
|
||||
ignore: '忽略',
|
||||
stop: '停止',
|
||||
refresh: '刷新',
|
||||
form: {
|
||||
groupName: '请选择组',
|
||||
workflowTip: '请配置工作流',
|
||||
@ -14,6 +15,26 @@ const local: FlowI18n.Schema = {
|
||||
stopMessage: '停止任务成功',
|
||||
taskTip: '请选择任务'
|
||||
},
|
||||
jobBatch: {
|
||||
groupName: '组名称',
|
||||
jobName: '任务名称',
|
||||
executorInfo: '执行器名称',
|
||||
executorType: '执行器类型',
|
||||
executionAt: '开始执行时间',
|
||||
taskBatchStatus: '状态',
|
||||
operationReason: '操作原因',
|
||||
createDt: '创建时间',
|
||||
jobTask: {
|
||||
id: 'ID',
|
||||
groupName: '组名称',
|
||||
taskStatus: '状态',
|
||||
clientInfo: '地址',
|
||||
argsStr: '参数',
|
||||
resultMessage: '结果',
|
||||
retryCount: '重试次数',
|
||||
createDt: '开始执行时间'
|
||||
}
|
||||
},
|
||||
enum: {
|
||||
failStrategy: {
|
||||
skip: '跳过',
|
||||
@ -41,6 +62,26 @@ const local: FlowI18n.Schema = {
|
||||
triggerType: {
|
||||
time: '固定时间',
|
||||
cron: 'CRON 表达式'
|
||||
},
|
||||
jobOperationReason: {
|
||||
none: '无',
|
||||
taskExecutionTimeout: '任务执行超时',
|
||||
notClient: '无客户端节点',
|
||||
closed: '任务已关闭',
|
||||
discard: '任务丢弃',
|
||||
overlay: '任务被覆盖',
|
||||
notExecutionTask: '无可执行任务项',
|
||||
taskExecutionError: '任务执行期间发生非预期异常',
|
||||
mannerStop: '手动停止',
|
||||
workflowConditionNodeExecutionError: '条件节点执行异常',
|
||||
jobTaskInterrupted: '任务中断',
|
||||
workflowCallbackNodeExecutionError: '回调节点执行异常',
|
||||
workflowNodeNoRequired: '无需处理',
|
||||
workflowNodeClosedSkipExecution: '节点关闭跳过执行',
|
||||
workflowDecisionFailed: '判定未通过'
|
||||
},
|
||||
executorType: {
|
||||
java: 'Java'
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -77,7 +118,10 @@ const local: FlowI18n.Schema = {
|
||||
webhookTip: '请配置回调通知'
|
||||
}
|
||||
},
|
||||
endNode: '流程结束'
|
||||
endNode: '流程结束',
|
||||
log: {
|
||||
title: '日志详情'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -7,6 +7,7 @@ import { $t } from '../locales';
|
||||
import { failStrategyRecord, taskBatchStatusEnum } from '../constants/business';
|
||||
import TaskDrawer from '../drawer/task-drawer.vue';
|
||||
import TaskDetail from '../detail/task-detail.vue';
|
||||
import DetailCard from '../components/detail-card.vue';
|
||||
import AddNode from './add-node.vue';
|
||||
|
||||
defineOptions({
|
||||
@ -271,7 +272,7 @@ const isStop = (taskBatchStatus: number) => {
|
||||
v-model:len="nodeConfig.conditionNodes!.length"
|
||||
@save="save"
|
||||
/>
|
||||
<!-- <DetailCard v-if="store.type !== 0 && cardDrawer" :id="detailId" v-model:open="cardDrawer" :ids="detailIds" /> -->
|
||||
<DetailCard v-if="store.type !== 0" :id="detailId" v-model:show="cardDrawer" :ids="detailIds" />
|
||||
</div>
|
||||
</template>
|
||||
|
44
packages/work-flow/src/typings/api.d.ts
vendored
44
packages/work-flow/src/typings/api.d.ts
vendored
@ -5,4 +5,48 @@ declare namespace FlowApi {
|
||||
id: string;
|
||||
jobName: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* namespace JobLog
|
||||
*
|
||||
* backend api module: "JobLog"
|
||||
*/
|
||||
namespace JobLog {
|
||||
type JobLevel = 'INFO' | 'WARN' | 'ERROR' | 'DEBUG';
|
||||
|
||||
type JobLogSearchParams = {
|
||||
taskBatchId: string;
|
||||
jobId: string;
|
||||
taskId: string;
|
||||
} & LogSearchParams;
|
||||
|
||||
type RetryLogSearchParams = {
|
||||
groupName: string;
|
||||
uniqueId: string;
|
||||
} & LogSearchParams;
|
||||
|
||||
type LogSearchParams = {
|
||||
startId: string;
|
||||
fromIndex: number;
|
||||
size: number;
|
||||
};
|
||||
|
||||
type JobLogList = {
|
||||
finished: boolean;
|
||||
fromIndex: number;
|
||||
message: JobMessage[];
|
||||
nextStartId: string;
|
||||
};
|
||||
|
||||
type JobMessage = {
|
||||
level: JobLevel;
|
||||
host: string;
|
||||
port: string;
|
||||
location: string;
|
||||
message: string;
|
||||
thread: string;
|
||||
['time_stamp']: string;
|
||||
throwable: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
78
packages/work-flow/src/typings/flow.d.ts
vendored
78
packages/work-flow/src/typings/flow.d.ts
vendored
@ -7,6 +7,8 @@ declare namespace Flow {
|
||||
type ContentType = 1 | 2;
|
||||
type TriggerType = 2 | 3;
|
||||
type WorkFlowNodeStatus = 0 | 1;
|
||||
type ExecutorType = 1;
|
||||
type OperationReason = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14;
|
||||
|
||||
/** 组 */
|
||||
export type NodeDataType = {
|
||||
@ -121,4 +123,80 @@ declare namespace Flow {
|
||||
/** 图标 */
|
||||
icon: string;
|
||||
};
|
||||
|
||||
/** 定时任务详情 */
|
||||
export type JobTaskType = {
|
||||
/** 定时任务 ID */
|
||||
id?: string;
|
||||
/** 组名称 */
|
||||
groupName?: string;
|
||||
/** 任务信息 ID */
|
||||
jobId?: string;
|
||||
/** 任务名称 */
|
||||
jobName?: string;
|
||||
/** 节点名称 */
|
||||
nodeName?: string;
|
||||
/** 任务实例 ID */
|
||||
taskBatchId?: string;
|
||||
/** 状态 */
|
||||
jobStatus?: number;
|
||||
/** 状态 */
|
||||
taskBatchStatus?: Flow.TaskBatchStatus;
|
||||
/** 执行器类型 */
|
||||
executorType?: ExecutorType;
|
||||
/** 操作原因 */
|
||||
operationReason?: OperationReason;
|
||||
/** 开始执行时间 */
|
||||
executionAt?: string;
|
||||
/** 执行器名称 */
|
||||
executorInfo?: string;
|
||||
/** 创建时间 */
|
||||
createDt?: string;
|
||||
/** 工作流节点ID */
|
||||
workflowNodeId?: number;
|
||||
/** 工作流任务批次ID */
|
||||
workflowTaskBatchId?: number;
|
||||
};
|
||||
|
||||
/** 任务项列表 */
|
||||
export type JobBatchType = {
|
||||
/** ID */
|
||||
id?: string;
|
||||
/** 任务 ID */
|
||||
jobId?: string;
|
||||
/** 组名称 */
|
||||
groupName?: string;
|
||||
/** 地址 */
|
||||
clientInfo?: string;
|
||||
/** 参数 */
|
||||
argsStr?: string;
|
||||
/** 结果 */
|
||||
resultMessage?: string;
|
||||
/** 重试次数 */
|
||||
retryCount?: string;
|
||||
/** 开始执行时间 */
|
||||
createDt?: string;
|
||||
/** 任务批次 ID */
|
||||
taskBatchId?: string;
|
||||
/** 任务状态 ID */
|
||||
taskStatus?: TaskBatchStatus;
|
||||
};
|
||||
|
||||
export type JobBatchPage = {
|
||||
total: number;
|
||||
data: JobTaskType[];
|
||||
};
|
||||
|
||||
/** 任务日志 */
|
||||
export type JobLogType = {};
|
||||
|
||||
/** Tag */
|
||||
export type JobTagType = {
|
||||
[key: number | string]: {
|
||||
/** 名称 */
|
||||
name: string;
|
||||
/** 颜色 */
|
||||
color: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
44
packages/work-flow/src/typings/i18n.d.ts
vendored
44
packages/work-flow/src/typings/i18n.d.ts
vendored
@ -9,6 +9,7 @@ declare namespace FlowI18n {
|
||||
retry: string;
|
||||
ignore: string;
|
||||
stop: string;
|
||||
refresh: string;
|
||||
failStrategy: string;
|
||||
form: {
|
||||
groupName: string;
|
||||
@ -17,6 +18,26 @@ declare namespace FlowI18n {
|
||||
stopMessage: string;
|
||||
taskTip: string;
|
||||
};
|
||||
jobBatch: {
|
||||
groupName: string;
|
||||
jobName: string;
|
||||
executorInfo: string;
|
||||
executorType: string;
|
||||
executionAt: string;
|
||||
taskBatchStatus: string;
|
||||
operationReason: string;
|
||||
createDt: string;
|
||||
jobTask: {
|
||||
id: string;
|
||||
groupName: string;
|
||||
taskStatus: string;
|
||||
clientInfo: string;
|
||||
argsStr: string;
|
||||
resultMessage: string;
|
||||
retryCount: string;
|
||||
createDt: string;
|
||||
};
|
||||
};
|
||||
enum: {
|
||||
failStrategy: {
|
||||
skip: string;
|
||||
@ -45,6 +66,26 @@ declare namespace FlowI18n {
|
||||
time: string;
|
||||
cron: string;
|
||||
};
|
||||
jobOperationReason: {
|
||||
none: string;
|
||||
taskExecutionTimeout: string;
|
||||
notClient: string;
|
||||
closed: string;
|
||||
discard: string;
|
||||
overlay: string;
|
||||
notExecutionTask: string;
|
||||
taskExecutionError: string;
|
||||
mannerStop: string;
|
||||
workflowConditionNodeExecutionError: string;
|
||||
jobTaskInterrupted: string;
|
||||
workflowCallbackNodeExecutionError: string;
|
||||
workflowNodeNoRequired: string;
|
||||
workflowNodeClosedSkipExecution: string;
|
||||
workflowDecisionFailed: string;
|
||||
};
|
||||
executorType: {
|
||||
java: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
node: {
|
||||
@ -81,6 +122,9 @@ declare namespace FlowI18n {
|
||||
};
|
||||
};
|
||||
endNode: string;
|
||||
log: {
|
||||
title: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import NodeWrap from './components/node-wrap.vue';
|
||||
import StartNode from './components/start-node.vue';
|
||||
import NodeWrap from './node/node-wrap.vue';
|
||||
import StartNode from './node/start-node.vue';
|
||||
import { $t } from './locales';
|
||||
|
||||
defineOptions({
|
||||
|
@ -5,7 +5,7 @@ import { localStg } from '@/utils/storage';
|
||||
import systemLogo from '@/assets/svg-icon/logo.svg?raw';
|
||||
|
||||
export function setupLoading() {
|
||||
const themeColor = localStg.get('themeColor') || '#22aae3';
|
||||
const themeColor = localStg.get('themeColor') || '#1366FF';
|
||||
|
||||
const { r, g, b } = getRgb(themeColor);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user