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 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, FolderCheck as FolderCheckIcon, SquareSigma as SquareSigmaIcon, Workflow as WorkflowIcon, } from "lucide-react"; 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_RUN_STATUSES, type WorkflowRunModel } from "@/domain/workflowRun"; import { list as listWorkflowRuns } from "@/repository/workflowRun"; import { getErrMsg } from "@/utils/error"; const Dashboard = () => { const navigate = useNavigate(); const { t } = useTranslation(); const { token: themeToken } = theme.useToken(); const breakpoints = Grid.useBreakpoint(); const [notificationApi, NotificationContextHolder] = notification.useNotification(); const statisticsGridSpans = { xs: { flex: "50%" }, md: { flex: "50%" }, lg: { flex: "33.3333%" }, xl: { flex: "33.3333%" }, xxl: { flex: "20%" }, }; const [statistics, setStatistics] = useState(); const { loading: statisticsLoading } = useRequest( () => { return getStatistics(); }, { onSuccess: (res) => { setStatistics(res.data); }, onError: (err) => { if (err instanceof ClientResponseError && err.isAbort) { return; } console.error(err); notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); }, } ); const tableColumns: TableProps["columns"] = [ { key: "$index", align: "center", fixed: "left", width: 50, render: (_, __, index) => 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: "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", width: 120, render: (_, record) => ( } variant="text" />} /> ), }, ]; const [tableData, setTableData] = useState([]); const { loading: tableLoading } = useRequest( () => { return listWorkflowRuns({ page: 1, perPage: 5, expand: true, }); }, { onSuccess: (res) => { setTableData(res.items); }, onError: (err) => { if (err instanceof ClientResponseError && err.isAbort) { return; } console.error(err); notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); }, } ); return (
{NotificationContextHolder} } label={t("dashboard.statistics.all_certificates")} loading={statisticsLoading} value={statistics?.certificateTotal ?? "-"} suffix={t("dashboard.statistics.unit")} onClick={() => navigate("/certificates")} /> } label={t("dashboard.statistics.expire_soon_certificates")} loading={statisticsLoading} value={statistics?.certificateExpireSoon ?? "-"} suffix={t("dashboard.statistics.unit")} onClick={() => navigate("/certificates?state=expireSoon")} /> } label={t("dashboard.statistics.expired_certificates")} loading={statisticsLoading} value={statistics?.certificateExpired ?? "-"} suffix={t("dashboard.statistics.unit")} onClick={() => navigate("/certificates?state=expired")} /> } label={t("dashboard.statistics.all_workflows")} loading={statisticsLoading} value={statistics?.workflowTotal ?? "-"} suffix={t("dashboard.statistics.unit")} onClick={() => navigate("/workflows")} /> } label={t("dashboard.statistics.enabled_workflows")} loading={statisticsLoading} value={statistics?.workflowEnabled ?? "-"} suffix={t("dashboard.statistics.unit")} onClick={() => navigate("/workflows?state=enabled")} /> columns={tableColumns} dataSource={tableData} loading={tableLoading} locale={{ emptyText: , }} pagination={false} rowKey={(record: WorkflowRunModel) => record.id} scroll={{ x: "max(100%, 960px)" }} />
); }; const StatisticCard = ({ label, loading, icon, value, suffix, onClick, }: { label: React.ReactNode; loading?: boolean; icon: React.ReactNode; value?: string | number | React.ReactNode; suffix?: React.ReactNode; onClick?: () => void; }) => { return ( {icon} { return {value}; }} suffix={{suffix}} /> ); }; export default Dashboard;