diff --git a/internal/domain/workflow_log.go b/internal/domain/workflow_log.go
index a33c5480..05eef5a7 100644
--- a/internal/domain/workflow_log.go
+++ b/internal/domain/workflow_log.go
@@ -8,8 +8,9 @@ type WorkflowLog struct {
Meta
WorkflowId string `json:"workflowId" db:"workflowId"`
RunId string `json:"workflorunIdwId" db:"runId"`
- NodeId string `json:"nodeId"`
- NodeName string `json:"nodeName"`
+ NodeId string `json:"nodeId" db:"nodeId"`
+ NodeName string `json:"nodeName" db:"nodeName"`
+ Timestamp int64 `json:"timestamp" db:"timestamp"` // 毫秒级时间戳
Level string `json:"level" db:"level"`
Message string `json:"message" db:"message"`
Data map[string]any `json:"data" db:"data"`
diff --git a/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go
index 8783c053..618af762 100644
--- a/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go
+++ b/internal/pkg/core/deployer/providers/huaweicloud-elb/huaweicloud_elb.go
@@ -90,14 +90,6 @@ func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
}
func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
- // 上传证书到 SCM
- upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
- if err != nil {
- return nil, xerrors.Wrap(err, "failed to upload certificate file")
- } else {
- d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
- }
-
// 根据部署资源类型决定部署方式
switch d.config.ResourceType {
case RESOURCE_TYPE_CERTIFICATE:
diff --git a/internal/repository/workflow_log.go b/internal/repository/workflow_log.go
index 95bc2e7d..0b801231 100644
--- a/internal/repository/workflow_log.go
+++ b/internal/repository/workflow_log.go
@@ -22,7 +22,7 @@ func (r *WorkflowLogRepository) ListByWorkflowRunId(ctx context.Context, workflo
records, err := app.GetApp().FindRecordsByFilter(
domain.CollectionNameWorkflowLog,
"runId={:runId}",
- "-created",
+ "timestamp",
0, 0,
dbx.Params{"runId": workflowRunId},
)
@@ -66,6 +66,7 @@ func (r *WorkflowLogRepository) Save(ctx context.Context, workflowLog *domain.Wo
record.Set("runId", workflowLog.RunId)
record.Set("nodeId", workflowLog.NodeId)
record.Set("nodeName", workflowLog.NodeName)
+ record.Set("timestamp", workflowLog.Timestamp)
record.Set("level", workflowLog.Level)
record.Set("message", workflowLog.Message)
record.Set("data", workflowLog.Data)
@@ -102,6 +103,7 @@ func (r *WorkflowLogRepository) castRecordToModel(record *core.Record) (*domain.
RunId: record.GetString("runId"),
NodeId: record.GetString("nodeId"),
NodeName: record.GetString("nodeName"),
+ Timestamp: int64(record.GetInt("timestamp")),
Level: record.GetString("level"),
Message: record.GetString("message"),
Data: logdata,
diff --git a/internal/workflow/dispatcher/invoker.go b/internal/workflow/dispatcher/invoker.go
index 23d70f01..5f344458 100644
--- a/internal/workflow/dispatcher/invoker.go
+++ b/internal/workflow/dispatcher/invoker.go
@@ -80,6 +80,7 @@ func (w *workflowInvoker) processNode(ctx context.Context, node *domain.Workflow
log.RunId = w.runId
log.NodeId = current.Id
log.NodeName = current.Name
+ log.Timestamp = record.Time.UnixMilli()
log.Level = record.Level.String()
log.Message = record.Message
log.Data = record.Data
diff --git a/internal/workflow/node-processor/apply_node.go b/internal/workflow/node-processor/apply_node.go
index 2fc6c223..dc9a95d1 100644
--- a/internal/workflow/node-processor/apply_node.go
+++ b/internal/workflow/node-processor/apply_node.go
@@ -42,7 +42,7 @@ func (n *applyNode) Process(ctx context.Context) error {
// 检测是否可以跳过本次执行
if skippable, skipReason := n.checkCanSkip(ctx, lastOutput); skippable {
- n.logger.Warn(fmt.Sprintf("skip this application, because %s", skipReason))
+ n.logger.Info(fmt.Sprintf("skip this application, because %s", skipReason))
return nil
} else if skipReason != "" {
n.logger.Info(fmt.Sprintf("continue to apply, because %s", skipReason))
@@ -124,7 +124,7 @@ func (n *applyNode) checkCanSkip(ctx context.Context, lastOutput *domain.Workflo
renewalInterval := time.Duration(currentNodeConfig.SkipBeforeExpiryDays) * time.Hour * 24
expirationTime := time.Until(lastCertificate.ExpireAt)
if expirationTime > renewalInterval {
- return true, fmt.Sprintf("the certificate has already been issued (expires in %dD, next renewal in %dD)", int(expirationTime.Hours()/24), currentNodeConfig.SkipBeforeExpiryDays)
+ return true, fmt.Sprintf("the certificate has already been issued (expires in %dd, next renewal in %dd)", int(expirationTime.Hours()/24), currentNodeConfig.SkipBeforeExpiryDays)
}
}
}
diff --git a/internal/workflow/node-processor/deploy_node.go b/internal/workflow/node-processor/deploy_node.go
index 95d99bfa..42bc9ca6 100644
--- a/internal/workflow/node-processor/deploy_node.go
+++ b/internal/workflow/node-processor/deploy_node.go
@@ -55,7 +55,7 @@ func (n *deployNode) Process(ctx context.Context) error {
// 检测是否可以跳过本次执行
if lastOutput != nil && certificate.CreatedAt.Before(lastOutput.UpdatedAt) {
if skippable, skipReason := n.checkCanSkip(ctx, lastOutput); skippable {
- n.logger.Warn(fmt.Sprintf("skip this deployment, because %s", skipReason))
+ n.logger.Info(fmt.Sprintf("skip this deployment, because %s", skipReason))
return nil
} else if skipReason != "" {
n.logger.Info(fmt.Sprintf("continue to deploy, because %s", skipReason))
diff --git a/internal/workflow/node-processor/upload_node.go b/internal/workflow/node-processor/upload_node.go
index a3640c2d..6c46e90f 100644
--- a/internal/workflow/node-processor/upload_node.go
+++ b/internal/workflow/node-processor/upload_node.go
@@ -40,7 +40,7 @@ func (n *uploadNode) Process(ctx context.Context) error {
// 检测是否可以跳过本次执行
if skippable, skipReason := n.checkCanSkip(ctx, lastOutput); skippable {
- n.logger.Warn(fmt.Sprintf("skip this upload, because %s", skipReason))
+ n.logger.Info(fmt.Sprintf("skip this upload, because %s", skipReason))
return nil
} else if skipReason != "" {
n.logger.Info(fmt.Sprintf("continue to upload, because %s", skipReason))
diff --git a/migrations/1742209200_upgrade.go b/migrations/1742209200_upgrade.go
index 8c9ede5f..0a980972 100644
--- a/migrations/1742209200_upgrade.go
+++ b/migrations/1742209200_upgrade.go
@@ -3,6 +3,7 @@ package migrations
import (
"encoding/json"
"strings"
+ "time"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
@@ -86,6 +87,18 @@ func init() {
"system": false,
"type": "text"
},
+ {
+ "hidden": false,
+ "id": "number2782324286",
+ "max": null,
+ "min": null,
+ "name": "timestamp",
+ "onlyInt": false,
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "number"
+ },
{
"autogeneratePattern": "",
"hidden": false,
@@ -192,13 +205,15 @@ func init() {
for _, log := range logs {
for _, logRecord := range log.Records {
record := core.NewRecord(collection)
+ createdAt, _ := time.Parse(time.RFC3339, logRecord.Time)
record.Set("workflowId", workflowRun.Get("workflowId"))
record.Set("runId", workflowRun.Get("id"))
record.Set("nodeId", log.NodeId)
record.Set("nodeName", log.NodeName)
+ record.Set("timestamp", createdAt.UnixMilli())
record.Set("level", logRecord.Level)
record.Set("message", strings.TrimSpace(logRecord.Content+" "+logRecord.Error))
- record.Set("created", log.Records)
+ record.Set("created", createdAt)
if err := app.Save(record); err != nil {
return err
}
diff --git a/ui/src/components/workflow/WorkflowRunDetail.tsx b/ui/src/components/workflow/WorkflowRunDetail.tsx
index 5fbe5520..5d8c7f29 100644
--- a/ui/src/components/workflow/WorkflowRunDetail.tsx
+++ b/ui/src/components/workflow/WorkflowRunDetail.tsx
@@ -1,8 +1,34 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
-import { RightOutlined as RightOutlinedIcon, SelectOutlined as SelectOutlinedIcon } from "@ant-design/icons";
+import {
+ CheckCircleOutlined as CheckCircleOutlinedIcon,
+ CheckOutlined as CheckOutlinedIcon,
+ ClockCircleOutlined as ClockCircleOutlinedIcon,
+ CloseCircleOutlined as CloseCircleOutlinedIcon,
+ RightOutlined as RightOutlinedIcon,
+ SelectOutlined as SelectOutlinedIcon,
+ SettingOutlined as SettingOutlinedIcon,
+ StopOutlined as StopOutlinedIcon,
+ SyncOutlined as SyncOutlinedIcon,
+} from "@ant-design/icons";
import { useRequest } from "ahooks";
-import { Alert, Button, Collapse, Divider, Empty, Skeleton, Space, Spin, Table, type TableProps, Tooltip, Typography, notification } from "antd";
+import {
+ Button,
+ Collapse,
+ Divider,
+ Dropdown,
+ Empty,
+ Flex,
+ Skeleton,
+ Space,
+ Spin,
+ Table,
+ type TableProps,
+ Tooltip,
+ Typography,
+ notification,
+ theme,
+} from "antd";
import dayjs from "dayjs";
import { ClientResponseError } from "pocketbase";
@@ -23,25 +49,14 @@ export type WorkflowRunDetailProps = {
};
const WorkflowRunDetail = ({ data, ...props }: WorkflowRunDetailProps) => {
- const { t } = useTranslation();
-
return (
-
- {t("workflow_run.props.status.succeeded")}} />
-
-
-
- {t("workflow_run.props.status.failed")}} />
-
-
-
+
-
+
-
+
-
@@ -51,9 +66,10 @@ 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 { token: themeToken } = theme.useToken();
+ type Log = Pick;
+ type LogGroup = { id: string; name: string; records: Log[] };
const [listData, setListData] = useState([]);
const { loading } = useRequest(
() => {
@@ -61,13 +77,12 @@ const WorkflowRunLogs = ({ runId, runStatus }: { runId: string; runStatus: strin
},
{
refreshDeps: [runId, runStatus],
- pollingInterval: runStatus === WORKFLOW_RUN_STATUSES.PENDING || runStatus === WORKFLOW_RUN_STATUSES.RUNNING ? 5000 : 0,
+ pollingInterval: runStatus === WORKFLOW_RUN_STATUSES.PENDING || runStatus === WORKFLOW_RUN_STATUSES.RUNNING ? 3000 : 0,
pollingWhenHidden: false,
throttleWait: 500,
- onBefore: () => {
- setListData([]);
- },
onSuccess: (res) => {
+ if (res.items.length === listData.flatMap((e) => e.records).length) return;
+
setListData(
res.items.reduce((acc, e) => {
let group = acc.at(-1);
@@ -75,7 +90,7 @@ const WorkflowRunLogs = ({ runId, runStatus }: { runId: string; runStatus: strin
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 });
+ group.records.push({ timestamp: e.timestamp, level: e.level, message: e.message, data: e.data });
return acc;
}, [] as LogGroup[])
);
@@ -92,7 +107,52 @@ const WorkflowRunLogs = ({ runId, runStatus }: { runId: string; runStatus: strin
}
);
- const renderLogRecord = (record: Log) => {
+ const [showTimestamp, setShowTimestamp] = useState(true);
+ const [showWhitespace, setShowWhitespace] = useState(true);
+
+ const renderBadge = () => {
+ switch (runStatus) {
+ case WORKFLOW_RUN_STATUSES.PENDING:
+ return (
+
+
+ {t("workflow_run.props.status.pending")}
+
+ );
+ case WORKFLOW_RUN_STATUSES.RUNNING:
+ return (
+
+
+ {t("workflow_run.props.status.running")}
+
+ );
+ case WORKFLOW_RUN_STATUSES.SUCCEEDED:
+ return (
+
+
+ {t("workflow_run.props.status.succeeded")}
+
+ );
+ case WORKFLOW_RUN_STATUSES.FAILED:
+ return (
+
+
+ {t("workflow_run.props.status.failed")}
+
+ );
+ case WORKFLOW_RUN_STATUSES.CANCELED:
+ return (
+
+
+ {t("workflow_run.props.status.canceled")}
+
+ );
+ }
+
+ return <>>;
+ };
+
+ const renderRecord = (record: Log) => {
let message = <>{record.message}>;
if (record.data != null && Object.keys(record.data).length > 0) {
message = (
@@ -100,8 +160,8 @@ const WorkflowRunLogs = ({ runId, runStatus }: { runId: string; runStatus: strin
{record.message}
{Object.entries(record.data).map(([key, value]) => (
-
{key}:
-
{JSON.stringify(value)}
+
{key}:
+
{JSON.stringify(value)}
))}
@@ -110,13 +170,14 @@ const WorkflowRunLogs = ({ runId, runStatus }: { runId: string; runStatus: strin
return (
-
[{dayjs(record.created).format("YYYY-MM-DD HH:mm:ss")}]
+ {showTimestamp ?
[{dayjs(record.timestamp).format("YYYY-MM-DD HH:mm:ss")}]
: <>>}
{message}
@@ -129,6 +190,35 @@ const WorkflowRunLogs = ({ runId, runStatus }: { runId: string; runStatus: strin
<>
{t("workflow_run.logs")}
+
+
{renderBadge()}
+
+ ,
+ onClick: () => setShowTimestamp(!showTimestamp),
+ },
+ {
+ key: "show-whitespace",
+ label: t("workflow_run.logs.menu.show_whitespaces"),
+ icon: ,
+ onClick: () => setShowWhitespace(!showWhitespace),
+ },
+ ],
+ }}
+ trigger={["click"]}
+ >
+ } ghost />
+
+
+
+
+
+
0}
fallback={
@@ -137,7 +227,7 @@ const WorkflowRunLogs = ({ runId, runStatus }: { runId: string; runStatus: strin
}
>
-
+
{group.records.map((record) => renderLogRecord(record))}
,
+ children:
{group.records.map((record) => renderRecord(record))}
,
};
})}
/>
@@ -221,9 +311,6 @@ const WorkflowRunArtifacts = ({ runId }: { runId: string }) => {
},
{
refreshDeps: [runId],
- onBefore: () => {
- setTableData([]);
- },
onSuccess: (res) => {
setTableData(res.items);
},
diff --git a/ui/src/components/workflow/node/BranchNode.tsx b/ui/src/components/workflow/node/BranchNode.tsx
index 4a68f315..f8a755d0 100644
--- a/ui/src/components/workflow/node/BranchNode.tsx
+++ b/ui/src/components/workflow/node/BranchNode.tsx
@@ -10,8 +10,6 @@ import AddNode from "./AddNode";
import WorkflowElement from "../WorkflowElement";
import { type SharedNodeProps } from "./_SharedNode";
-const { useToken } = theme;
-
export type BrandNodeProps = SharedNodeProps;
const BranchNode = ({ node, disabled }: BrandNodeProps) => {
@@ -19,7 +17,7 @@ const BranchNode = ({ node, disabled }: BrandNodeProps) => {
const { addBranch } = useWorkflowStore(useZustandShallowSelector(["addBranch"]));
- const token = useToken();
+ const { token: themeToken } = theme.useToken();
const renderBranch = (node: WorkflowNode, branchNodeId?: string, branchIndex?: number) => {
const elements: JSX.Element[] = [];
@@ -38,7 +36,7 @@ const BranchNode = ({ node, disabled }: BrandNodeProps) => {