diff --git a/ui/src/domain/workflowRun.ts b/ui/src/domain/workflowRun.ts index 36920de2..95b44546 100644 --- a/ui/src/domain/workflowRun.ts +++ b/ui/src/domain/workflowRun.ts @@ -1,3 +1,5 @@ +import type { WorkflowModel } from "./workflow"; + export interface WorkflowRunModel extends BaseModel { workflowId: string; status: string; @@ -6,6 +8,9 @@ export interface WorkflowRunModel extends BaseModel { endedAt: ISO8601String; logs: WorkflowRunLog[]; error: string; + expand?: { + workflowId?: WorkflowModel; + }; } export type WorkflowRunLog = { diff --git a/ui/src/i18n/locales/en/nls.dashboard.json b/ui/src/i18n/locales/en/nls.dashboard.json index 865e11ec..e179f20f 100644 --- a/ui/src/i18n/locales/en/nls.dashboard.json +++ b/ui/src/i18n/locales/en/nls.dashboard.json @@ -8,5 +8,11 @@ "dashboard.statistics.enabled_workflows": "Enabled workflows", "dashboard.statistics.unit": "", - "dashboard.latest_workflow_run": "Latest workflow run" + "dashboard.latest_workflow_run": "Latest workflow run", + + "dashboard.quick_actions": "Quick actions", + "dashboard.quick_actions.create_workflow": "Create workflow", + "dashboard.quick_actions.change_login_password": "Change login password", + "dashboard.quick_actions.notification_settings": "Notification settings", + "dashboard.quick_actions.certificate_authority_configuration": "Certificate authority configuration" } diff --git a/ui/src/i18n/locales/zh/nls.dashboard.json b/ui/src/i18n/locales/zh/nls.dashboard.json index 72086011..471ebfce 100644 --- a/ui/src/i18n/locales/zh/nls.dashboard.json +++ b/ui/src/i18n/locales/zh/nls.dashboard.json @@ -8,5 +8,12 @@ "dashboard.statistics.enabled_workflows": "已启用工作流", "dashboard.statistics.unit": "个", - "dashboard.latest_workflow_run": "最近执行的工作流" + "dashboard.latest_workflow_run": "最近执行的工作流", + + "dashboard.quick_actions": "快捷操作", + "dashboard.quick_actions.create_workflow": "新建工作流", + "dashboard.quick_actions.change_login_password": "修改登录密码", + "dashboard.quick_actions.notification_settings": "消息推送设置", + "dashboard.quick_actions.certificate_authority_configuration": "证书颁发机构配置" } + diff --git a/ui/src/pages/dashboard/Dashboard.tsx b/ui/src/pages/dashboard/Dashboard.tsx index d5a12e19..82e0acea 100644 --- a/ui/src/pages/dashboard/Dashboard.tsx +++ b/ui/src/pages/dashboard/Dashboard.tsx @@ -1,9 +1,22 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; +import { + ApiOutlined, + CheckCircleOutlined, + ClockCircleOutlined, + CloseCircleOutlined, + LockOutlined, + PlusOutlined, + SelectOutlined, + SendOutlined, + SyncOutlined, +} from "@ant-design/icons"; import { PageHeader } from "@ant-design/pro-components"; import { useRequest } from "ahooks"; -import { Card, Col, Divider, Row, Space, Statistic, Typography, notification, theme } from "antd"; +import type { TableProps } from "antd"; +import { Button, Card, Col, Divider, Empty, Flex, Grid, Row, Space, Statistic, Table, Tag, Typography, notification, theme } from "antd"; +import dayjs from "dayjs"; import { CalendarClock as CalendarClockIcon, CalendarX2 as CalendarX2Icon, @@ -14,18 +27,158 @@ import { import { ClientResponseError } from "pocketbase"; import { get as getStatistics } from "@/api/statistics"; +import WorkflowRunDetailDrawer from "@/components/workflow/WorkflowRunDetailDrawer"; import { type Statistics } from "@/domain/statistics"; +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"; +const { useBreakpoint } = Grid; + const Dashboard = () => { const navigate = useNavigate(); + const screens = useBreakpoint(); + const { t } = useTranslation(); const { token: themeToken } = theme.useToken(); const [notificationApi, NotificationContextHolder] = notification.useNotification(); + const tableColumns: TableProps["columns"] = [ + { + key: "$index", + align: "center", + fixed: "left", + width: 50, + render: (_, __, index) => (page - 1) * pageSize + index + 1, + }, + { + key: "name", + title: t("workflow.props.name"), + ellipsis: true, + render: (_, record) => ( + + {record.expand?.workflowId?.name} + + {record.expand?.workflowId?.description} + + + ), + }, + { + key: "status", + title: t("workflow_run.props.status"), + ellipsis: true, + render: (_, record) => { + if (record.status === WORKFLOW_RUN_STATUSES.PENDING) { + return }>{t("workflow_run.props.status.pending")}; + } else if (record.status === WORKFLOW_RUN_STATUSES.RUNNING) { + return ( + } color="processing"> + {t("workflow_run.props.status.running")} + + ); + } else if (record.status === WORKFLOW_RUN_STATUSES.SUCCEEDED) { + return ( + } color="success"> + {t("workflow_run.props.status.succeeded")} + + ); + } else if (record.status === WORKFLOW_RUN_STATUSES.FAILED) { + return ( + } color="error"> + {t("workflow_run.props.status.failed")} + + ); + } + + return <>; + }, + }, + { + key: "trigger", + title: t("workflow_run.props.trigger"), + ellipsis: true, + 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: (_, record) => { + if (record.startedAt) { + return dayjs(record.startedAt).format("YYYY-MM-DD HH:mm:ss"); + } + + return <>; + }, + }, + { + key: "endedAt", + title: t("workflow_run.props.ended_at"), + ellipsis: true, + render: (_, record) => { + if (record.endedAt) { + return dayjs(record.endedAt).format("YYYY-MM-DD HH:mm:ss"); + } + + return <>; + }, + }, + { + key: "$action", + align: "end", + fixed: "right", + width: 120, + render: (_, record) => ( + + } variant="text" />} /> + + ), + }, + ]; + const [tableData, setTableData] = useState([]); + const [_tableTotal, setTableTotal] = useState(0); + + const [page, _setPage] = useState(1); + const [pageSize, _setPageSize] = useState(3); + + const { loading: loadingWorkflowRun } = useRequest( + () => { + return listWorkflowRuns({ + page: page, + perPage: pageSize, + expand: true, + }); + }, + { + refreshDeps: [page, pageSize], + onSuccess: (data) => { + setTableData(data.items); + setTableTotal(data.totalItems > 3 ? 3 : data.totalItems); + }, + onError: (err) => { + if (err instanceof ClientResponseError && err.isAbort) { + return; + } + + console.error(err); + notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); + }, + } + ); + const statisticsGridSpans = { xs: { flex: "50%" }, md: { flex: "50%" }, @@ -115,7 +268,39 @@ const Dashboard = () => { -
TODO: {t("dashboard.latest_workflow_run")}
+ + +
{t("dashboard.quick_actions")}
+
+ + + + +
+
+ +
{t("dashboard.latest_workflow_run")}
+ + className="mt-5" + columns={tableColumns} + dataSource={tableData} + loading={loadingWorkflowRun} + locale={{ + emptyText: , + }} + rowKey={(record: WorkflowRunModel) => record.id} + scroll={{ x: "max(100%, 960px)" }} + /> +
+
); }; diff --git a/ui/src/repository/workflowRun.ts b/ui/src/repository/workflowRun.ts index 7dac78a6..fd99de98 100644 --- a/ui/src/repository/workflowRun.ts +++ b/ui/src/repository/workflowRun.ts @@ -5,20 +5,29 @@ import { getPocketBase } from "./pocketbase"; const COLLECTION_NAME = "workflow_run"; export type ListWorkflowRunsRequest = { - workflowId: string; + workflowId?: string; page?: number; perPage?: number; + expand?: boolean; }; export const list = async (request: ListWorkflowRunsRequest) => { const page = request.page || 1; const perPage = request.perPage || 10; + console.log("request.workflowId", request.workflowId); + let filter = ""; + const params: Record = {}; + if (request.workflowId) { + filter = `workflowId={:workflowId}`; + params.workflowId = request.workflowId; + } return await getPocketBase() .collection(COLLECTION_NAME) .getList(page, perPage, { - filter: getPocketBase().filter("workflowId={:workflowId}", { workflowId: request.workflowId }), + filter: getPocketBase().filter(filter, params), sort: "-created", requestKey: null, + expand: request.expand ? "workflowId" : undefined, }); };