diff --git a/internal/domain/workflow_run_log.go b/internal/domain/workflow_run_log.go
index fa77a9c9..91a40963 100644
--- a/internal/domain/workflow_run_log.go
+++ b/internal/domain/workflow_run_log.go
@@ -20,3 +20,14 @@ type WorkflowRunLog struct {
Succeed bool `json:"succeed"`
Error string `json:"error"`
}
+
+type RunLogs []RunLog
+
+func (r RunLogs) Error() string {
+ for _, log := range r {
+ if log.Error != "" {
+ return log.Error
+ }
+ }
+ return ""
+}
diff --git a/internal/workflow/node-processor/apply_node.go b/internal/workflow/node-processor/apply_node.go
index 8411f575..44257d4c 100644
--- a/internal/workflow/node-processor/apply_node.go
+++ b/internal/workflow/node-processor/apply_node.go
@@ -2,6 +2,7 @@ package nodeprocessor
import (
"context"
+ "strings"
"time"
"github.com/usual2970/certimate/internal/applicant"
@@ -95,7 +96,7 @@ func (a *applyNode) Run(ctx context.Context) error {
}
certificateRecord := &domain.Certificate{
- SAN: cert.Subject.CommonName,
+ SAN: strings.Join(cert.DNSNames, ";"),
Certificate: certificate.Certificate,
PrivateKey: certificate.PrivateKey,
IssuerCertificate: certificate.IssuerCertificate,
diff --git a/internal/workflow/node-processor/workflow_processor.go b/internal/workflow/node-processor/workflow_processor.go
index 4602254f..96cfc7f4 100644
--- a/internal/workflow/node-processor/workflow_processor.go
+++ b/internal/workflow/node-processor/workflow_processor.go
@@ -33,7 +33,7 @@ func (w *workflowProcessor) runNode(ctx context.Context, node *domain.WorkflowNo
if current.Type == domain.WorkflowNodeTypeBranch {
for _, branch := range current.Branches {
if err := w.runNode(ctx, &branch); err != nil {
- continue
+ return err
}
}
}
diff --git a/internal/workflow/service.go b/internal/workflow/service.go
index 446381d2..629cd841 100644
--- a/internal/workflow/service.go
+++ b/internal/workflow/service.go
@@ -57,11 +57,18 @@ func (s *WorkflowService) Run(ctx context.Context, req *domain.WorkflowRunReq) e
}
// 保存执行日志
-
+ logs := processor.Log(ctx)
+ runLogs := domain.RunLogs(logs)
+ runErr := runLogs.Error()
+ succeed := true
+ if runErr != "" {
+ succeed = false
+ }
log := &domain.WorkflowRunLog{
Workflow: workflow.Id,
Log: processor.Log(ctx),
- Succeed: true,
+ Error: runErr,
+ Succeed: succeed,
}
if err := s.repo.SaveRunLog(ctx, log); err != nil {
app.GetApp().Logger().Error("failed to save run log", "err", err)
diff --git a/ui/src/components/certificate/CertificateDetail.tsx b/ui/src/components/certificate/CertificateDetail.tsx
new file mode 100644
index 00000000..ff270489
--- /dev/null
+++ b/ui/src/components/certificate/CertificateDetail.tsx
@@ -0,0 +1,63 @@
+import { Sheet, SheetContent, SheetHeader, SheetTitle } from "../ui/sheet";
+
+import { Certificate } from "@/domain/certificate";
+import { Textarea } from "../ui/textarea";
+import { Button } from "../ui/button";
+import { Label } from "../ui/label";
+import { CustomFile, saveFiles2ZIP } from "@/lib/file";
+
+type WorkflowLogDetailProps = {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ certificate?: Certificate;
+};
+const CertificateDetail = ({ open, onOpenChange, certificate }: WorkflowLogDetailProps) => {
+ const handleDownloadClick = async () => {
+ const zipName = `${certificate?.id}-${certificate?.san}.zip`;
+ const files: CustomFile[] = [
+ {
+ name: `${certificate?.san}.pem`,
+ content: certificate?.certificate ? certificate?.certificate : "",
+ },
+ {
+ name: `${certificate?.san}.key`,
+ content: certificate?.privateKey ? certificate?.privateKey : "",
+ },
+ ];
+
+ await saveFiles2ZIP(zipName, files);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default CertificateDetail;
diff --git a/ui/src/components/workflow/WorkflowLog.tsx b/ui/src/components/workflow/WorkflowLog.tsx
index c60671bc..526d0e58 100644
--- a/ui/src/components/workflow/WorkflowLog.tsx
+++ b/ui/src/components/workflow/WorkflowLog.tsx
@@ -31,7 +31,7 @@ const WorkflowLog = () => {
const succeed: boolean = row.getValue("succeed");
if (succeed) {
return (
-
+
@@ -40,7 +40,7 @@ const WorkflowLog = () => {
);
} else {
return (
-
+
@@ -58,7 +58,7 @@ const WorkflowLog = () => {
if (!error) {
error = "";
}
- return
{error}
;
+ return
{error}
;
},
},
{
@@ -89,4 +89,3 @@ const WorkflowLog = () => {
};
export default WorkflowLog;
-
diff --git a/ui/src/components/workflow/WorkflowLogDetail.tsx b/ui/src/components/workflow/WorkflowLogDetail.tsx
index 6e805504..1c6d1861 100644
--- a/ui/src/components/workflow/WorkflowLogDetail.tsx
+++ b/ui/src/components/workflow/WorkflowLogDetail.tsx
@@ -1,6 +1,7 @@
import { WorkflowOutput, WorkflowRunLog, WorkflowRunLogItem } from "@/domain/workflow";
-import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "../ui/sheet";
+import { Sheet, SheetContent, SheetHeader, SheetTitle } from "../ui/sheet";
import { Check, X } from "lucide-react";
+import { ScrollArea } from "../ui/scroll-area";
type WorkflowLogDetailProps = {
open: boolean;
@@ -18,7 +19,7 @@ const WorkflowLogDetail = ({ open, onOpenChange, log }: WorkflowLogDetailProps)
{log?.succeed ? (
-
+
@@ -28,49 +29,51 @@ const WorkflowLogDetail = ({ open, onOpenChange, log }: WorkflowLogDetailProps)
{new Date(log.created).toLocaleString()}
) : (
-
-
+
+
-
{log?.error}
+
{log?.error}
{log?.created && new Date(log.created).toLocaleString()}
)}
-
- {log?.log.map((item: WorkflowRunLogItem, i) => {
- return (
-
-
{item.nodeName}
-
- {item.outputs.map((output: WorkflowOutput) => {
- return (
- <>
-
-
[{output.time}]
- {output.error ? (
- <>
-
{output.error}
- >
- ) : (
- <>
-
{output.content}
- >
- )}
-
- >
- );
- })}
+
+
+ {log?.log.map((item: WorkflowRunLogItem, i) => {
+ return (
+
+
{item.nodeName}
+
+ {item.outputs.map((output: WorkflowOutput) => {
+ return (
+ <>
+
+
[{output.time}]
+ {output.error ? (
+ <>
+
{output.error}
+ >
+ ) : (
+ <>
+
{output.content}
+ >
+ )}
+
+ >
+ );
+ })}
+
-
- );
- })}
-
+ );
+ })}
+
+
@@ -78,4 +81,3 @@ const WorkflowLogDetail = ({ open, onOpenChange, log }: WorkflowLogDetailProps)
};
export default WorkflowLogDetail;
-
diff --git a/ui/src/domain/certificate.ts b/ui/src/domain/certificate.ts
new file mode 100644
index 00000000..457efc7a
--- /dev/null
+++ b/ui/src/domain/certificate.ts
@@ -0,0 +1,21 @@
+import { Workflow } from "./workflow";
+
+export type Certificate = {
+ id: string;
+ san: string;
+ certificate: string;
+ privateKey: string;
+ issuerCertificate: string;
+ certUrl: string;
+ certStableUrl: string;
+ output: string;
+ expireAt: string;
+ workflow: string;
+ nodeId: string;
+ created: string;
+ updated: string;
+
+ expand: {
+ workflow?: Workflow;
+ };
+};
diff --git a/ui/src/domain/workflow.ts b/ui/src/domain/workflow.ts
index 892476cc..efb22829 100644
--- a/ui/src/domain/workflow.ts
+++ b/ui/src/domain/workflow.ts
@@ -469,4 +469,3 @@ export const workflowNodeDropdownList: WorkflowwNodeDropdwonItem[] = [
},
},
];
-
diff --git a/ui/src/lib/time.ts b/ui/src/lib/time.ts
index 27bcb552..651c0b6d 100644
--- a/ui/src/lib/time.ts
+++ b/ui/src/lib/time.ts
@@ -31,6 +31,14 @@ export const getLeftDays = (zuluTime: string) => {
return days;
};
+export const diffDays = (date1: string, date2: string) => {
+ const target1 = new Date(date1);
+ const target2 = new Date(date2);
+ const diff = target1.getTime() - target2.getTime();
+ const days = Math.ceil(diff / (1000 * 60 * 60 * 24));
+ return days;
+};
+
export function getTimeBefore(days: number): string {
// 获取当前时间
const currentDate = new Date();
diff --git a/ui/src/pages/certificate/index.tsx b/ui/src/pages/certificate/index.tsx
new file mode 100644
index 00000000..daa633cd
--- /dev/null
+++ b/ui/src/pages/certificate/index.tsx
@@ -0,0 +1,141 @@
+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";
+
+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 (
+
+ );
+};
+
+export default Certificate;
diff --git a/ui/src/pages/workflow/WorkflowDetail.tsx b/ui/src/pages/workflow/WorkflowDetail.tsx
index 8077159d..f66cf81b 100644
--- a/ui/src/pages/workflow/WorkflowDetail.tsx
+++ b/ui/src/pages/workflow/WorkflowDetail.tsx
@@ -66,7 +66,8 @@ const WorkflowDetail = () => {
}, [workflow]);
const handleBackClick = () => {
- navigate("/workflow");
+ // 返回上一步
+ navigate(-1);
};
const handleEnableChange = () => {
diff --git a/ui/src/repository/certificate.ts b/ui/src/repository/certificate.ts
new file mode 100644
index 00000000..e26e638e
--- /dev/null
+++ b/ui/src/repository/certificate.ts
@@ -0,0 +1,28 @@
+import { Certificate } from "@/domain/certificate";
+import { getPb } from "./api";
+
+type CertificateListReq = {
+ page?: number;
+ perPage?: number;
+};
+
+export const list = async (req: CertificateListReq) => {
+ const pb = getPb();
+
+ let page = 1;
+ if (req.page) {
+ page = req.page;
+ }
+
+ let perPage = 2;
+ if (req.perPage) {
+ perPage = req.perPage;
+ }
+
+ const response = pb.collection("certificate").getList(page, perPage, {
+ sort: "-created",
+ expand: "workflow",
+ });
+
+ return response;
+};
diff --git a/ui/src/repository/workflow.ts b/ui/src/repository/workflow.ts
index 24a6a240..c72f55f5 100644
--- a/ui/src/repository/workflow.ts
+++ b/ui/src/repository/workflow.ts
@@ -65,4 +65,3 @@ export const logs = async (req: WorkflowLogsReq) => {
return response;
};
-
diff --git a/ui/src/router.tsx b/ui/src/router.tsx
index dc33cfd6..a78e2c06 100644
--- a/ui/src/router.tsx
+++ b/ui/src/router.tsx
@@ -15,6 +15,7 @@ import Notify from "./pages/setting/Notify";
import SSLProvider from "./pages/setting/SSLProvider";
import Workflow from "./pages/workflow";
import WorkflowDetail from "./pages/workflow/WorkflowDetail";
+import Certificate from "./pages/certificate";
export const router = createHashRouter([
{
@@ -45,6 +46,10 @@ export const router = createHashRouter([
path: "/workflow",
element: ,
},
+ {
+ path: "/certificate",
+ element: ,
+ },
{
path: "/setting",
element: ,
@@ -84,3 +89,4 @@ export const router = createHashRouter([
element: ,
},
]);
+