- Monorepo: NestJS API (Fastify) + React web (antd/ProComponents) + shared packages - PostgreSQL/Prisma (unified resources, relations graph, audit, events, jobs) - Auth/RBAC/workspace isolation, credential vault (AES-256-GCM) - Servers/SSH, Docker discovery & lifecycle, Job Center - Websites & deployments (git provider credential clone, env, health verification) - Domains/DNS (Cloudflare adapter), certificates (TLS probe), monitoring - Automation (cron/event/webhook HMAC + notifications), AI Gateway, MCP (29 tools) - AI chat window (LLM function-calling + rules fallback, SSE streaming, approvals) - Notes: Vditor WYSIWYG markdown editor, non-blocking autosave - Tests: 35 unit/security tests; UI audit clean (desktop+mobile, deep links)
69 lines
2.0 KiB
TypeScript
69 lines
2.0 KiB
TypeScript
/**
|
|
* # 容器列表页(全工作区)
|
|
*/
|
|
import { useRef } from "react";
|
|
import {
|
|
PageContainer,
|
|
ProTable,
|
|
type ActionType,
|
|
type ProColumns,
|
|
} from "@ant-design/pro-components";
|
|
import { App as AntApp, Popconfirm } from "antd";
|
|
import { ReloadOutlined } from "@ant-design/icons";
|
|
import { containerApi } from "../services/api";
|
|
import type { ContainerView } from "@devos/types";
|
|
import { StatusBadge } from "../components/shared";
|
|
|
|
export default function ContainersPage() {
|
|
const { message } = AntApp.useApp();
|
|
const actionRef = useRef<ActionType>(null);
|
|
|
|
const columns: ProColumns<ContainerView>[] = [
|
|
{ title: "名称", dataIndex: "name" },
|
|
{ title: "镜像", dataIndex: "image", ellipsis: true },
|
|
{ title: "状态", dataIndex: "state", render: (_, r) => <StatusBadge status={r.state} /> },
|
|
{ title: "容器 ID", dataIndex: "containerId", copyable: true },
|
|
{
|
|
title: "操作",
|
|
valueType: "option",
|
|
render: (_, record) => [
|
|
<Popconfirm
|
|
key="restart"
|
|
title={`重启容器 ${record.name}?(HIGH 风险操作)`}
|
|
onConfirm={async () => {
|
|
try {
|
|
await containerApi.restart(record.id);
|
|
message.success("容器已重启");
|
|
actionRef.current?.reload();
|
|
} catch (e) {
|
|
message.error(e instanceof Error ? e.message : "失败");
|
|
}
|
|
}}
|
|
>
|
|
<a style={{ color: "#fa8c16" }}>
|
|
<ReloadOutlined /> 重启
|
|
</a>
|
|
</Popconfirm>,
|
|
],
|
|
},
|
|
];
|
|
|
|
return (
|
|
<PageContainer>
|
|
<ProTable<ContainerView>
|
|
scroll={{ x: "max-content" }}
|
|
rowKey="id"
|
|
columns={columns}
|
|
actionRef={actionRef}
|
|
headerTitle="容器"
|
|
search={false}
|
|
pagination={{ pageSize: 20 }}
|
|
request={async () => {
|
|
const items = await containerApi.list();
|
|
return { data: items, success: true, total: items.length };
|
|
}}
|
|
/>
|
|
</PageContainer>
|
|
);
|
|
}
|