mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-09 14:09:52 +00:00
feat: support removing workflow runs
This commit is contained in:
parent
c61b2d2d3f
commit
8dc86209df
@ -2,12 +2,11 @@
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/rsa"
|
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/pavlo-v-chernykh/keystore-go/v4"
|
"github.com/pavlo-v-chernykh/keystore-go/v4"
|
||||||
"software.sslmate.com/src/go-pkcs12"
|
"software.sslmate.com/src/go-pkcs12"
|
||||||
)
|
)
|
||||||
@ -28,23 +27,9 @@ func TransformCertificateFromPEMToPFX(certPem string, privkeyPem string, pfxPass
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var privkey interface{}
|
privkey, err := certcrypto.ParsePEMPrivateKey([]byte(privkeyPem))
|
||||||
switch cert.PublicKey.(type) {
|
if err != nil {
|
||||||
case *rsa.PublicKey:
|
return nil, err
|
||||||
{
|
|
||||||
privkey, err = ParsePKCS1PrivateKeyFromPEM(privkeyPem)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ecdsa.PublicKey:
|
|
||||||
{
|
|
||||||
privkey, err = ParseECPrivateKeyFromPEM(privkeyPem)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pfxData, err := pkcs12.LegacyRC2.Encode(privkey, cert, nil, pfxPassword)
|
pfxData, err := pkcs12.LegacyRC2.Encode(privkey, cert, nil, pfxPassword)
|
||||||
|
@ -4,17 +4,18 @@ import {
|
|||||||
CheckCircleOutlined as CheckCircleOutlinedIcon,
|
CheckCircleOutlined as CheckCircleOutlinedIcon,
|
||||||
ClockCircleOutlined as ClockCircleOutlinedIcon,
|
ClockCircleOutlined as ClockCircleOutlinedIcon,
|
||||||
CloseCircleOutlined as CloseCircleOutlinedIcon,
|
CloseCircleOutlined as CloseCircleOutlinedIcon,
|
||||||
|
DeleteOutlined as DeleteOutlinedIcon,
|
||||||
SelectOutlined as SelectOutlinedIcon,
|
SelectOutlined as SelectOutlinedIcon,
|
||||||
SyncOutlined as SyncOutlinedIcon,
|
SyncOutlined as SyncOutlinedIcon,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { useRequest } from "ahooks";
|
import { useRequest } from "ahooks";
|
||||||
import { Button, Empty, Table, type TableProps, Tag, notification } from "antd";
|
import { Button, Empty, Modal, Table, type TableProps, Tag, Tooltip, notification } from "antd";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { ClientResponseError } from "pocketbase";
|
import { ClientResponseError } from "pocketbase";
|
||||||
|
|
||||||
import { WORKFLOW_TRIGGERS } from "@/domain/workflow";
|
import { WORKFLOW_TRIGGERS } from "@/domain/workflow";
|
||||||
import { WORKFLOW_RUN_STATUSES, type WorkflowRunModel } from "@/domain/workflowRun";
|
import { WORKFLOW_RUN_STATUSES, type WorkflowRunModel } from "@/domain/workflowRun";
|
||||||
import { list as listWorkflowRuns } from "@/repository/workflowRun";
|
import { list as listWorkflowRuns, remove as removeWorkflowRun } from "@/repository/workflowRun";
|
||||||
import { getErrMsg } from "@/utils/error";
|
import { getErrMsg } from "@/utils/error";
|
||||||
import WorkflowRunDetailDrawer from "./WorkflowRunDetailDrawer";
|
import WorkflowRunDetailDrawer from "./WorkflowRunDetailDrawer";
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ export type WorkflowRunsProps = {
|
|||||||
const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => {
|
const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [modalApi, ModelContextHolder] = Modal.useModal();
|
||||||
const [notificationApi, NotificationContextHolder] = notification.useNotification();
|
const [notificationApi, NotificationContextHolder] = notification.useNotification();
|
||||||
|
|
||||||
const tableColumns: TableProps<WorkflowRunModel>["columns"] = [
|
const tableColumns: TableProps<WorkflowRunModel>["columns"] = [
|
||||||
@ -118,7 +120,27 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => {
|
|||||||
width: 120,
|
width: 120,
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<Button.Group>
|
<Button.Group>
|
||||||
<WorkflowRunDetailDrawer data={record} trigger={<Button color="primary" icon={<SelectOutlinedIcon />} variant="text" />} />
|
<WorkflowRunDetailDrawer
|
||||||
|
data={record}
|
||||||
|
trigger={
|
||||||
|
<Tooltip title={t("workflow_run.action.view")}>
|
||||||
|
<Button color="primary" icon={<SelectOutlinedIcon />} variant="text" />
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Tooltip title={t("workflow_run.action.delete")}>
|
||||||
|
<Button
|
||||||
|
color="danger"
|
||||||
|
danger
|
||||||
|
disabled={record.status !== WORKFLOW_RUN_STATUSES.SUCCEEDED && record.status !== WORKFLOW_RUN_STATUSES.FAILED}
|
||||||
|
icon={<DeleteOutlinedIcon />}
|
||||||
|
variant="text"
|
||||||
|
onClick={() => {
|
||||||
|
handleDeleteClick(record);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
</Button.Group>
|
</Button.Group>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -129,7 +151,11 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => {
|
|||||||
const [page, setPage] = useState<number>(1);
|
const [page, setPage] = useState<number>(1);
|
||||||
const [pageSize, setPageSize] = useState<number>(10);
|
const [pageSize, setPageSize] = useState<number>(10);
|
||||||
|
|
||||||
const { loading, error: loadedError } = useRequest(
|
const {
|
||||||
|
loading,
|
||||||
|
error: loadedError,
|
||||||
|
run: refreshData,
|
||||||
|
} = useRequest(
|
||||||
() => {
|
() => {
|
||||||
return listWorkflowRuns({
|
return listWorkflowRuns({
|
||||||
workflowId: workflowId,
|
workflowId: workflowId,
|
||||||
@ -156,8 +182,28 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleDeleteClick = (workflowRun: WorkflowRunModel) => {
|
||||||
|
modalApi.confirm({
|
||||||
|
title: t("workflow_run.action.delete"),
|
||||||
|
content: t("workflow_run.action.delete.confirm"),
|
||||||
|
onOk: async () => {
|
||||||
|
try {
|
||||||
|
const resp = await removeWorkflowRun(workflowRun);
|
||||||
|
if (resp) {
|
||||||
|
setTableData((prev) => prev.filter((item) => item.id !== workflowRun.id));
|
||||||
|
refreshData();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{ModelContextHolder}
|
||||||
{NotificationContextHolder}
|
{NotificationContextHolder}
|
||||||
|
|
||||||
<div className={className} style={style}>
|
<div className={className} style={style}>
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
{
|
{
|
||||||
|
"workflow_run.action.view": "View detail",
|
||||||
|
"workflow_run.action.delete": "Delete history run",
|
||||||
|
"workflow_run.action.delete.confirm": "Are you sure to delete this history run?",
|
||||||
|
|
||||||
"workflow_run.props.id": "ID",
|
"workflow_run.props.id": "ID",
|
||||||
"workflow_run.props.status": "Status",
|
"workflow_run.props.status": "Status",
|
||||||
"workflow_run.props.status.pending": "Pending",
|
"workflow_run.props.status.pending": "Pending",
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
{
|
{
|
||||||
|
"workflow_run.action.edit": "查看详情",
|
||||||
|
"workflow_run.action.delete": "删除执行历史",
|
||||||
|
"workflow_run.action.delete.confirm": "确定要删除此执行历史吗?",
|
||||||
|
|
||||||
"workflow_run.props.id": "ID",
|
"workflow_run.props.id": "ID",
|
||||||
"workflow_run.props.status": "状态",
|
"workflow_run.props.status": "状态",
|
||||||
"workflow_run.props.status.pending": "等待执行",
|
"workflow_run.props.status.pending": "等待执行",
|
||||||
|
@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
CheckCircleOutlined as CheckCircleOutlinedIcon,
|
CheckCircleOutlined as CheckCircleOutlinedIcon,
|
||||||
|
ClockCircleOutlined as ClockCircleOutlinedIcon,
|
||||||
CloseCircleOutlined as CloseCircleOutlinedIcon,
|
CloseCircleOutlined as CloseCircleOutlinedIcon,
|
||||||
DeleteOutlined as DeleteOutlinedIcon,
|
DeleteOutlined as DeleteOutlinedIcon,
|
||||||
EditOutlined as EditOutlinedIcon,
|
EditOutlined as EditOutlinedIcon,
|
||||||
@ -13,7 +14,6 @@ import {
|
|||||||
import { PageHeader } from "@ant-design/pro-components";
|
import { PageHeader } from "@ant-design/pro-components";
|
||||||
import { useRequest } from "ahooks";
|
import { useRequest } from "ahooks";
|
||||||
import {
|
import {
|
||||||
Badge,
|
|
||||||
Button,
|
Button,
|
||||||
Divider,
|
Divider,
|
||||||
Empty,
|
Empty,
|
||||||
@ -159,32 +159,23 @@ const WorkflowList = () => {
|
|||||||
key: "lastRun",
|
key: "lastRun",
|
||||||
title: t("workflow.props.last_run_at"),
|
title: t("workflow.props.last_run_at"),
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
if (record.lastRunId) {
|
let icon = <></>;
|
||||||
if (record.lastRunStatus === WORKFLOW_RUN_STATUSES.RUNNING) {
|
if (record.lastRunStatus === WORKFLOW_RUN_STATUSES.PENDING) {
|
||||||
return (
|
icon = <ClockCircleOutlinedIcon style={{ color: themeToken.colorTextSecondary }} />;
|
||||||
<Space>
|
} else if (record.lastRunStatus === WORKFLOW_RUN_STATUSES.RUNNING) {
|
||||||
<Badge status="processing" count={<SyncOutlinedIcon style={{ color: themeToken.colorInfo }} />} />
|
icon = <SyncOutlinedIcon style={{ color: themeToken.colorInfo }} spin />;
|
||||||
<Typography.Text>{dayjs(record.lastRunTime!).format("YYYY-MM-DD HH:mm:ss")}</Typography.Text>
|
} else if (record.lastRunStatus === WORKFLOW_RUN_STATUSES.SUCCEEDED) {
|
||||||
</Space>
|
icon = <CheckCircleOutlinedIcon style={{ color: themeToken.colorSuccess }} />;
|
||||||
);
|
} else if (record.lastRunStatus === WORKFLOW_RUN_STATUSES.FAILED) {
|
||||||
} else if (record.lastRunStatus === WORKFLOW_RUN_STATUSES.SUCCEEDED) {
|
icon = <CloseCircleOutlinedIcon style={{ color: themeToken.colorError }} />;
|
||||||
return (
|
|
||||||
<Space>
|
|
||||||
<Badge status="success" count={<CheckCircleOutlinedIcon style={{ color: themeToken.colorSuccess }} />} />
|
|
||||||
<Typography.Text>{dayjs(record.lastRunTime!).format("YYYY-MM-DD HH:mm:ss")}</Typography.Text>
|
|
||||||
</Space>
|
|
||||||
);
|
|
||||||
} else if (record.lastRunStatus === WORKFLOW_RUN_STATUSES.FAILED) {
|
|
||||||
return (
|
|
||||||
<Space>
|
|
||||||
<Badge status="error" count={<CloseCircleOutlinedIcon style={{ color: themeToken.colorError }} />} />
|
|
||||||
<Typography.Text>{dayjs(record.lastRunTime!).format("YYYY-MM-DD HH:mm:ss")}</Typography.Text>
|
|
||||||
</Space>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return <></>;
|
return (
|
||||||
|
<Space>
|
||||||
|
{icon}
|
||||||
|
<Typography.Text>{record.lastRunTime ? dayjs(record.lastRunTime!).format("YYYY-MM-DD HH:mm:ss") : ""}</Typography.Text>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -31,3 +31,7 @@ export const list = async (request: ListWorkflowRunsRequest) => {
|
|||||||
expand: request.expand ? "workflowId" : undefined,
|
expand: request.expand ? "workflowId" : undefined,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const remove = async (record: MaybeModelRecordWithId<WorkflowRunModel>) => {
|
||||||
|
return await getPocketBase().collection(COLLECTION_NAME).delete(record.id);
|
||||||
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user