diff --git a/internal/deployer/deployer.go b/internal/deployer/deployer.go
index 3a892404..36e92866 100644
--- a/internal/deployer/deployer.go
+++ b/internal/deployer/deployer.go
@@ -54,7 +54,6 @@ func NewWithDeployNode(node *domain.WorkflowNode, certdata struct {
}
return &proxyDeployer{
- logger: slog.Default(),
deployer: deployer,
deployCertificate: certdata.Certificate,
deployPrivateKey: certdata.PrivateKey,
@@ -63,7 +62,6 @@ func NewWithDeployNode(node *domain.WorkflowNode, certdata struct {
// TODO: 暂时使用代理模式以兼容之前版本代码,后续重新实现此处逻辑
type proxyDeployer struct {
- logger *slog.Logger
deployer deployer.Deployer
deployCertificate string
deployPrivateKey string
@@ -74,7 +72,7 @@ func (d *proxyDeployer) SetLogger(logger *slog.Logger) {
panic("logger is nil")
}
- d.logger = logger
+ d.deployer.WithLogger(logger)
}
func (d *proxyDeployer) Deploy(ctx context.Context) error {
diff --git a/internal/workflow/node-processor/deploy_node.go b/internal/workflow/node-processor/deploy_node.go
index 556ca891..95d99bfa 100644
--- a/internal/workflow/node-processor/deploy_node.go
+++ b/internal/workflow/node-processor/deploy_node.go
@@ -92,7 +92,7 @@ func (n *deployNode) Process(ctx context.Context) error {
return err
}
- n.logger.Info("apply completed")
+ n.logger.Info("deploy completed")
return nil
}
diff --git a/ui/src/components/workflow/WorkflowRunDetail.tsx b/ui/src/components/workflow/WorkflowRunDetail.tsx
index 785624f9..5fbe5520 100644
--- a/ui/src/components/workflow/WorkflowRunDetail.tsx
+++ b/ui/src/components/workflow/WorkflowRunDetail.tsx
@@ -1,16 +1,19 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
-import { SelectOutlined as SelectOutlinedIcon } from "@ant-design/icons";
+import { RightOutlined as RightOutlinedIcon, SelectOutlined as SelectOutlinedIcon } from "@ant-design/icons";
import { useRequest } from "ahooks";
-import { Alert, Button, Divider, Empty, Space, Table, type TableProps, Tooltip, Typography, notification } from "antd";
+import { Alert, Button, Collapse, Divider, Empty, Skeleton, Space, Spin, 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 type { WorkflowLogModel } from "@/domain/workflowLog";
import { WORKFLOW_RUN_STATUSES, type WorkflowRunModel } from "@/domain/workflowRun";
-import { listByWorkflowRunId as listCertificateByWorkflowRunId } from "@/repository/certificate";
+import { listByWorkflowRunId as listCertificatesByWorkflowRunId } from "@/repository/certificate";
+import { listByWorkflowRunId as listLogsByWorkflowRunId } from "@/repository/workflowLog";
+import { mergeCls } from "@/utils/css";
import { getErrMsg } from "@/utils/error";
export type WorkflowRunDetailProps = {
@@ -33,28 +36,7 @@ const WorkflowRunDetail = ({ data, ...props }: WorkflowRunDetailProps) => {
-
{t("workflow_run.logs")}
-
-
- {data.logs?.map((item, i) => {
- return (
-
-
{item.nodeName}
-
- {item.records?.map((output, j) => {
- return (
-
-
[{dayjs(output.time).format("YYYY-MM-DD HH:mm:ss")}]
- {output.error ?
{output.error}
:
{output.content}
}
-
- );
- })}
-
-
- );
- })}
-
-
+
@@ -66,6 +48,124 @@ const WorkflowRunDetail = ({ data, ...props }: WorkflowRunDetailProps) => {
);
};
+const WorkflowRunLogs = ({ runId, runStatus }: { runId: string; runStatus: string }) => {
+ const { t } = useTranslation();
+
+ type Log = Pick;
+ type LogGroup = { id: string; name: string; records: Log[] };
+
+ const [listData, setListData] = useState([]);
+ const { loading } = useRequest(
+ () => {
+ return listLogsByWorkflowRunId(runId);
+ },
+ {
+ refreshDeps: [runId, runStatus],
+ pollingInterval: runStatus === WORKFLOW_RUN_STATUSES.PENDING || runStatus === WORKFLOW_RUN_STATUSES.RUNNING ? 5000 : 0,
+ pollingWhenHidden: false,
+ throttleWait: 500,
+ onBefore: () => {
+ setListData([]);
+ },
+ onSuccess: (res) => {
+ setListData(
+ res.items.reduce((acc, e) => {
+ let group = acc.at(-1);
+ if (!group || group.id !== e.nodeId) {
+ group = { id: e.nodeId, name: e.nodeName, records: [] };
+ acc.push(group);
+ }
+ group.records.push({ level: e.level, message: e.message, data: e.data, created: e.created });
+ return acc;
+ }, [] as LogGroup[])
+ );
+ },
+ onError: (err) => {
+ if (err instanceof ClientResponseError && err.isAbort) {
+ return;
+ }
+
+ console.error(err);
+
+ throw err;
+ },
+ }
+ );
+
+ const renderLogRecord = (record: Log) => {
+ let message = <>{record.message}>;
+ if (record.data != null && Object.keys(record.data).length > 0) {
+ message = (
+
+ {record.message}
+ {Object.entries(record.data).map(([key, value]) => (
+
+
{key}:
+
{JSON.stringify(value)}
+
+ ))}
+
+ );
+ }
+
+ return (
+
+
[{dayjs(record.created).format("YYYY-MM-DD HH:mm:ss")}]
+
+ {message}
+
+
+ );
+ };
+
+ return (
+ <>
+ {t("workflow_run.logs")}
+
+
0}
+ fallback={
+
+
+
+ }
+ >
+
+
group.id)}
+ expandIcon={({ isActive }) => }
+ items={listData.map((group) => {
+ return {
+ key: group.id,
+ classNames: {
+ header: "text-sm text-stone-200",
+ body: "text-stone-200",
+ },
+ style: { color: "inherit", border: "none" },
+ styles: {
+ header: { color: "inherit" },
+ },
+ label: group.name,
+ children: {group.records.map((record) => renderLogRecord(record))}
,
+ };
+ })}
+ />
+
+
+
+ >
+ );
+};
+
const WorkflowRunArtifacts = ({ runId }: { runId: string }) => {
const { t } = useTranslation();
@@ -117,7 +217,7 @@ const WorkflowRunArtifacts = ({ runId }: { runId: string }) => {
const [tableData, setTableData] = useState([]);
const { loading: tableLoading } = useRequest(
() => {
- return listCertificateByWorkflowRunId(runId);
+ return listCertificatesByWorkflowRunId(runId);
},
{
refreshDeps: [runId],
diff --git a/ui/src/domain/workflowLog.ts b/ui/src/domain/workflowLog.ts
new file mode 100644
index 00000000..ffe6fdd8
--- /dev/null
+++ b/ui/src/domain/workflowLog.ts
@@ -0,0 +1,7 @@
+export interface WorkflowLogModel extends Omit {
+ nodeId: string;
+ nodeName: string;
+ level: "DEBUG" | "INFO" | "WARN" | "ERROR";
+ message: string;
+ data: Record;
+}
diff --git a/ui/src/domain/workflowRun.ts b/ui/src/domain/workflowRun.ts
index 80872b31..6df4a406 100644
--- a/ui/src/domain/workflowRun.ts
+++ b/ui/src/domain/workflowRun.ts
@@ -6,27 +6,12 @@ export interface WorkflowRunModel extends BaseModel {
trigger: string;
startedAt: ISO8601String;
endedAt: ISO8601String;
- logs?: WorkflowRunLog[];
error?: string;
expand?: {
- workflowId?: WorkflowModel;
+ workflowId?: WorkflowModel; // TODO: ugly, maybe to use an alias?
};
}
-export type WorkflowRunLog = {
- nodeId: string;
- nodeName: string;
- records?: WorkflowRunLogRecord[];
- error?: string;
-};
-
-export type WorkflowRunLogRecord = {
- time: ISO8601String;
- level: string;
- content: string;
- error?: string;
-};
-
export const WORKFLOW_RUN_STATUSES = Object.freeze({
PENDING: "pending",
RUNNING: "running",
diff --git a/ui/src/i18n/locales/en/nls.dashboard.json b/ui/src/i18n/locales/en/nls.dashboard.json
index 8ae9d94d..38e20e1b 100644
--- a/ui/src/i18n/locales/en/nls.dashboard.json
+++ b/ui/src/i18n/locales/en/nls.dashboard.json
@@ -8,7 +8,7 @@
"dashboard.statistics.enabled_workflows": "Enabled workflows",
"dashboard.statistics.unit": "",
- "dashboard.latest_workflow_run": "Latest workflow run",
+ "dashboard.latest_workflow_runs": "Latest workflow runs",
"dashboard.quick_actions": "Quick actions",
"dashboard.quick_actions.create_workflow": "Create workflow",
diff --git a/ui/src/i18n/locales/zh/nls.dashboard.json b/ui/src/i18n/locales/zh/nls.dashboard.json
index badd7cb8..30eb5972 100644
--- a/ui/src/i18n/locales/zh/nls.dashboard.json
+++ b/ui/src/i18n/locales/zh/nls.dashboard.json
@@ -8,7 +8,7 @@
"dashboard.statistics.enabled_workflows": "已启用工作流",
"dashboard.statistics.unit": "个",
- "dashboard.latest_workflow_run": "最近执行的工作流",
+ "dashboard.latest_workflow_runs": "最近执行的工作流",
"dashboard.quick_actions": "快捷操作",
"dashboard.quick_actions.create_workflow": "新建工作流",
diff --git a/ui/src/pages/certificates/CertificateList.tsx b/ui/src/pages/certificates/CertificateList.tsx
index 265f0185..049069a3 100644
--- a/ui/src/pages/certificates/CertificateList.tsx
+++ b/ui/src/pages/certificates/CertificateList.tsx
@@ -28,7 +28,7 @@ import { ClientResponseError } from "pocketbase";
import CertificateDetailDrawer from "@/components/certificate/CertificateDetailDrawer";
import { CERTIFICATE_SOURCES, type CertificateModel } from "@/domain/certificate";
-import { type ListCertificateRequest, list as listCertificate, remove as removeCertificate } from "@/repository/certificate";
+import { list as listCertificates, type ListRequest as listCertificatesRequest, remove as removeCertificate } from "@/repository/certificate";
import { getErrMsg } from "@/utils/error";
const CertificateList = () => {
@@ -223,9 +223,9 @@ const CertificateList = () => {
run: refreshData,
} = useRequest(
() => {
- return listCertificate({
+ return listCertificates({
keyword: filters["keyword"] as string,
- state: filters["state"] as ListCertificateRequest["state"],
+ state: filters["state"] as listCertificatesRequest["state"],
page: page,
perPage: pageSize,
});
diff --git a/ui/src/pages/dashboard/Dashboard.tsx b/ui/src/pages/dashboard/Dashboard.tsx
index ea7a21cb..83f8cd47 100644
--- a/ui/src/pages/dashboard/Dashboard.tsx
+++ b/ui/src/pages/dashboard/Dashboard.tsx
@@ -275,7 +275,7 @@ const Dashboard = () => {
-
+
columns={tableColumns}
dataSource={tableData}
diff --git a/ui/src/pages/workflows/WorkflowList.tsx b/ui/src/pages/workflows/WorkflowList.tsx
index 18a8b577..09bca7fc 100644
--- a/ui/src/pages/workflows/WorkflowList.tsx
+++ b/ui/src/pages/workflows/WorkflowList.tsx
@@ -41,7 +41,7 @@ import { ClientResponseError } from "pocketbase";
import { WORKFLOW_TRIGGERS, type WorkflowModel, isAllNodesValidated } from "@/domain/workflow";
import { WORKFLOW_RUN_STATUSES } from "@/domain/workflowRun";
-import { list as listWorkflow, remove as removeWorkflow, save as saveWorkflow } from "@/repository/workflow";
+import { list as listWorkflows, remove as removeWorkflow, save as saveWorkflow } from "@/repository/workflow";
import { getErrMsg } from "@/utils/error";
const WorkflowList = () => {
@@ -253,7 +253,7 @@ const WorkflowList = () => {
run: refreshData,
} = useRequest(
() => {
- return listWorkflow({
+ return listWorkflows({
keyword: filters["keyword"] as string,
enabled: (filters["state"] as string) === "enabled" ? true : (filters["state"] as string) === "disabled" ? false : undefined,
page: page,
diff --git a/ui/src/repository/_pocketbase.ts b/ui/src/repository/_pocketbase.ts
index 983c4987..85068f50 100644
--- a/ui/src/repository/_pocketbase.ts
+++ b/ui/src/repository/_pocketbase.ts
@@ -14,3 +14,4 @@ export const COLLECTION_NAME_SETTINGS = "settings";
export const COLLECTION_NAME_WORKFLOW = "workflow";
export const COLLECTION_NAME_WORKFLOW_RUN = "workflow_run";
export const COLLECTION_NAME_WORKFLOW_OUTPUT = "workflow_output";
+export const COLLECTION_NAME_WORKFLOW_LOG = "workflow_logs";
diff --git a/ui/src/repository/certificate.ts b/ui/src/repository/certificate.ts
index b6b8d55e..f7c95f7d 100644
--- a/ui/src/repository/certificate.ts
+++ b/ui/src/repository/certificate.ts
@@ -3,14 +3,14 @@ import dayjs from "dayjs";
import { type CertificateModel } from "@/domain/certificate";
import { COLLECTION_NAME_CERTIFICATE, getPocketBase } from "./_pocketbase";
-export type ListCertificateRequest = {
+export type ListRequest = {
keyword?: string;
state?: "expireSoon" | "expired";
page?: number;
perPage?: number;
};
-export const list = async (request: ListCertificateRequest) => {
+export const list = async (request: ListRequest) => {
const pb = getPocketBase();
const filters: string[] = ["deleted=null"];
@@ -39,7 +39,7 @@ export const listByWorkflowRunId = async (workflowRunId: string) => {
const list = await pb.collection(COLLECTION_NAME_CERTIFICATE).getFullList({
batch: 65535,
filter: pb.filter("workflowRunId={:workflowRunId}", { workflowRunId: workflowRunId }),
- sort: "-created",
+ // sort: "created",
requestKey: null,
});
diff --git a/ui/src/repository/workflow.ts b/ui/src/repository/workflow.ts
index 0b35a5e2..5701927c 100644
--- a/ui/src/repository/workflow.ts
+++ b/ui/src/repository/workflow.ts
@@ -3,14 +3,14 @@ import { type RecordSubscription } from "pocketbase";
import { type WorkflowModel } from "@/domain/workflow";
import { COLLECTION_NAME_WORKFLOW, getPocketBase } from "./_pocketbase";
-export type ListWorkflowRequest = {
+export type ListRequest = {
keyword?: string;
enabled?: boolean;
page?: number;
perPage?: number;
};
-export const list = async (request: ListWorkflowRequest) => {
+export const list = async (request: ListRequest) => {
const pb = getPocketBase();
const filters: string[] = [];
diff --git a/ui/src/repository/workflowLog.ts b/ui/src/repository/workflowLog.ts
new file mode 100644
index 00000000..a866d624
--- /dev/null
+++ b/ui/src/repository/workflowLog.ts
@@ -0,0 +1,19 @@
+import { type WorkflowLogModel } from "@/domain/workflowLog";
+
+import { COLLECTION_NAME_WORKFLOW_LOG, getPocketBase } from "./_pocketbase";
+
+export const listByWorkflowRunId = async (workflowRunId: string) => {
+ const pb = getPocketBase();
+
+ const list = await pb.collection(COLLECTION_NAME_WORKFLOW_LOG).getFullList({
+ batch: 65535,
+ filter: pb.filter("runId={:runId}", { runId: workflowRunId }),
+ // sort: "created",
+ requestKey: null,
+ });
+
+ return {
+ totalItems: list.length,
+ items: list,
+ };
+};
diff --git a/ui/src/repository/workflowRun.ts b/ui/src/repository/workflowRun.ts
index 51038f18..22c69802 100644
--- a/ui/src/repository/workflowRun.ts
+++ b/ui/src/repository/workflowRun.ts
@@ -4,14 +4,14 @@ import { type WorkflowRunModel } from "@/domain/workflowRun";
import { COLLECTION_NAME_WORKFLOW_RUN, getPocketBase } from "./_pocketbase";
-export type ListWorkflowRunsRequest = {
+export type ListRequest = {
workflowId?: string;
page?: number;
perPage?: number;
expand?: boolean;
};
-export const list = async (request: ListWorkflowRunsRequest) => {
+export const list = async (request: ListRequest) => {
const pb = getPocketBase();
const filters: string[] = [];
diff --git a/ui/src/stores/access/index.ts b/ui/src/stores/access/index.ts
index 61601978..55d8835a 100644
--- a/ui/src/stores/access/index.ts
+++ b/ui/src/stores/access/index.ts
@@ -2,7 +2,7 @@
import { create } from "zustand";
import { type AccessModel } from "@/domain/access";
-import { list as listAccess, remove as removeAccess, save as saveAccess } from "@/repository/access";
+import { list as listAccesses, remove as removeAccess, save as saveAccess } from "@/repository/access";
export interface AccessesState {
accesses: AccessModel[];
@@ -24,7 +24,7 @@ export const useAccessesStore = create((set) => {
loadedAtOnce: false,
fetchAccesses: async () => {
- fetcher ??= listAccess().then((res) => res.items);
+ fetcher ??= listAccesses().then((res) => res.items);
try {
set({ loading: true });