mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-08 13:39:53 +00:00
refine the dashboard
This commit is contained in:
parent
9839e2bb60
commit
503d9c34f8
@ -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 = {
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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": "证书颁发机构配置"
|
||||
}
|
||||
|
||||
|
@ -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<WorkflowRunModel>["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) => (
|
||||
<Space className="max-w-full" direction="vertical" size={4}>
|
||||
<Typography.Text ellipsis>{record.expand?.workflowId?.name}</Typography.Text>
|
||||
<Typography.Text type="secondary" ellipsis>
|
||||
{record.expand?.workflowId?.description}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "status",
|
||||
title: t("workflow_run.props.status"),
|
||||
ellipsis: true,
|
||||
render: (_, record) => {
|
||||
if (record.status === WORKFLOW_RUN_STATUSES.PENDING) {
|
||||
return <Tag icon={<ClockCircleOutlined />}>{t("workflow_run.props.status.pending")}</Tag>;
|
||||
} else if (record.status === WORKFLOW_RUN_STATUSES.RUNNING) {
|
||||
return (
|
||||
<Tag icon={<SyncOutlined spin />} color="processing">
|
||||
{t("workflow_run.props.status.running")}
|
||||
</Tag>
|
||||
);
|
||||
} else if (record.status === WORKFLOW_RUN_STATUSES.SUCCEEDED) {
|
||||
return (
|
||||
<Tag icon={<CheckCircleOutlined />} color="success">
|
||||
{t("workflow_run.props.status.succeeded")}
|
||||
</Tag>
|
||||
);
|
||||
} else if (record.status === WORKFLOW_RUN_STATUSES.FAILED) {
|
||||
return (
|
||||
<Tag icon={<CloseCircleOutlined />} color="error">
|
||||
{t("workflow_run.props.status.failed")}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
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) => (
|
||||
<Button.Group>
|
||||
<WorkflowRunDetailDrawer data={record} trigger={<Button color="primary" icon={<SelectOutlined />} variant="text" />} />
|
||||
</Button.Group>
|
||||
),
|
||||
},
|
||||
];
|
||||
const [tableData, setTableData] = useState<WorkflowRunModel[]>([]);
|
||||
const [_tableTotal, setTableTotal] = useState<number>(0);
|
||||
|
||||
const [page, _setPage] = useState<number>(1);
|
||||
const [pageSize, _setPageSize] = useState<number>(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 = () => {
|
||||
|
||||
<Divider />
|
||||
|
||||
<div>TODO: {t("dashboard.latest_workflow_run")}</div>
|
||||
<Flex vertical={!screens.md} gap={16}>
|
||||
<Card className="sm:h-full sm:w-[500px] sm:pb-32">
|
||||
<div className="text-lg font-semibold">{t("dashboard.quick_actions")}</div>
|
||||
<div className="mt-9">
|
||||
<Button className="w-full" type="primary" size="large" icon={<PlusOutlined />} onClick={() => navigate("/workflows/new")}>
|
||||
{t("dashboard.quick_actions.create_workflow")}
|
||||
</Button>
|
||||
<Button className="mt-5 w-full" size="large" icon={<LockOutlined />} onClick={() => navigate("/settings/password")}>
|
||||
{t("dashboard.quick_actions.change_login_password")}
|
||||
</Button>
|
||||
<Button className="mt-5 w-full" size="large" icon={<SendOutlined />} onClick={() => navigate("/settings/notification")}>
|
||||
{t("dashboard.quick_actions.notification_settings")}
|
||||
</Button>
|
||||
<Button className="mt-5 w-full" size="large" icon={<ApiOutlined />} onClick={() => navigate("/settings/ssl-provider")}>
|
||||
{t("dashboard.quick_actions.certificate_authority_configuration")}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
<Card className="size-full">
|
||||
<div className="text-lg font-semibold">{t("dashboard.latest_workflow_run")} </div>
|
||||
<Table<WorkflowRunModel>
|
||||
className="mt-5"
|
||||
columns={tableColumns}
|
||||
dataSource={tableData}
|
||||
loading={loadingWorkflowRun}
|
||||
locale={{
|
||||
emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />,
|
||||
}}
|
||||
rowKey={(record: WorkflowRunModel) => record.id}
|
||||
scroll={{ x: "max(100%, 960px)" }}
|
||||
/>
|
||||
</Card>
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -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<string, string> = {};
|
||||
if (request.workflowId) {
|
||||
filter = `workflowId={:workflowId}`;
|
||||
params.workflowId = request.workflowId;
|
||||
}
|
||||
|
||||
return await getPocketBase()
|
||||
.collection(COLLECTION_NAME)
|
||||
.getList<WorkflowRunModel>(page, perPage, {
|
||||
filter: getPocketBase().filter("workflowId={:workflowId}", { workflowId: request.workflowId }),
|
||||
filter: getPocketBase().filter(filter, params),
|
||||
sort: "-created",
|
||||
requestKey: null,
|
||||
expand: request.expand ? "workflowId" : undefined,
|
||||
});
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user