From ebbfc2db1899ec13c4f0da9b22a3b13b4a39dc58 Mon Sep 17 00:00:00 2001 From: weichen Date: Sat, 12 Sep 2026 02:53:09 +0000 Subject: [PATCH] feat(web): add admin workflows and template library tab Admin console can list workflow definitions, validate/publish draft versions, and view bootstrap template assets from the server API. Co-authored-by: Cursor --- web/src/api/workflowAdmin.ts | 82 +++++++++ .../components/admin/AdminSettingsModal.tsx | 174 +++++++++++++++++- 2 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 web/src/api/workflowAdmin.ts diff --git a/web/src/api/workflowAdmin.ts b/web/src/api/workflowAdmin.ts new file mode 100644 index 00000000..7ff33a0e --- /dev/null +++ b/web/src/api/workflowAdmin.ts @@ -0,0 +1,82 @@ +import { authClient } from './auth'; + +export interface WorkflowDefinitionSummary { + id: number; + key: string; + name: string; + description: string; + status: string; + latest_published_version: number; + product_domain?: string; +} + +export interface WorkflowVersionSummary { + id: number; + workflow_id: number; + version: number; + status: string; + definition_hash: string; +} + +export interface ValidationIssue { + code: string; + message: string; + field?: string; +} + +async function adminFetch(path: string, init?: RequestInit): Promise { + const token = authClient.getSessionToken(); + return fetch(path, { + ...init, + headers: { + ...(init?.headers ?? {}), + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + }); +} + +export async function listAdminWorkflows(): Promise { + const resp = await adminFetch('/api/v1/admin/workflows'); + if (!resp.ok) throw new Error('获取工作流列表失败'); + const data = await resp.json(); + return data.workflows ?? []; +} + +export async function getAdminWorkflow(id: number): Promise<{ + definition: WorkflowDefinitionSummary; + versions: WorkflowVersionSummary[]; +}> { + const resp = await adminFetch(`/api/v1/admin/workflows/${id}`); + if (!resp.ok) throw new Error('获取工作流详情失败'); + return resp.json(); +} + +export async function validateWorkflowVersion(workflowId: number, version: number): Promise<{ + valid: boolean; + issues: ValidationIssue[]; +}> { + const resp = await adminFetch(`/api/v1/admin/workflows/${workflowId}/versions/${version}/validate`, { + method: 'POST', + }); + if (!resp.ok) throw new Error('校验失败'); + return resp.json(); +} + +export async function publishWorkflowVersion(workflowId: number, version: number): Promise { + const resp = await adminFetch(`/api/v1/admin/workflows/${workflowId}/versions/${version}/publish`, { + method: 'POST', + }); + if (!resp.ok) { + const txt = await resp.text(); + throw new Error(txt || '发布失败'); + } +} + +export async function listAdminTemplates(): Promise< + Array<{ id: number; asset_key: string; name: string; status: string }> +> { + const resp = await adminFetch('/api/v1/admin/templates'); + if (!resp.ok) throw new Error('获取模板列表失败'); + const data = await resp.json(); + return data.templates ?? []; +} diff --git a/web/src/components/admin/AdminSettingsModal.tsx b/web/src/components/admin/AdminSettingsModal.tsx index 7c44ff8e..51f6f68e 100644 --- a/web/src/components/admin/AdminSettingsModal.tsx +++ b/web/src/components/admin/AdminSettingsModal.tsx @@ -14,11 +14,21 @@ import { Shield, Search, UserPlus, + GitBranch, } from 'lucide-react'; import { UserProfile } from '../../types/docgov'; import { RoleDefinition, PermissionKey } from '../../types/rbac'; import { SystemAuthConfig, UserAccount } from '../../types/auth'; import { authClient } from '../../api/auth'; +import { + listAdminWorkflows, + getAdminWorkflow, + validateWorkflowVersion, + publishWorkflowVersion, + listAdminTemplates, + WorkflowDefinitionSummary, + WorkflowVersionSummary, +} from '../../api/workflowAdmin'; export interface AdminSettingsModalProps { isOpen: boolean; @@ -96,7 +106,9 @@ export const AdminSettingsModal: React.FC = ({ onUpdateSystemConfig, env = 'dev', }) => { - const [activeTab, setActiveTab] = useState<'users' | 'approvals' | 'policy' | 'roles' | 'status'>('users'); + const [activeTab, setActiveTab] = useState< + 'users' | 'approvals' | 'policy' | 'roles' | 'workflows' | 'status' + >('users'); // 全量账号列表(含待审核及禁用状态) const [adminUsers, setAdminUsers] = useState([]); @@ -124,6 +136,12 @@ export const AdminSettingsModal: React.FC = ({ const [allowReg, setAllowReg] = useState(systemConfig.allowRegistration); const [requireAppr, setRequireAppr] = useState(systemConfig.requireApproval); + const [adminWorkflows, setAdminWorkflows] = useState([]); + const [selectedWorkflowId, setSelectedWorkflowId] = useState(null); + const [workflowVersions, setWorkflowVersions] = useState([]); + const [adminTemplates, setAdminTemplates] = useState>([]); + const [validationMsg, setValidationMsg] = useState(''); + // 刷新全量用户列表 const refreshUsers = async () => { try { @@ -134,6 +152,52 @@ export const AdminSettingsModal: React.FC = ({ } }; + const refreshWorkflows = async () => { + try { + const list = await listAdminWorkflows(); + setAdminWorkflows(list); + const templates = await listAdminTemplates(); + setAdminTemplates(templates.map((t) => ({ asset_key: t.asset_key, name: t.name }))); + } catch { + setAdminWorkflows([]); + } + }; + + const loadWorkflowDetail = async (id: number) => { + setSelectedWorkflowId(id); + try { + const detail = await getAdminWorkflow(id); + setWorkflowVersions(detail.versions ?? []); + } catch { + setWorkflowVersions([]); + } + }; + + const handleValidateVersion = async (workflowId: number, version: number) => { + try { + const res = await validateWorkflowVersion(workflowId, version); + if (res.valid) { + setValidationMsg(`v${version} 校验通过`); + } else { + setValidationMsg(res.issues.map((i) => i.message).join(';')); + } + } catch (err: any) { + setValidationMsg(err.message || '校验失败'); + } + }; + + const handlePublishVersion = async (workflowId: number, version: number) => { + if (!window.confirm(`确认发布工作流版本 v${version}?发布后不可原地修改。`)) return; + try { + await publishWorkflowVersion(workflowId, version); + showNotification('success', `已发布 v${version}`); + refreshWorkflows(); + loadWorkflowDetail(workflowId); + } catch (err: any) { + showNotification('error', err.message || '发布失败'); + } + }; + const refreshRoles = async () => { try { const list = await authClient.listRoles(); @@ -147,6 +211,7 @@ export const AdminSettingsModal: React.FC = ({ if (isOpen) { refreshUsers(); refreshRoles(); + refreshWorkflows(); setAllowReg(systemConfig.allowRegistration); setRequireAppr(systemConfig.requireApproval); } @@ -587,6 +652,28 @@ export const AdminSettingsModal: React.FC = ({ RBAC 角色权限矩阵 + + + ))} + +
+ {selectedWorkflowId && ( + <> +
版本列表
+ {workflowVersions.map((v) => ( +
+
+ v{v.version} · {v.status} +
+
+ hash: {v.definition_hash.slice(0, 16)}… +
+
+ + {v.status === 'draft' && ( + + )} +
+
+ ))} + {validationMsg && ( +
{validationMsg}
+ )} + + )} +
+ +
+
模板库 ({adminTemplates.length})
+
+ {adminTemplates.map((t) => t.asset_key).join(', ') || '暂无模板(需 PG 环境 bootstrap)'} +
+
+ + )} + {activeTab === 'status' && (