diff --git a/internal/repository/workflow_output.go b/internal/repository/workflow_output.go
index e75b2cb7..4cee625c 100644
--- a/internal/repository/workflow_output.go
+++ b/internal/repository/workflow_output.go
@@ -61,7 +61,22 @@ func (r *WorkflowOutputRepository) SaveWithCertificate(ctx context.Context, work
workflowOutput.UpdatedAt = record.GetDateTime("updated").Time()
}
- if certificate != nil {
+ if certificate == nil {
+ panic("certificate is nil")
+ } else {
+ if certificate.WorkflowId != "" && certificate.WorkflowId != workflowOutput.WorkflowId {
+ return workflowOutput, fmt.Errorf("certificate #%s is not belong to workflow #%s", certificate.Id, workflowOutput.WorkflowId)
+ }
+ if certificate.WorkflowRunId != "" && certificate.WorkflowRunId != workflowOutput.RunId {
+ return workflowOutput, fmt.Errorf("certificate #%s is not belong to workflow run #%s", certificate.Id, workflowOutput.RunId)
+ }
+ if certificate.WorkflowNodeId != "" && certificate.WorkflowNodeId != workflowOutput.NodeId {
+ return workflowOutput, fmt.Errorf("certificate #%s is not belong to workflow node #%s", certificate.Id, workflowOutput.NodeId)
+ }
+ if certificate.WorkflowOutputId != "" && certificate.WorkflowOutputId != workflowOutput.Id {
+ return workflowOutput, fmt.Errorf("certificate #%s is not belong to workflow output #%s", certificate.Id, workflowOutput.Id)
+ }
+
certificate.WorkflowId = workflowOutput.WorkflowId
certificate.WorkflowRunId = workflowOutput.RunId
certificate.WorkflowNodeId = workflowOutput.NodeId
@@ -143,5 +158,5 @@ func (r *WorkflowOutputRepository) saveRecord(workflowOutput *domain.WorkflowOut
return record, err
}
- return record, err
+ return record, nil
}
diff --git a/ui/src/components/workflow/WorkflowRunDetail.tsx b/ui/src/components/workflow/WorkflowRunDetail.tsx
index db9610f7..d2a20a5f 100644
--- a/ui/src/components/workflow/WorkflowRunDetail.tsx
+++ b/ui/src/components/workflow/WorkflowRunDetail.tsx
@@ -1,9 +1,17 @@
+import { useState } from "react";
import { useTranslation } from "react-i18next";
-import { Alert, Typography } from "antd";
+import { SelectOutlined as SelectOutlinedIcon } from "@ant-design/icons";
+import { useRequest } from "ahooks";
+import { Alert, Button, Divider, Empty, Table, type TableProps, Tooltip, Typography, notification } from "antd";
import dayjs from "dayjs";
+import { ClientResponseError } from "pocketbase";
+import CertificateDetailDrawer from "@/components/certificate/CertificateDetailDrawer";
import Show from "@/components/Show";
+import { type CertificateModel } from "@/domain/certificate";
import { WORKFLOW_RUN_STATUSES, type WorkflowRunModel } from "@/domain/workflowRun";
+import { listByWorkflowRunId as listCertificateByWorkflowRunId } from "@/repository/certificate";
+import { getErrMsg } from "@/utils/error";
export type WorkflowRunDetailProps = {
className?: string;
@@ -45,8 +53,108 @@ const WorkflowRunDetail = ({ data, ...props }: WorkflowRunDetailProps) => {
})}
+
+
+
+
+
+
);
};
+const WorkflowRunArtifacts = ({ runId }: { runId: string }) => {
+ const { t } = useTranslation();
+
+ const [notificationApi, NotificationContextHolder] = notification.useNotification();
+
+ const tableColumns: TableProps["columns"] = [
+ {
+ key: "$index",
+ align: "center",
+ fixed: "left",
+ width: 50,
+ render: (_, __, index) => index + 1,
+ },
+ {
+ key: "type",
+ title: t("workflow_run_artifact.props.type"),
+ render: () => t("workflow_run_artifact.props.type.certificate"),
+ },
+ {
+ key: "name",
+ title: t("workflow_run_artifact.props.name"),
+ ellipsis: true,
+ render: (_, record) => {
+ return (
+
+ {record.subjectAltNames}
+
+ );
+ },
+ },
+ {
+ key: "$action",
+ align: "end",
+ width: 120,
+ render: (_, record) => (
+
+
+ } variant="text" />
+
+ }
+ />
+
+ ),
+ },
+ ];
+ const [tableData, setTableData] = useState([]);
+ const { loading: tableLoading } = useRequest(
+ () => {
+ return listCertificateByWorkflowRunId(runId);
+ },
+ {
+ refreshDeps: [runId],
+ onBefore: () => {
+ setTableData([]);
+ },
+ 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) });
+
+ throw err;
+ },
+ }
+ );
+
+ return (
+ <>
+ {NotificationContextHolder}
+
+ {t("workflow_run.artifacts")}
+
+ columns={tableColumns}
+ dataSource={tableData}
+ loading={tableLoading}
+ locale={{
+ emptyText: ,
+ }}
+ pagination={false}
+ rowKey={(record) => record.id}
+ size="small"
+ />
+ >
+ );
+};
+
export default WorkflowRunDetail;
diff --git a/ui/src/components/workflow/WorkflowRuns.tsx b/ui/src/components/workflow/WorkflowRuns.tsx
index 02bfb453..2a9496ed 100644
--- a/ui/src/components/workflow/WorkflowRuns.tsx
+++ b/ui/src/components/workflow/WorkflowRuns.tsx
@@ -301,7 +301,7 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => {
setPageSize(pageSize);
},
}}
- rowKey={(record: WorkflowRunModel) => record.id}
+ rowKey={(record) => record.id}
scroll={{ x: "max(100%, 960px)" }}
/>
diff --git a/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx b/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx
index 5c6ea85f..413a9d74 100644
--- a/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx
+++ b/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx
@@ -86,7 +86,7 @@ const NotifyNodeConfigForm = forwardRef
-
+
diff --git a/ui/src/components/workflow/node/UploadNode.tsx b/ui/src/components/workflow/node/UploadNode.tsx
index 25850743..0197a8c4 100644
--- a/ui/src/components/workflow/node/UploadNode.tsx
+++ b/ui/src/components/workflow/node/UploadNode.tsx
@@ -3,8 +3,7 @@ import { useTranslation } from "react-i18next";
import { Flex, Typography } from "antd";
import { produce } from "immer";
-import type { WorkflowNodeConfigForUpload } from "@/domain/workflow";
-import { WorkflowNodeType } from "@/domain/workflow";
+import { type WorkflowNodeConfigForUpload, WorkflowNodeType } from "@/domain/workflow";
import { useZustandShallowSelector } from "@/hooks";
import { useWorkflowStore } from "@/stores/workflow";
diff --git a/ui/src/components/workflow/node/UploadNodeConfigForm.tsx b/ui/src/components/workflow/node/UploadNodeConfigForm.tsx
index 807edb11..abfdf25b 100644
--- a/ui/src/components/workflow/node/UploadNodeConfigForm.tsx
+++ b/ui/src/components/workflow/node/UploadNodeConfigForm.tsx
@@ -141,7 +141,7 @@ const UploadNodeConfigForm = forwardRef
-
+
@@ -151,7 +151,7 @@ const UploadNodeConfigForm = forwardRef
-
+
diff --git a/ui/src/domain/workflowRun.ts b/ui/src/domain/workflowRun.ts
index 55b5f36e..269694d2 100644
--- a/ui/src/domain/workflowRun.ts
+++ b/ui/src/domain/workflowRun.ts
@@ -1,4 +1,4 @@
-import type { WorkflowModel } from "./workflow";
+import { type WorkflowModel } from "./workflow";
export interface WorkflowRunModel extends BaseModel {
workflowId: string;
diff --git a/ui/src/i18n/locales/en/nls.workflow.runs.json b/ui/src/i18n/locales/en/nls.workflow.runs.json
index c58d4ad9..c0b2feb3 100644
--- a/ui/src/i18n/locales/en/nls.workflow.runs.json
+++ b/ui/src/i18n/locales/en/nls.workflow.runs.json
@@ -16,5 +16,11 @@
"workflow_run.props.trigger.auto": "Timing",
"workflow_run.props.trigger.manual": "Manual",
"workflow_run.props.started_at": "Started at",
- "workflow_run.props.ended_at": "Ended at"
+ "workflow_run.props.ended_at": "Ended at",
+
+ "workflow_run.artifacts": "Artifacts",
+
+ "workflow_run_artifact.props.type": "Type",
+ "workflow_run_artifact.props.type.certificate": "Certificate",
+ "workflow_run_artifact.props.name": "Name"
}
diff --git a/ui/src/i18n/locales/zh/nls.workflow.runs.json b/ui/src/i18n/locales/zh/nls.workflow.runs.json
index 762a1196..8a7dcd18 100644
--- a/ui/src/i18n/locales/zh/nls.workflow.runs.json
+++ b/ui/src/i18n/locales/zh/nls.workflow.runs.json
@@ -16,5 +16,11 @@
"workflow_run.props.trigger.auto": "定时执行",
"workflow_run.props.trigger.manual": "手动执行",
"workflow_run.props.started_at": "开始时间",
- "workflow_run.props.ended_at": "完成时间"
+ "workflow_run.props.ended_at": "完成时间",
+
+ "workflow_run.artifacts": "输出产物",
+
+ "workflow_run_artifact.props.type": "类型",
+ "workflow_run_artifact.props.type.certificate": "证书",
+ "workflow_run_artifact.props.name": "名称"
}
diff --git a/ui/src/pages/accesses/AccessList.tsx b/ui/src/pages/accesses/AccessList.tsx
index bbc7abe6..53f10d52 100644
--- a/ui/src/pages/accesses/AccessList.tsx
+++ b/ui/src/pages/accesses/AccessList.tsx
@@ -207,7 +207,7 @@ const AccessList = () => {
setPageSize(pageSize);
},
}}
- rowKey={(record: AccessModel) => record.id}
+ rowKey={(record) => record.id}
scroll={{ x: "max(100%, 960px)" }}
/>
diff --git a/ui/src/pages/certificates/CertificateList.tsx b/ui/src/pages/certificates/CertificateList.tsx
index 63a03f6d..4c1b9684 100644
--- a/ui/src/pages/certificates/CertificateList.tsx
+++ b/ui/src/pages/certificates/CertificateList.tsx
@@ -276,7 +276,7 @@ const CertificateList = () => {
setPageSize(pageSize);
},
}}
- rowKey={(record: CertificateModel) => record.id}
+ rowKey={(record) => record.id}
scroll={{ x: "max(100%, 960px)" }}
/>
diff --git a/ui/src/pages/dashboard/Dashboard.tsx b/ui/src/pages/dashboard/Dashboard.tsx
index b5c48cd2..d5020200 100644
--- a/ui/src/pages/dashboard/Dashboard.tsx
+++ b/ui/src/pages/dashboard/Dashboard.tsx
@@ -15,8 +15,7 @@ import {
} 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 { Button, Card, Col, Divider, Empty, Flex, Grid, Row, Space, Statistic, Table, type TableProps, Tag, Typography, notification, theme } from "antd";
import dayjs from "dayjs";
import {
CalendarClock as CalendarClockIcon,
@@ -177,7 +176,7 @@ const Dashboard = () => {
() => {
return listWorkflowRuns({
page: 1,
- perPage: 5,
+ perPage: 9,
expand: true,
});
},
@@ -285,8 +284,9 @@ const Dashboard = () => {
emptyText: ,
}}
pagination={false}
- rowKey={(record: WorkflowRunModel) => record.id}
+ rowKey={(record) => record.id}
scroll={{ x: "max(100%, 960px)" }}
+ size="small"
/>
diff --git a/ui/src/pages/workflows/WorkflowList.tsx b/ui/src/pages/workflows/WorkflowList.tsx
index c0241413..edadd9c0 100644
--- a/ui/src/pages/workflows/WorkflowList.tsx
+++ b/ui/src/pages/workflows/WorkflowList.tsx
@@ -366,7 +366,7 @@ const WorkflowList = () => {
setPageSize(pageSize);
},
}}
- rowKey={(record: WorkflowModel) => record.id}
+ rowKey={(record) => record.id}
scroll={{ x: "max(100%, 960px)" }}
/>
diff --git a/ui/src/repository/certificate.ts b/ui/src/repository/certificate.ts
index 6bfbbcbe..598443ae 100644
--- a/ui/src/repository/certificate.ts
+++ b/ui/src/repository/certificate.ts
@@ -38,6 +38,23 @@ export const list = async (request: ListCertificateRequest) => {
return pb.collection(COLLECTION_NAME).getList(page, perPage, options);
};
+export const listByWorkflowRunId = async (workflowRunId: string) => {
+ const pb = getPocketBase();
+
+ const options: RecordListOptions = {
+ filter: pb.filter("workflowRunId={:workflowRunId}", {
+ workflowRunId: workflowRunId,
+ }),
+ sort: "-created",
+ requestKey: null,
+ };
+ const items = await pb.collection(COLLECTION_NAME).getFullList(options);
+ return {
+ totalItems: items.length,
+ items: items,
+ };
+};
+
export const remove = async (record: MaybeModelRecordWithId) => {
await getPocketBase()
.collection(COLLECTION_NAME)