From 86761bd3a0bde73cbf562197682600cbfa65b3e3 Mon Sep 17 00:00:00 2001 From: yoan <536464346@qq.com> Date: Fri, 22 Nov 2024 10:59:57 +0800 Subject: [PATCH] Certificate displaying and monitoring --- internal/domain/statistics.go | 11 + internal/notify/expire.go | 6 +- internal/repository/statistics.go | 63 +++++ internal/rest/statistics.go | 35 +++ internal/routes/routes.go | 6 + internal/statistics/service.go | 25 ++ ui/src/api/statistics.ts | 15 ++ ui/src/api/workflow.ts | 21 ++ .../certificate/CertificateList.tsx | 162 ++++++++++++ ui/src/components/workflow/DataTable.tsx | 43 ++-- ui/src/domain/domain.ts | 11 +- ui/src/lib/time.ts | 1 - ui/src/pages/DashboardLayout.tsx | 41 +-- ui/src/pages/certificate/index.tsx | 132 +--------- ui/src/pages/dashboard/Dashboard.tsx | 243 +++++------------- ui/src/pages/workflow/WorkflowDetail.tsx | 35 ++- ui/src/pages/workflow/index.tsx | 2 +- ui/src/repository/domains.ts | 26 +- ui/src/router.tsx | 1 - 19 files changed, 498 insertions(+), 381 deletions(-) create mode 100644 internal/domain/statistics.go create mode 100644 internal/repository/statistics.go create mode 100644 internal/rest/statistics.go create mode 100644 internal/statistics/service.go create mode 100644 ui/src/api/statistics.ts create mode 100644 ui/src/api/workflow.ts create mode 100644 ui/src/components/certificate/CertificateList.tsx diff --git a/internal/domain/statistics.go b/internal/domain/statistics.go new file mode 100644 index 00000000..1d3cbfdd --- /dev/null +++ b/internal/domain/statistics.go @@ -0,0 +1,11 @@ +package domain + +type Statistics struct { + CertificateTotal int `json:"certificateTotal"` + CertificateExpireSoon int `json:"certificateExpireSoon"` + CertificateExpired int `json:"certificateExpired"` + + WorkflowTotal int `json:"workflowTotal"` + WorkflowEnabled int `json:"workflowEnabled"` + WorkflowDisabled int `json:"workflowDisabled"` +} diff --git a/internal/notify/expire.go b/internal/notify/expire.go index 5dc424ce..0f5251c9 100644 --- a/internal/notify/expire.go +++ b/internal/notify/expire.go @@ -19,8 +19,8 @@ const ( func PushExpireMsg() { // 查询即将过期的证书 - records, err := app.GetApp().Dao().FindRecordsByFilter("domains", "expiredAt<{:time}&&certUrl!=''", "-created", 500, 0, - dbx.Params{"time": xtime.GetTimeAfter(24 * time.Hour * 15)}) + records, err := app.GetApp().Dao().FindRecordsByFilter("certificate", "expireAt<{:time}&&certUrl!=''", "-created", 500, 0, + dbx.Params{"time": xtime.GetTimeAfter(24 * time.Hour * 20)}) if err != nil { app.GetApp().Logger().Error("find expired domains by filter", "error", err) return @@ -76,7 +76,7 @@ func buildMsg(records []*models.Record) *notifyMessage { domains := make([]string, count) for i, record := range records { - domains[i] = record.GetString("domain") + domains[i] = record.GetString("san") } countStr := strconv.Itoa(count) diff --git a/internal/repository/statistics.go b/internal/repository/statistics.go new file mode 100644 index 00000000..92627b36 --- /dev/null +++ b/internal/repository/statistics.go @@ -0,0 +1,63 @@ +package repository + +import ( + "context" + + "github.com/usual2970/certimate/internal/domain" + "github.com/usual2970/certimate/internal/utils/app" +) + +type StatisticsRepository struct{} + +func NewStatisticsRepository() *StatisticsRepository { + return &StatisticsRepository{} +} + +type totalResp struct { + Total int `json:"total" db:"total"` +} + +func (r *StatisticsRepository) Get(ctx context.Context) (*domain.Statistics, error) { + rs := &domain.Statistics{} + // 所有证书 + certTotal := totalResp{} + if err := app.GetApp().Dao().DB().NewQuery("select count(*) as total from certificate").One(&certTotal); err != nil { + return nil, err + } + rs.CertificateTotal = certTotal.Total + + // 即将过期证书 + certExpireSoonTotal := totalResp{} + if err := app.GetApp().Dao().DB(). + NewQuery("select count(*) as total from certificate where expireAt > datetime('now') and expireAt < datetime('now', '+20 days')"). + One(&certExpireSoonTotal); err != nil { + return nil, err + } + rs.CertificateExpireSoon = certExpireSoonTotal.Total + + // 已过期证书 + certExpiredTotal := totalResp{} + if err := app.GetApp().Dao().DB(). + NewQuery("select count(*) as total from certificate where expireAt < datetime('now')"). + One(&certExpiredTotal); err != nil { + return nil, err + } + rs.CertificateExpired = certExpiredTotal.Total + + // 所有工作流 + workflowTotal := totalResp{} + if err := app.GetApp().Dao().DB().NewQuery("select count(*) as total from workflow").One(&workflowTotal); err != nil { + return nil, err + } + rs.WorkflowTotal = workflowTotal.Total + + // 已启用工作流 + workflowEnabledTotal := totalResp{} + if err := app.GetApp().Dao().DB().NewQuery("select count(*) as total from workflow where enabled is TRUE").One(&workflowEnabledTotal); err != nil { + return nil, err + } + rs.WorkflowEnabled = workflowEnabledTotal.Total + rs.WorkflowDisabled = workflowTotal.Total - workflowEnabledTotal.Total + + return rs, nil +} diff --git a/internal/rest/statistics.go b/internal/rest/statistics.go new file mode 100644 index 00000000..a26f1d36 --- /dev/null +++ b/internal/rest/statistics.go @@ -0,0 +1,35 @@ +package rest + +import ( + "context" + + "github.com/labstack/echo/v5" + "github.com/usual2970/certimate/internal/domain" + "github.com/usual2970/certimate/internal/utils/resp" +) + +type StatisticsService interface { + Get(ctx context.Context) (*domain.Statistics, error) +} + +type statisticsHandler struct { + service StatisticsService +} + +func NewStatisticsHandler(route *echo.Group, service StatisticsService) { + handler := &statisticsHandler{ + service: service, + } + + group := route.Group("/statistics") + + group.GET("/get", handler.get) +} + +func (handler *statisticsHandler) get(c echo.Context) error { + if statistics, err := handler.service.Get(c.Request().Context()); err != nil { + return resp.Err(c, err) + } else { + return resp.Succ(c, statistics) + } +} diff --git a/internal/routes/routes.go b/internal/routes/routes.go index 72985fef..4e0e51b7 100644 --- a/internal/routes/routes.go +++ b/internal/routes/routes.go @@ -4,6 +4,7 @@ import ( "github.com/usual2970/certimate/internal/notify" "github.com/usual2970/certimate/internal/repository" "github.com/usual2970/certimate/internal/rest" + "github.com/usual2970/certimate/internal/statistics" "github.com/usual2970/certimate/internal/workflow" "github.com/labstack/echo/v5" @@ -19,7 +20,12 @@ func Register(e *echo.Echo) { workflowRepo := repository.NewWorkflowRepository() workflowSvc := workflow.NewWorkflowService(workflowRepo) + statisticsRepo := repository.NewStatisticsRepository() + statisticsSvc := statistics.NewStatisticsService(statisticsRepo) + rest.NewWorkflowHandler(group, workflowSvc) rest.NewNotifyHandler(group, notifySvc) + + rest.NewStatisticsHandler(group, statisticsSvc) } diff --git a/internal/statistics/service.go b/internal/statistics/service.go new file mode 100644 index 00000000..18b58e85 --- /dev/null +++ b/internal/statistics/service.go @@ -0,0 +1,25 @@ +package statistics + +import ( + "context" + + "github.com/usual2970/certimate/internal/domain" +) + +type StatisticsRepository interface { + Get(ctx context.Context) (*domain.Statistics, error) +} + +type StatisticsService struct { + repo StatisticsRepository +} + +func NewStatisticsService(repo StatisticsRepository) *StatisticsService { + return &StatisticsService{ + repo: repo, + } +} + +func (s *StatisticsService) Get(ctx context.Context) (*domain.Statistics, error) { + return s.repo.Get(ctx) +} diff --git a/ui/src/api/statistics.ts b/ui/src/api/statistics.ts new file mode 100644 index 00000000..11b84d27 --- /dev/null +++ b/ui/src/api/statistics.ts @@ -0,0 +1,15 @@ +import { getPb } from "@/repository/api"; + +export const get = async () => { + const pb = getPb(); + + const resp = await pb.send("/api/statistics/get", { + method: "GET", + }); + + if (resp.code != 0) { + throw new Error(resp.msg); + } + + return resp.data; +}; diff --git a/ui/src/api/workflow.ts b/ui/src/api/workflow.ts new file mode 100644 index 00000000..f639e9b9 --- /dev/null +++ b/ui/src/api/workflow.ts @@ -0,0 +1,21 @@ +import { getPb } from "@/repository/api"; + +export const run = async (id: string) => { + const pb = getPb(); + + const resp = await pb.send("/api/workflow/run", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: { + id, + }, + }); + + if (resp.code != 0) { + throw new Error(resp.msg); + } + + return resp; +}; diff --git a/ui/src/components/certificate/CertificateList.tsx b/ui/src/components/certificate/CertificateList.tsx new file mode 100644 index 00000000..2aed204e --- /dev/null +++ b/ui/src/components/certificate/CertificateList.tsx @@ -0,0 +1,162 @@ +import CertificateDetail from "@/components/certificate/CertificateDetail"; +import { Button } from "@/components/ui/button"; +import { DataTable } from "@/components/workflow/DataTable"; +import { Certificate as CertificateType } from "@/domain/certificate"; +import { diffDays, getLeftDays } from "@/lib/time"; +import { list } from "@/repository/certificate"; +import { ColumnDef } from "@tanstack/react-table"; +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; + +type CertificateListProps = { + withPagination?: boolean; +}; + +const CertificateList = ({ withPagination }: CertificateListProps) => { + const [data, setData] = useState([]); + const [pageCount, setPageCount] = useState(0); + const [open, setOpen] = useState(false); + const [selectedCertificate, setSelectedCertificate] = useState(); + + const fetchData = async (page: number, pageSize?: number) => { + const resp = await list({ page: page, perPage: pageSize }); + setData(resp.items); + setPageCount(resp.totalPages); + }; + + const navigate = useNavigate(); + + const columns: ColumnDef[] = [ + { + accessorKey: "san", + header: "域名", + cell: ({ row }) => { + let san: string = row.getValue("san"); + if (!san) { + san = ""; + } + + return ( +
+ {san.split(";").map((item, i) => { + return ( +
+ {item} +
+ ); + })} +
+ ); + }, + }, + { + accessorKey: "expireAt", + header: "有效期限", + cell: ({ row }) => { + const expireAt: string = row.getValue("expireAt"); + const data = row.original; + const leftDays = getLeftDays(expireAt); + const allDays = diffDays(data.expireAt, data.created); + return ( +
+ {leftDays > 0 ? ( +
+ {leftDays} / {allDays} 天 +
+ ) : ( +
已到期
+ )} + +
{new Date(expireAt).toLocaleString().split(" ")[0]} 到期
+
+ ); + }, + }, + { + accessorKey: "workflow", + header: "所属工作流", + cell: ({ row }) => { + const name = row.original.expand.workflow?.name; + const workflowId: string = row.getValue("workflow"); + return ( +
+ +
+ ); + }, + }, + { + accessorKey: "created", + header: "颁发时间", + cell: ({ row }) => { + const date: string = row.getValue("created"); + return new Date(date).toLocaleString(); + }, + }, + { + id: "actions", + cell: ({ row }) => { + return ( +
+ +
+ ); + }, + }, + ]; + + const handleWorkflowClick = (id: string) => { + navigate(`/workflow/detail?id=${id}`); + }; + + const handleView = (id: string) => { + setOpen(true); + const certificate = data.find((item) => item.id === id); + setSelectedCertificate(certificate); + }; + + return ( + <> + +
暂无证书,添加工作流去生成证书吧😀
+ + + } + /> + + + + ); +}; + +export default CertificateList; diff --git a/ui/src/components/workflow/DataTable.tsx b/ui/src/components/workflow/DataTable.tsx index 4fdfe233..c7ab3980 100644 --- a/ui/src/components/workflow/DataTable.tsx +++ b/ui/src/components/workflow/DataTable.tsx @@ -3,6 +3,7 @@ import { ColumnDef, flexRender, getCoreRowModel, getPaginationRowModel, Paginati import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Button } from "../ui/button"; import { useEffect, useState } from "react"; +import Show from "../Show"; interface DataTableProps { columns: ColumnDef[]; @@ -10,9 +11,19 @@ interface DataTableProps { pageCount: number; onPageChange?: (pageIndex: number, pageSize?: number) => Promise; onRowClick?: (id: string) => void; + withPagination?: boolean; + fallback?: React.ReactNode; } -export function DataTable({ columns, data, onPageChange, pageCount, onRowClick }: DataTableProps) { +export function DataTable({ + columns, + data, + onPageChange, + pageCount, + onRowClick, + withPagination, + fallback, +}: DataTableProps) { const [{ pageIndex, pageSize }, setPagination] = useState({ pageIndex: 0, pageSize: 10, @@ -77,28 +88,30 @@ export function DataTable({ columns, data, ) : ( - No results. + {fallback ? fallback : "暂无数据"} )} -
-
- {table.getCanPreviousPage() && ( - - )} + +
+
+ {table.getCanPreviousPage() && ( + + )} - {table.getCanNextPage && ( - - )} + {table.getCanNextPage && ( + + )} +
-
+
); } diff --git a/ui/src/domain/domain.ts b/ui/src/domain/domain.ts index 63f6195b..3d6129be 100644 --- a/ui/src/domain/domain.ts +++ b/ui/src/domain/domain.ts @@ -57,10 +57,13 @@ export type ApplyConfig = { }; export type Statistic = { - total: number; - expired: number; - enabled: number; - disabled: number; + certificateTotal: number; + certificateExpired: number; + certificateExpireSoon: number; + + workflowTotal: number; + workflowEnabled: number; + workflowDisabled: number; }; export type DeployTarget = { diff --git a/ui/src/lib/time.ts b/ui/src/lib/time.ts index 651c0b6d..c86ce1e2 100644 --- a/ui/src/lib/time.ts +++ b/ui/src/lib/time.ts @@ -84,4 +84,3 @@ export function getTimeAfter(days: number): string { return formattedDate; } - diff --git a/ui/src/pages/DashboardLayout.tsx b/ui/src/pages/DashboardLayout.tsx index 269b04a7..c63c3e3c 100644 --- a/ui/src/pages/DashboardLayout.tsx +++ b/ui/src/pages/DashboardLayout.tsx @@ -57,19 +57,23 @@ export default function Dashboard() { {t("dashboard.page.title")} - + - {t("domain.page.title")} + 工作流列表 + + + + 证书列表 + + {t("access.page.title")} - - - - {t("history.page.title")} - @@ -99,12 +103,21 @@ export default function Dashboard() { {t("dashboard.page.title")} - {t("domain.page.title")} + 工作流 + + + + 证书 + + {t("access.page.title")} - - - - {t("history.page.title")} -
diff --git a/ui/src/pages/certificate/index.tsx b/ui/src/pages/certificate/index.tsx index daa633cd..f3853a2f 100644 --- a/ui/src/pages/certificate/index.tsx +++ b/ui/src/pages/certificate/index.tsx @@ -1,139 +1,11 @@ -import CertificateDetail from "@/components/certificate/CertificateDetail"; -import { Button } from "@/components/ui/button"; -import { DataTable } from "@/components/workflow/DataTable"; -import { Certificate as CertificateType } from "@/domain/certificate"; -import { diffDays, getLeftDays } from "@/lib/time"; -import { list } from "@/repository/certificate"; -import { ColumnDef } from "@tanstack/react-table"; -import { useState } from "react"; -import { useNavigate } from "react-router-dom"; +import CertificateList from "@/components/certificate/CertificateList"; const Certificate = () => { - const [data, setData] = useState([]); - const [pageCount, setPageCount] = useState(0); - const [open, setOpen] = useState(false); - const [selectedCertificate, setSelectedCertificate] = useState(); - - const fetchData = async (page: number, pageSize?: number) => { - const resp = await list({ page: page, perPage: pageSize }); - setData(resp.items); - setPageCount(resp.totalPages); - }; - - const navigate = useNavigate(); - - const columns: ColumnDef[] = [ - { - accessorKey: "san", - header: "域名", - cell: ({ row }) => { - let san: string = row.getValue("san"); - if (!san) { - san = ""; - } - - return ( -
- {san.split(";").map((item, i) => { - return ( -
- {item} -
- ); - })} -
- ); - }, - }, - { - accessorKey: "expireAt", - header: "有效期限", - cell: ({ row }) => { - const expireAt: string = row.getValue("expireAt"); - const data = row.original; - const leftDays = getLeftDays(expireAt); - const allDays = diffDays(data.expireAt, data.created); - return ( -
- {leftDays > 0 ? ( -
- {leftDays} / {allDays} 天 -
- ) : ( -
已到期
- )} - -
{new Date(expireAt).toLocaleString().split(" ")[0]} 到期
-
- ); - }, - }, - { - accessorKey: "workflow", - header: "所属工作流", - cell: ({ row }) => { - const name = row.original.expand.workflow?.name; - const workflowId: string = row.getValue("workflow"); - return ( -
- -
- ); - }, - }, - { - accessorKey: "created", - header: "颁发时间", - cell: ({ row }) => { - const date: string = row.getValue("created"); - return new Date(date).toLocaleString(); - }, - }, - { - id: "actions", - cell: ({ row }) => { - return ( -
- -
- ); - }, - }, - ]; - - const handleWorkflowClick = (id: string) => { - navigate(`/workflow/detail?id=${id}`); - }; - - const handleView = (id: string) => { - setOpen(true); - const certificate = data.find((item) => item.id === id); - setSelectedCertificate(certificate); - }; - return (
证书
- - - +
); }; diff --git a/ui/src/pages/dashboard/Dashboard.tsx b/ui/src/pages/dashboard/Dashboard.tsx index 7de1d16a..a5ff2111 100644 --- a/ui/src/pages/dashboard/Dashboard.tsx +++ b/ui/src/pages/dashboard/Dashboard.tsx @@ -1,47 +1,28 @@ import { useEffect, useState } from "react"; -import { Link, useNavigate } from "react-router-dom"; +import { Link } from "react-router-dom"; import { useTranslation } from "react-i18next"; -import { Ban, CalendarX2, LoaderPinwheel, Smile, SquareSigma } from "lucide-react"; +import { CalendarClock, CalendarX2, FolderCheck, SquareSigma, Workflow } from "lucide-react"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; -import { Button } from "@/components/ui/button"; -import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"; -import DeployProgress from "@/components/certimate/DeployProgress"; -import DeployState from "@/components/certimate/DeployState"; -import { convertZulu2Beijing } from "@/lib/time"; import { Statistic } from "@/domain/domain"; -import { Deployment, DeploymentListReq, Log } from "@/domain/deployment"; -import { statistics } from "@/repository/domains"; -import { list } from "@/repository/deployment"; + +import { get } from "@/api/statistics"; + +import CertificateList from "@/components/certificate/CertificateList"; const Dashboard = () => { const [statistic, setStatistic] = useState(); - const [deployments, setDeployments] = useState(); - const navigate = useNavigate(); const { t } = useTranslation(); useEffect(() => { const fetchStatistic = async () => { - const data = await statistics(); + const data = await get(); setStatistic(data); }; fetchStatistic(); }, []); - useEffect(() => { - const fetchData = async () => { - const param: DeploymentListReq = { - perPage: 8, - }; - - const data = await list(param); - setDeployments(data.items); - }; - fetchData(); - }, []); - return (
@@ -53,12 +34,12 @@ const Dashboard = () => {
-
{t("dashboard.statistics.all")}
+
全部证书
- {statistic?.total ? ( + {statistic?.certificateTotal ? ( - {statistic?.total} + {statistic?.certificateTotal} ) : ( 0 @@ -70,37 +51,37 @@ const Dashboard = () => {
+
+ +
+
+
即将过期证书
+
+
+ {statistic?.certificateExpireSoon ? ( + + {statistic?.certificateExpireSoon} + + ) : ( + 0 + )} +
+
{t("dashboard.statistics.unit")}
+
+
+
+ +
-
{t("dashboard.statistics.near_expired")}
+
已过期证书
- {statistic?.expired ? ( - - {statistic?.expired} - - ) : ( - 0 - )} -
-
{t("dashboard.statistics.unit")}
-
-
-
- -
-
- -
-
-
{t("dashboard.statistics.enabled")}
-
-
- {statistic?.enabled ? ( + {statistic?.certificateExpired ? ( - {statistic?.enabled} + {statistic?.certificateExpired} ) : ( 0 @@ -113,15 +94,36 @@ const Dashboard = () => {
- +
-
{t("dashboard.statistics.disabled")}
+
全部工作流
- {statistic?.disabled ? ( + {statistic?.workflowTotal ? ( - {statistic?.disabled} + {statistic?.workflowTotal} + + ) : ( + 0 + )} +
+
{t("dashboard.statistics.unit")}
+
+
+
+ +
+
+ +
+
+
已启用工作流
+
+
+ {statistic?.workflowEnabled ? ( + + {statistic?.workflowEnabled} ) : ( 0 @@ -138,132 +140,9 @@ const Dashboard = () => {
-
{t("dashboard.history")}
+
最新证书
- {deployments?.length == 0 ? ( - <> - - {t("common.text.nodata")} - -
-
- -
-
{t("history.nodata")}
-
-
- -
-
-
- - ) : ( - <> -
-
{t("history.props.domain")}
- -
{t("history.props.status")}
-
{t("history.props.stage")}
-
{t("history.props.last_execution_time")}
- -
{t("common.text.operations")}
-
- - {deployments?.map((deployment) => ( -
-
- {deployment.expand.domain?.domain.split(";").map((domain: string) =>
{domain}
)} -
-
- -
-
- -
-
{convertZulu2Beijing(deployment.deployedAt)}
-
- - - - - - - - {deployment.expand.domain?.domain}-{deployment.id} - {t("history.log")} - - -
- {deployment.log.check && ( - <> - {deployment.log.check.map((item: Log) => { - return ( -
-
-
[{item.time}]
-
{item.message}
-
- {item.error &&
{item.error}
} -
- ); - })} - - )} - - {deployment.log.apply && ( - <> - {deployment.log.apply.map((item: Log) => { - return ( -
-
-
[{item.time}]
-
{item.message}
-
- {item.info && - item.info.map((info: string) => { - return
{info}
; - })} - {item.error &&
{item.error}
} -
- ); - })} - - )} - - {deployment.log.deploy && ( - <> - {deployment.log.deploy.map((item: Log) => { - return ( -
-
-
[{item.time}]
-
{item.message}
-
- {item.error &&
{item.error}
} -
- ); - })} - - )} -
-
-
-
-
- ))} - - )} +
); diff --git a/ui/src/pages/workflow/WorkflowDetail.tsx b/ui/src/pages/workflow/WorkflowDetail.tsx index f66cf81b..299cb2e0 100644 --- a/ui/src/pages/workflow/WorkflowDetail.tsx +++ b/ui/src/pages/workflow/WorkflowDetail.tsx @@ -1,3 +1,4 @@ +import { run } from "@/api/workflow"; import Show from "@/components/Show"; import { Button } from "@/components/ui/button"; import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; @@ -11,6 +12,7 @@ import WorkflowLog from "@/components/workflow/WorkflowLog"; import WorkflowProvider from "@/components/workflow/WorkflowProvider"; import { allNodesValidated, WorkflowNode } from "@/domain/workflow"; +import { getErrMessage } from "@/lib/error"; import { cn } from "@/lib/utils"; import { useWorkflowStore, WorkflowState } from "@/providers/workflow"; import { ArrowLeft } from "lucide-react"; @@ -37,6 +39,8 @@ const WorkflowDetail = () => { const [tab, setTab] = useState("workflow"); + const [running, setRunning] = useState(false); + useEffect(() => { console.log(id); init(id ?? ""); @@ -101,6 +105,28 @@ const WorkflowDetail = () => { return "border-transparent hover:text-primary hover:border-b-primary"; }; + const handleRunClick = async () => { + if (running) { + return; + } + setRunning(true); + try { + await run(workflow.id as string); + toast({ + title: "执行成功", + description: "工作流已成功执行", + variant: "default", + }); + } catch (e) { + toast({ + title: "执行失败", + description: getErrMessage(e), + variant: "destructive", + }); + } + setRunning(false); + }; + return ( <> @@ -140,7 +166,14 @@ const WorkflowDetail = () => {
- 立即执行}> + + {running ? "执行中" : "立即执行"} + + } + > diff --git a/ui/src/pages/workflow/index.tsx b/ui/src/pages/workflow/index.tsx index ca928870..79a2e4f0 100644 --- a/ui/src/pages/workflow/index.tsx +++ b/ui/src/pages/workflow/index.tsx @@ -200,7 +200,7 @@ const Workflow = () => {
- +
{ return response; }; -export const statistics = async (): Promise => { - const pb = getPb(); - const total = await pb.collection("domains").getList(1, 1, {}); - const expired = await pb.collection("domains").getList(1, 1, { - filter: pb.filter("expiredAt<{:expiredAt}", { - expiredAt: getTimeAfter(15), - }), - }); - - const enabled = await pb.collection("domains").getList(1, 1, { - filter: "enabled=true", - }); - const disabled = await pb.collection("domains").getList(1, 1, { - filter: "enabled=false", - }); - - return { - total: total.totalItems, - expired: expired.totalItems, - enabled: enabled.totalItems, - disabled: disabled.totalItems, - }; -}; - export const get = async (id: string) => { const response = await getPb().collection("domains").getOne(id); return response; diff --git a/ui/src/router.tsx b/ui/src/router.tsx index a78e2c06..75fd106d 100644 --- a/ui/src/router.tsx +++ b/ui/src/router.tsx @@ -89,4 +89,3 @@ export const router = createHashRouter([ element: , }, ]); -