mirror of
https://github.com/certimate-go/certimate.git
synced 2026-06-16 21:09:50 +08:00
feat(ui): new WorkflowDetail page
This commit is contained in:
parent
a37c42cb0b
commit
488960397a
@ -125,7 +125,7 @@ const WorkflowRunLogs = ({ runId, runStatus }: { runId: string; runStatus: strin
|
||||
{Object.entries(record.data).map(([key, value]) => (
|
||||
<div key={key} className="flex space-x-2" style={{ wordBreak: "break-word" }}>
|
||||
<div className="whitespace-nowrap">{key}:</div>
|
||||
<div className={!showWhitespace ? "whitespace-pre-line" : ""}>{JSON.stringify(value)}</div>
|
||||
<div className={showWhitespace ? "whitespace-normal" : "whitespace-pre-line"}>{JSON.stringify(value)}</div>
|
||||
</div>
|
||||
))}
|
||||
</details>
|
||||
@ -263,6 +263,94 @@ const WorkflowRunLogs = ({ runId, runStatus }: { runId: string; runStatus: strin
|
||||
);
|
||||
};
|
||||
|
||||
const WorkflowRunArtifacts = ({ runId }: { runId: string }) => {};
|
||||
const WorkflowRunArtifacts = ({ runId }: { runId: string }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { notification } = App.useApp();
|
||||
|
||||
const tableColumns: TableProps<CertificateModel>["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"),
|
||||
render: (_, record) => {
|
||||
return (
|
||||
<div className="max-w-full truncate">
|
||||
<Typography.Text delete={!!record.deleted} ellipsis>
|
||||
{record.subjectAltNames}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "$action",
|
||||
align: "end",
|
||||
width: 32,
|
||||
render: (_, record) => (
|
||||
<div className="flex items-center justify-end">
|
||||
<CertificateDetailDrawer
|
||||
data={record}
|
||||
trigger={
|
||||
<Tooltip title={t("common.button.view")}>
|
||||
<Button color="primary" disabled={!!record.deleted} icon={<IconBrowserShare size="1.25em" />} variant="text" />
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
const [tableData, setTableData] = useState<CertificateModel[]>([]);
|
||||
const { loading: tableLoading } = useRequest(
|
||||
() => {
|
||||
return listCertificatesByWorkflowRunId(runId);
|
||||
},
|
||||
{
|
||||
refreshDeps: [runId],
|
||||
onSuccess: (res) => {
|
||||
setTableData(res.items);
|
||||
},
|
||||
onError: (err) => {
|
||||
if (err instanceof ClientResponseError && err.isAbort) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(err);
|
||||
notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
|
||||
|
||||
throw err;
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography.Title level={5}>{t("workflow_run.artifacts")}</Typography.Title>
|
||||
<Table<CertificateModel>
|
||||
columns={tableColumns}
|
||||
dataSource={tableData}
|
||||
loading={tableLoading}
|
||||
locale={{
|
||||
emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />,
|
||||
}}
|
||||
pagination={false}
|
||||
rowKey={(record) => record.id}
|
||||
size="small"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default WorkflowRunDetail;
|
||||
|
||||
@ -11,8 +11,11 @@
|
||||
|
||||
"access.action.create.button": "Create credential",
|
||||
"access.action.create.modal.title": "Create credential",
|
||||
"access.action.edit.button": "Edit credential",
|
||||
"access.action.edit.modal.title": "Edit credential",
|
||||
"access.action.duplicate.button": "Duplicate credential",
|
||||
"access.action.duplicate.modal.title": "Duplicate credential",
|
||||
"access.action.delete.button": "Delete credential",
|
||||
"access.action.delete.modal.title": "Delete \"{{name}}\"",
|
||||
"access.action.delete.modal.content": "Are you sure want to delete this credential? <br>This action cannot be undone.",
|
||||
"access.action.batch_delete.modal.title": "Delete credentials",
|
||||
|
||||
@ -8,6 +8,8 @@
|
||||
|
||||
"certificate.search.placeholder": "Search by certificate name or serial number ...",
|
||||
|
||||
"certificate.action.view.button": "View details",
|
||||
"certificate.action.delete.button": "Delete certificate",
|
||||
"certificate.action.delete.modal.title": "Delete \"{{name}}\"",
|
||||
"certificate.action.delete.modal.content": "Are you sure want to delete this certificate? <br>This action cannot be undone.",
|
||||
"certificate.action.batch_delete.modal.title": "Delete certificates",
|
||||
|
||||
@ -9,8 +9,11 @@
|
||||
"workflow.search.placeholder": "Search by workflow name ...",
|
||||
|
||||
"workflow.action.create.button": "Create workflow",
|
||||
"workflow.action.edit.button": "Edit workflow",
|
||||
"workflow.action.duplicate.button": "Duplicate workflow",
|
||||
"workflow.action.duplicate.modal.title": "Duplicate \"{{name}}\"",
|
||||
"workflow.action.duplicate.modal.content": "Are you sure to duplicate this workflow?",
|
||||
"workflow.action.delete.button": "Delete workflow",
|
||||
"workflow.action.delete.modal.title": "Delete \"{{name}}\"",
|
||||
"workflow.action.delete.modal.content": "Are you sure want to delete this workflow? <br>This action cannot be undone.",
|
||||
"workflow.action.batch_delete.modal.title": "Delete workflows",
|
||||
|
||||
@ -10,8 +10,11 @@
|
||||
|
||||
"access.action.create.button": "新建授权",
|
||||
"access.action.create.modal.title": "新建授权",
|
||||
"access.action.edit.button": "编辑授权",
|
||||
"access.action.edit.modal.title": "编辑授权",
|
||||
"access.action.duplicate.button": "复制授权",
|
||||
"access.action.duplicate.modal.title": "复制授权",
|
||||
"access.action.delete.button": "删除授权",
|
||||
"access.action.delete.modal.title": "删除「{{name}}」",
|
||||
"access.action.delete.modal.content": "确定要删除该授权吗?<br>注意此操作不可撤销,请谨慎操作。",
|
||||
"access.action.batch_delete.modal.title": "删除授权",
|
||||
|
||||
@ -8,6 +8,8 @@
|
||||
|
||||
"certificate.search.placeholder": "按证书名称或序列号搜索……",
|
||||
|
||||
"certificate.action.view.button": "查看详情",
|
||||
"certificate.action.delete.button": "删除证书",
|
||||
"certificate.action.delete.modal.title": "删除「{{name}}」",
|
||||
"certificate.action.delete.modal.content": "确定要删除该证书吗?<br>注意此操作不可撤销,请谨慎操作。",
|
||||
"certificate.action.batch_delete.modal.title": "删除证书",
|
||||
|
||||
@ -9,8 +9,11 @@
|
||||
"workflow.search.placeholder": "按工作流名称搜索……",
|
||||
|
||||
"workflow.action.create.button": "新建工作流",
|
||||
"workflow.action.edit.button": "编辑工作流",
|
||||
"workflow.action.duplicate.button": "复制工作流",
|
||||
"workflow.action.duplicate.modal.title": "复制「{{name}}」",
|
||||
"workflow.action.duplicate.modal.content": "确定要复制该工作流吗?",
|
||||
"workflow.action.delete.button": "删除工作流",
|
||||
"workflow.action.delete.modal.title": "删除「{{name}}」",
|
||||
"workflow.action.delete.modal.content": "确定要删除该工作流吗?<br>注意此操作不可撤销,请谨慎操作。",
|
||||
"workflow.action.batch_delete.modal.title": "删除工作流",
|
||||
|
||||
@ -12,7 +12,7 @@ import {
|
||||
IconMenu2,
|
||||
IconSettings,
|
||||
} from "@tabler/icons-react";
|
||||
import { Alert, Button, Divider, Drawer, Layout, Menu, type MenuProps, theme } from "antd";
|
||||
import { Alert, Button, Drawer, Layout, Menu, type MenuProps, theme } from "antd";
|
||||
|
||||
import AppLocale from "@/components/AppLocale";
|
||||
import AppTheme from "@/components/AppTheme";
|
||||
@ -103,7 +103,25 @@ const ConsoleLayout = () => {
|
||||
</Layout.Sider>
|
||||
|
||||
<Layout className="flex flex-col overflow-hidden">
|
||||
<Layout.Header className="border-b shadow-sm md:hidden" style={{ padding: 0, borderBottomColor: themeToken.colorBorderSecondary }}>
|
||||
<Layout.Header
|
||||
className="relative border-b shadow-sm md:hidden"
|
||||
style={{
|
||||
padding: 0,
|
||||
borderBottomColor: themeToken.colorBorderSecondary,
|
||||
}}
|
||||
>
|
||||
<div className="absolute inset-0 z-0">
|
||||
<div
|
||||
className="h-full w-full"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"linear-gradient(rgba(255, 255, 255, 0.063) 1px, transparent 1px), linear-gradient(90deg, rgba(255, 255, 255, 0.063) 1px, transparent 1px)",
|
||||
backgroundSize: "20px 20px",
|
||||
}}
|
||||
>
|
||||
<div className="h-full w-full backdrop-blur-[1px]"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex size-full items-center justify-between overflow-hidden px-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<SiderMenuDrawer trigger={<Button icon={<IconMenu2 size="1.25em" stroke="1.25" />} />} />
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { IconCirclePlus, IconCopy, IconDotsVertical, IconFingerprint, IconPlus, IconReload, IconTrash } from "@tabler/icons-react";
|
||||
import { IconCirclePlus, IconCopy, IconDotsVertical, IconEdit, IconFingerprint, IconPlus, IconReload, IconTrash } from "@tabler/icons-react";
|
||||
import { useRequest } from "ahooks";
|
||||
import { App, Avatar, Button, Dropdown, Input, Skeleton, Table, type TableProps, Tabs, Typography, theme } from "antd";
|
||||
import dayjs from "dayjs";
|
||||
@ -87,9 +87,21 @@ const AccessList = () => {
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: "edit",
|
||||
label: t("access.action.edit.button"),
|
||||
icon: (
|
||||
<span className="anticon scale-125">
|
||||
<IconEdit size="1em" />
|
||||
</span>
|
||||
),
|
||||
onClick: () => {
|
||||
handleRecordDetailClick(record);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "duplicate",
|
||||
label: t("common.button.duplicate"),
|
||||
label: t("access.action.duplicate.button"),
|
||||
icon: (
|
||||
<span className="anticon scale-125">
|
||||
<IconCopy size="1em" />
|
||||
@ -104,7 +116,7 @@ const AccessList = () => {
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
label: t("common.button.delete"),
|
||||
label: t("access.action.delete.button"),
|
||||
danger: true,
|
||||
icon: (
|
||||
<span className="anticon scale-125">
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { IconCertificate, IconDotsVertical, IconExternalLink, IconReload, IconTrash } from "@tabler/icons-react";
|
||||
import { IconBrowserShare, IconCertificate, IconDotsVertical, IconExternalLink, IconReload, IconTrash } from "@tabler/icons-react";
|
||||
import { useRequest } from "ahooks";
|
||||
import { App, Button, Dropdown, Input, Segmented, Skeleton, Table, type TableProps, Typography, theme } from "antd";
|
||||
import dayjs from "dayjs";
|
||||
@ -146,9 +146,24 @@ const CertificateList = () => {
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: "view",
|
||||
label: t("certificate.action.view.button"),
|
||||
icon: (
|
||||
<span className="anticon scale-125">
|
||||
<IconBrowserShare size="1em" />
|
||||
</span>
|
||||
),
|
||||
onClick: () => {
|
||||
handleRecordDetailClick(record);
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "divider",
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
label: t("common.button.delete"),
|
||||
label: t("certificate.action.delete.button"),
|
||||
danger: true,
|
||||
icon: (
|
||||
<span className="anticon scale-125">
|
||||
|
||||
@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { IconArrowBackUp, IconChevronDown, IconDots, IconHistory, IconPlayerPlay, IconRobot, IconTrash } from "@tabler/icons-react";
|
||||
import { Alert, App, Button, Card, Dropdown, Flex, Form, Input, Space, Tabs } from "antd";
|
||||
import { Alert, App, Button, Card, Dropdown, Flex, Form, Input, Segmented, Space, Tabs } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { isEqual } from "radash";
|
||||
import { z } from "zod";
|
||||
@ -194,55 +194,53 @@ const WorkflowDetail = () => {
|
||||
|
||||
return (
|
||||
<div className="flex size-full flex-col">
|
||||
<Card styles={{ body: { padding: 0 } }}>
|
||||
<div className="px-6 py-4">
|
||||
<div className="mx-auto max-w-320">
|
||||
<div className="flex justify-between gap-2">
|
||||
<div>
|
||||
<h1>{workflow.name || "\u00A0"}</h1>
|
||||
<p className="mb-0 text-base text-gray-500">{workflow.description || "\u00A0"}</p>
|
||||
</div>
|
||||
<Flex gap="small">
|
||||
{initialized
|
||||
? [
|
||||
<WorkflowBaseInfoModal key="edit" trigger={<Button>{t("common.button.edit")}</Button>} />,
|
||||
<Button key="enable" onClick={handleEnableChange}>
|
||||
{workflow.enabled ? t("workflow.action.disable.button") : t("workflow.action.enable.button")}
|
||||
</Button>,
|
||||
<Dropdown
|
||||
key="more"
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: "delete",
|
||||
label: t("common.button.delete"),
|
||||
danger: true,
|
||||
icon: <IconTrash size="1.25em" />,
|
||||
onClick: () => {
|
||||
handleDeleteClick();
|
||||
},
|
||||
},
|
||||
],
|
||||
}}
|
||||
trigger={["click"]}
|
||||
>
|
||||
<Button icon={<IconChevronDown size="1.25em" />} iconPosition="end">
|
||||
{t("common.button.more")}
|
||||
</Button>
|
||||
</Dropdown>,
|
||||
]
|
||||
: []}
|
||||
</Flex>
|
||||
<div className="px-6 py-4">
|
||||
<div className="relative mx-auto max-w-320">
|
||||
<div className="flex justify-between gap-2">
|
||||
<div>
|
||||
<h1>{workflow.name || "\u00A0"}</h1>
|
||||
<p className="mb-0 text-base text-gray-500">{workflow.description || "\u00A0"}</p>
|
||||
</div>
|
||||
<Flex className="my-2" gap="small">
|
||||
{initialized
|
||||
? [
|
||||
<WorkflowBaseInfoModal key="edit" trigger={<Button>{t("common.button.edit")}</Button>} />,
|
||||
<Button key="enable" onClick={handleEnableChange}>
|
||||
{workflow.enabled ? t("workflow.action.disable.button") : t("workflow.action.enable.button")}
|
||||
</Button>,
|
||||
<Dropdown
|
||||
key="more"
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: "delete",
|
||||
label: t("common.button.delete"),
|
||||
danger: true,
|
||||
icon: <IconTrash size="1.25em" />,
|
||||
onClick: () => {
|
||||
handleDeleteClick();
|
||||
},
|
||||
},
|
||||
],
|
||||
}}
|
||||
trigger={["click"]}
|
||||
>
|
||||
<Button icon={<IconChevronDown size="1.25em" />} iconPosition="end">
|
||||
{t("common.button.more")}
|
||||
</Button>
|
||||
</Dropdown>,
|
||||
]
|
||||
: []}
|
||||
</Flex>
|
||||
</div>
|
||||
|
||||
<Tabs
|
||||
className="-mb-4"
|
||||
activeKey={tabValue}
|
||||
defaultActiveKey="orchestration"
|
||||
items={[
|
||||
<div className="absolute -bottom-12 left-1/2 z-1 -translate-x-1/2">
|
||||
<Segmented
|
||||
className="shadow"
|
||||
options={[
|
||||
{
|
||||
key: "orchestration",
|
||||
label: t("workflow.detail.orchestration.tab"),
|
||||
value: "orchestration",
|
||||
label: <span className="px-2 text-sm">{t("workflow.detail.orchestration.tab")}</span>,
|
||||
icon: (
|
||||
<span className="anticon scale-125" role="img">
|
||||
<IconRobot size="1em" />
|
||||
@ -250,8 +248,8 @@ const WorkflowDetail = () => {
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "runs",
|
||||
label: t("workflow.detail.runs.tab"),
|
||||
value: "runs",
|
||||
label: <span className="px-2 text-sm">{t("workflow.detail.runs.tab")}</span>,
|
||||
icon: (
|
||||
<span className="anticon scale-125" role="img">
|
||||
<IconHistory size="1em" />
|
||||
@ -259,13 +257,16 @@ const WorkflowDetail = () => {
|
||||
),
|
||||
},
|
||||
]}
|
||||
renderTabBar={(props, DefaultTabBar) => <DefaultTabBar {...props} style={{ margin: 0 }} />}
|
||||
tabBarStyle={{ border: "none" }}
|
||||
onChange={(key) => setTabValue(key as typeof tabValue)}
|
||||
size="large"
|
||||
value={tabValue}
|
||||
defaultValue="orchestration"
|
||||
onChange={(value) => {
|
||||
setTabValue(value as typeof tabValue);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Show when={tabValue === "orchestration"}>
|
||||
<div className="min-h-[360px] flex-1 overflow-hidden p-4">
|
||||
@ -280,7 +281,7 @@ const WorkflowDetail = () => {
|
||||
}}
|
||||
loading={!initialized}
|
||||
>
|
||||
<div className="absolute inset-x-6 top-4 z-2 mx-auto flex max-w-320 items-center justify-between gap-4">
|
||||
<div className="absolute inset-x-6 top-4 z-2 mx-auto flex max-w-320 items-center justify-between gap-4 pt-6">
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<Show when={workflow.hasDraft!}>
|
||||
<Alert message={<div className="truncate">{t("workflow.detail.orchestration.draft.alert")}</div>} showIcon type="warning" />
|
||||
@ -316,14 +317,14 @@ const WorkflowDetail = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<WorkflowElementsContainer className="pt-16" />
|
||||
<WorkflowElementsContainer className="pt-24" />
|
||||
</Card>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={tabValue === "runs"}>
|
||||
<div className="p-4">
|
||||
<div className="mx-auto max-w-320">
|
||||
<div className="mx-auto max-w-320 pt-6">
|
||||
<WorkflowRuns workflowId={workflowId!} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { IconCirclePlus, IconCopy, IconDotsVertical, IconHierarchy3, IconPlus, IconReload, IconTrash } from "@tabler/icons-react";
|
||||
import { IconCirclePlus, IconCopy, IconDotsVertical, IconEdit, IconHierarchy3, IconPlus, IconReload, IconTrash } from "@tabler/icons-react";
|
||||
import { useRequest } from "ahooks";
|
||||
import { App, Button, Dropdown, Flex, Input, Segmented, Skeleton, Switch, Table, type TableProps, Typography, theme } from "antd";
|
||||
import dayjs from "dayjs";
|
||||
@ -133,9 +133,21 @@ const WorkflowList = () => {
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: "edit",
|
||||
label: t("workflow.action.edit.button"),
|
||||
icon: (
|
||||
<span className="anticon scale-125">
|
||||
<IconEdit size="1em" />
|
||||
</span>
|
||||
),
|
||||
onClick: () => {
|
||||
handleRecordDetailClick(record);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "duplicate",
|
||||
label: t("common.button.duplicate"),
|
||||
label: t("workflow.action.duplicate.button"),
|
||||
icon: (
|
||||
<span className="anticon scale-125">
|
||||
<IconCopy size="1em" />
|
||||
@ -150,7 +162,7 @@ const WorkflowList = () => {
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
label: t("common.button.delete"),
|
||||
label: t("workflow.action.delete.button"),
|
||||
danger: true,
|
||||
icon: (
|
||||
<span className="anticon scale-125">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user