mirror of
https://github.com/usual2970/certimate.git
synced 2025-10-04 13:34:52 +00:00
feat: workflow run status & time
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
import { WORKFLOW_TRIGGERS } from "@/domain/workflow";
|
||||
import { getPocketBase } from "@/repository/pocketbase";
|
||||
|
||||
export const run = async (id: string) => {
|
||||
@@ -11,7 +12,8 @@ export const run = async (id: string) => {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: {
|
||||
id,
|
||||
workflowId: id,
|
||||
trigger: WORKFLOW_TRIGGERS.MANUAL,
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -4,7 +4,7 @@ import { Alert, Drawer, Typography } from "antd";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import Show from "@/components/Show";
|
||||
import { type WorkflowRunModel } from "@/domain/workflowRun";
|
||||
import { WORKFLOW_RUN_STATUSES, type WorkflowRunModel } from "@/domain/workflowRun";
|
||||
import { useTriggerElement } from "@/hooks";
|
||||
|
||||
export type WorkflowRunDetailDrawerProps = {
|
||||
@@ -32,11 +32,11 @@ const WorkflowRunDetailDrawer = ({ data, loading, trigger, ...props }: WorkflowR
|
||||
|
||||
<Drawer destroyOnClose open={open} loading={loading} placement="right" title={`runlog-${data?.id}`} width={640} onClose={() => setOpen(false)}>
|
||||
<Show when={!!data}>
|
||||
<Show when={data!.succeeded}>
|
||||
<Show when={data!.status === WORKFLOW_RUN_STATUSES.SUCCEEDED}>
|
||||
<Alert showIcon type="success" message={<Typography.Text type="success">{t("workflow_run.props.status.succeeded")}</Typography.Text>} />
|
||||
</Show>
|
||||
|
||||
<Show when={!!data!.error}>
|
||||
<Show when={data!.status === WORKFLOW_RUN_STATUSES.FAILED}>
|
||||
<Alert showIcon type="error" message={<Typography.Text type="danger">{t("workflow_run.props.status.failed")}</Typography.Text>} />
|
||||
</Show>
|
||||
|
||||
|
@@ -2,14 +2,18 @@ import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
CheckCircleOutlined as CheckCircleOutlinedIcon,
|
||||
ClockCircleOutlined as ClockCircleOutlinedIcon,
|
||||
CloseCircleOutlined as CloseCircleOutlinedIcon,
|
||||
SelectOutlined as SelectOutlinedIcon,
|
||||
SyncOutlined as SyncOutlinedIcon,
|
||||
} from "@ant-design/icons";
|
||||
import { useRequest } from "ahooks";
|
||||
import { Button, Empty, Space, Table, type TableProps, Typography, notification, theme } from "antd";
|
||||
import { Button, Empty, Table, type TableProps, Tag, notification } from "antd";
|
||||
import dayjs from "dayjs";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
import { type WorkflowRunModel } from "@/domain/workflowRun";
|
||||
import { WORKFLOW_TRIGGERS } from "@/domain/workflow";
|
||||
import { WORKFLOW_RUN_STATUSES, type WorkflowRunModel } from "@/domain/workflowRun";
|
||||
import { list as listWorkflowRuns } from "@/repository/workflowRun";
|
||||
import { getErrMsg } from "@/utils/error";
|
||||
import WorkflowRunDetailDrawer from "./WorkflowRunDetailDrawer";
|
||||
@@ -23,8 +27,6 @@ export type WorkflowRunsProps = {
|
||||
const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { token: themeToken } = theme.useToken();
|
||||
|
||||
const [notificationApi, NotificationContextHolder] = notification.useNotification();
|
||||
|
||||
const tableColumns: TableProps<WorkflowRunModel>["columns"] = [
|
||||
@@ -46,45 +48,67 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => {
|
||||
title: t("workflow_run.props.status"),
|
||||
ellipsis: true,
|
||||
render: (_, record) => {
|
||||
if (record.succeeded) {
|
||||
if (record.status === WORKFLOW_RUN_STATUSES.PENDING) {
|
||||
return <Tag icon={<ClockCircleOutlinedIcon />}>{t("workflow_run.props.status.pending")}</Tag>;
|
||||
} else if (record.status === WORKFLOW_RUN_STATUSES.RUNNING) {
|
||||
return (
|
||||
<Space>
|
||||
<CheckCircleOutlinedIcon style={{ color: themeToken.colorSuccess }} />
|
||||
<Typography.Text type="success">{t("workflow_run.props.status.succeeded")}</Typography.Text>
|
||||
</Space>
|
||||
<Tag icon={<SyncOutlinedIcon spin />} color="processing">
|
||||
{t("workflow_run.props.status.running")}
|
||||
</Tag>
|
||||
);
|
||||
} else {
|
||||
} else if (record.status === WORKFLOW_RUN_STATUSES.SUCCEEDED) {
|
||||
return (
|
||||
<Space>
|
||||
<CloseCircleOutlinedIcon style={{ color: themeToken.colorError }} />
|
||||
<Typography.Text type="danger">{t("workflow_run.props.status.failed")}</Typography.Text>
|
||||
</Space>
|
||||
<Tag icon={<CheckCircleOutlinedIcon />} color="success">
|
||||
{t("workflow_run.props.status.succeeded")}
|
||||
</Tag>
|
||||
);
|
||||
} else if (record.status === WORKFLOW_RUN_STATUSES.FAILED) {
|
||||
return (
|
||||
<Tag icon={<CloseCircleOutlinedIcon />} color="error">
|
||||
{t("workflow_run.props.status.failed")}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
return <></>;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "trigger",
|
||||
title: t("workflow_run.props.trigger"),
|
||||
ellipsis: true,
|
||||
render: () => {
|
||||
return "TODO";
|
||||
render: (_, record) => {
|
||||
if (record.trigger === WORKFLOW_TRIGGERS.AUTO) {
|
||||
return t("workflow_run.props.trigger.auto");
|
||||
} else if (record.trigger === WORKFLOW_TRIGGERS.MANUAL) {
|
||||
return t("workflow_run.props.trigger.manual");
|
||||
}
|
||||
|
||||
return <></>;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "startedAt",
|
||||
title: t("workflow_run.props.started_at"),
|
||||
ellipsis: true,
|
||||
render: () => {
|
||||
return "TODO";
|
||||
render: (_, record) => {
|
||||
if (record.startedAt) {
|
||||
return dayjs(record.startedAt).format("YYYY-MM-DD HH:mm:ss");
|
||||
}
|
||||
|
||||
return <></>;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "completedAt",
|
||||
title: t("workflow_run.props.completed_at"),
|
||||
key: "endedAt",
|
||||
title: t("workflow_run.props.ended_at"),
|
||||
ellipsis: true,
|
||||
render: () => {
|
||||
return "TODO";
|
||||
render: (_, record) => {
|
||||
if (record.endedAt) {
|
||||
return dayjs(record.endedAt).format("YYYY-MM-DD HH:mm:ss");
|
||||
}
|
||||
|
||||
return <></>;
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@@ -13,6 +13,9 @@ export interface WorkflowModel extends BaseModel {
|
||||
content?: WorkflowNode;
|
||||
draft?: WorkflowNode;
|
||||
hasDraft?: boolean;
|
||||
lastRunId?: string;
|
||||
lastRunStatus?: string;
|
||||
lastRunTime?: string;
|
||||
}
|
||||
|
||||
export const WORKFLOW_TRIGGERS = Object.freeze({
|
||||
|
@@ -1,8 +1,11 @@
|
||||
export interface WorkflowRunModel extends BaseModel {
|
||||
workflowId: string;
|
||||
status: string;
|
||||
trigger: string;
|
||||
startedAt: ISO8601String;
|
||||
endedAt: ISO8601String;
|
||||
logs: WorkflowRunLog[];
|
||||
error: string;
|
||||
succeeded: boolean;
|
||||
}
|
||||
|
||||
export type WorkflowRunLog = {
|
||||
@@ -18,3 +21,12 @@ export type WorkflowRunLogOutput = {
|
||||
content: string;
|
||||
error: string;
|
||||
};
|
||||
|
||||
export const WORKFLOW_RUN_STATUSES = Object.freeze({
|
||||
PENDING: "pending",
|
||||
RUNNING: "running",
|
||||
SUCCEEDED: "succeeded",
|
||||
FAILED: "failed",
|
||||
} as const);
|
||||
|
||||
export type WorkflorRunStatusType = (typeof WORKFLOW_RUN_STATUSES)[keyof typeof WORKFLOW_RUN_STATUSES];
|
||||
|
@@ -16,7 +16,7 @@
|
||||
"workflow.props.trigger": "Trigger",
|
||||
"workflow.props.trigger.auto": "Auto",
|
||||
"workflow.props.trigger.manual": "Manual",
|
||||
"workflow.props.latest_execution_status": "Latest execution status",
|
||||
"workflow.props.last_run_at": "Last run at",
|
||||
"workflow.props.state": "State",
|
||||
"workflow.props.state.filter.enabled": "Enabled",
|
||||
"workflow.props.state.filter.disabled": "Disabled",
|
||||
|
@@ -1,9 +1,13 @@
|
||||
{
|
||||
"workflow_run.props.id": "ID",
|
||||
"workflow_run.props.status": "Status",
|
||||
"workflow_run.props.status.pending": "Pending",
|
||||
"workflow_run.props.status.running": "Running",
|
||||
"workflow_run.props.status.succeeded": "Succeeded",
|
||||
"workflow_run.props.status.failed": "Failed",
|
||||
"workflow_run.props.trigger": "Trigger",
|
||||
"workflow_run.props.trigger.auto": "Timing",
|
||||
"workflow_run.props.trigger.manual": "Manual",
|
||||
"workflow_run.props.started_at": "Started at",
|
||||
"workflow_run.props.completed_at": "Completed at"
|
||||
"workflow_run.props.ended_at": "Ended at"
|
||||
}
|
||||
|
@@ -16,7 +16,7 @@
|
||||
"workflow.props.trigger": "触发方式",
|
||||
"workflow.props.trigger.auto": "自动",
|
||||
"workflow.props.trigger.manual": "手动",
|
||||
"workflow.props.latest_execution_status": "最近执行状态",
|
||||
"workflow.props.last_run_at": "最近执行时间",
|
||||
"workflow.props.state": "启用状态",
|
||||
"workflow.props.state.filter.enabled": "启用",
|
||||
"workflow.props.state.filter.disabled": "未启用",
|
||||
|
@@ -1,9 +1,13 @@
|
||||
{
|
||||
"workflow_run.props.id": "ID",
|
||||
"workflow_run.props.status": "状态",
|
||||
"workflow_run.props.status.pending": "等待执行",
|
||||
"workflow_run.props.status.running": "执行中",
|
||||
"workflow_run.props.status.succeeded": "成功",
|
||||
"workflow_run.props.status.failed": "失败",
|
||||
"workflow_run.props.trigger": "触发方式",
|
||||
"workflow_run.props.trigger": "执行方式",
|
||||
"workflow_run.props.trigger.auto": "定时执行",
|
||||
"workflow_run.props.trigger.manual": "手动执行",
|
||||
"workflow_run.props.started_at": "开始时间",
|
||||
"workflow_run.props.completed_at": "完成时间"
|
||||
"workflow_run.props.ended_at": "完成时间"
|
||||
}
|
||||
|
@@ -125,7 +125,7 @@ const CertificateList = () => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
{record.expand?.workflowId?.name ?? `#${workflowId}`}
|
||||
{record.expand?.workflowId?.name ?? <span className="font-mono">{t(`#${workflowId}`)}</span>}
|
||||
</Typography.Link>
|
||||
</Space>
|
||||
);
|
||||
@@ -174,7 +174,7 @@ const CertificateList = () => {
|
||||
icon={<DeleteOutlinedIcon />}
|
||||
variant="text"
|
||||
onClick={() => {
|
||||
alert("TODO");
|
||||
alert("TODO: 暂时不支持删除证书");
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
@@ -248,7 +248,7 @@ const WorkflowDetail = () => {
|
||||
<Show when={tabValue === "orchestration"}>
|
||||
<div className="relative">
|
||||
<div className="py-12 lg:pr-36 xl:pr-48">
|
||||
<WorkflowElements disabled={isRunning} />
|
||||
<WorkflowElements />
|
||||
</div>
|
||||
<div className="absolute right-0 top-0 z-[1]">
|
||||
<Space>
|
||||
|
@@ -1,10 +1,18 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { DeleteOutlined as DeleteOutlinedIcon, EditOutlined as EditOutlinedIcon, PlusOutlined as PlusOutlinedIcon } from "@ant-design/icons";
|
||||
import {
|
||||
CheckCircleOutlined as CheckCircleOutlinedIcon,
|
||||
CloseCircleOutlined as CloseCircleOutlinedIcon,
|
||||
DeleteOutlined as DeleteOutlinedIcon,
|
||||
EditOutlined as EditOutlinedIcon,
|
||||
PlusOutlined as PlusOutlinedIcon,
|
||||
} from "@ant-design/icons";
|
||||
|
||||
import { PageHeader } from "@ant-design/pro-components";
|
||||
import { useRequest } from "ahooks";
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Divider,
|
||||
Empty,
|
||||
@@ -26,6 +34,7 @@ import dayjs from "dayjs";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
import { WORKFLOW_TRIGGERS, type WorkflowModel, isAllNodesValidated } from "@/domain/workflow";
|
||||
import { WORKFLOW_RUN_STATUSES } from "@/domain/workflowRun";
|
||||
import { list as listWorkflow, remove as removeWorkflow, save as saveWorkflow } from "@/repository/workflow";
|
||||
import { getErrMsg } from "@/utils/error";
|
||||
|
||||
@@ -146,11 +155,28 @@ const WorkflowList = () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "lastExecutedAt",
|
||||
title: t("workflow.props.latest_execution_status"),
|
||||
render: () => {
|
||||
// TODO: 最近执行状态
|
||||
return <>TODO</>;
|
||||
key: "lastRun",
|
||||
title: t("workflow.props.last_run_at"),
|
||||
render: (_, record) => {
|
||||
if (record.lastRunId) {
|
||||
if (record.lastRunStatus === WORKFLOW_RUN_STATUSES.SUCCEEDED) {
|
||||
return (
|
||||
<Space className="max-w-full" direction="vertical" size={4}>
|
||||
<Badge status="success" count={<CheckCircleOutlinedIcon style={{ color: themeToken.colorSuccess }} />} />
|
||||
<Typography.Text type="secondary">{dayjs(record.lastRunTime!).format("YYYY-MM-DD HH:mm:ss")}</Typography.Text>
|
||||
</Space>
|
||||
);
|
||||
} else if (record.lastRunStatus === WORKFLOW_RUN_STATUSES.FAILED) {
|
||||
return (
|
||||
<Space>
|
||||
<Badge status="error" count={<CloseCircleOutlinedIcon style={{ color: themeToken.colorError }} />} />
|
||||
<Typography.Text>{dayjs(record.lastRunTime!).format("YYYY-MM-DD HH:mm:ss")}</Typography.Text>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return <></>;
|
||||
},
|
||||
},
|
||||
{
|
||||
|
Reference in New Issue
Block a user