import { type FormEvent, useEffect, useState, useRef } from "react"; import { ApiRequestError, type Employee, type Workspace, type WorkspaceMember, } from "../../services/api"; import { useApi, useAuth } from "../../context/AuthContext"; import Icon from "../../components/common/Icon"; import "../../styles/admin.css"; import "../../styles/dashboard.css"; import "../../styles/platform.css"; type Notice = { tone: "success" | "error" | "info"; message: string; }; export function DashboardPage({ scriptCount, online, onNavigate, }: { scriptCount: number; online: boolean; onNavigate: (page: "scripts" | "schedules" | "system") => void; }) { const { user, currentWorkspace } = useAuth(); return (
MODEL DEVELOPMENT PLATFORM

下午好,{user?.display_name ?? "用户"}

当前位于 {currentWorkspace?.workspace_name ?? "(未选择 Workspace)"} ,可以继续构建脚本或配置调度。

{online ? "服务正常" : "服务连接中"}
{scriptCount}工作副本
2Workspace
4平台用户
{online ? "正常" : "检查中"}平台状态
运行趋势

近 7 天调度执行

成功率 92.6%
{[38, 55, 44, 73, 61, 86, 78].map((value, index) => (
{Math.round(value / 7)} {["周一", "周二", "周三", "周四", "周五", "周六", "今天"][index]}
))}
成功 75失败 6
脚本资产

类型分布

{scriptCount}全部脚本
Notebook{Math.max(1, Math.round(scriptCount * .67))} 个 · 67% Python{Math.max(0, scriptCount - Math.round(scriptCount * .67))} 个 · 33% 稳定版本3 个已发布
ACTIVITY

最近平台活动

操作内容执行人状态时间
{[ ["数据探索.ipynb 发布稳定版本 v3.0", "张三", "成功", "16:42"], ["每日模型训练流程完成调度运行", "Scheduler", "成功", "15:25"], ["批量预测.py 更新工作副本", "王五", "已同步", "14:18"], ["风险验证流程完成 DAG 校验", "李四", "成功", "11:06"], ].map((row) => (
{row[0]} {row[1]}{row[2]}{row[3]}
))}
); } const EMPTY_FORM = { username: "", display_name: "", email: "", role_code: "developer" as "admin" | "developer", password: "", status: "active" as "active" | "disabled" | "locked", }; const EMPTY_PROJECT_FORM = { workspace_code: "", workspace_name: "", quota_bytes: 0, description: "", }; type ProjectWithMembers = Workspace & { member_count: number; members?: WorkspaceMember[]; }; export function SystemAdminPage({ onNotify, onConnectionChange, }: { onNotify: (notice: Notice) => void; onConnectionChange: (online: boolean) => void; }) { const [activeTab, setActiveTab] = useState<"users" | "projects">("users"); const api = useApi(); const { user, currentWorkspace } = useAuth(); const [employees, setEmployees] = useState([]); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [editing, setEditing] = useState(null); const [dialogOpen, setDialogOpen] = useState(false); const [form, setForm] = useState(EMPTY_FORM); const [userSearchTerm, setUserSearchTerm] = useState(""); // 项目管理状态 const [projects, setProjects] = useState([]); const [projectLoading, setProjectLoading] = useState(true); const [projectDialogOpen, setProjectDialogOpen] = useState(false); const [projectForm, setProjectForm] = useState(EMPTY_PROJECT_FORM); const [editingProject, setEditingProject] = useState(null); const [importMemberDialogOpen, setImportMemberDialogOpen] = useState(false); const [selectedProject, setSelectedProject] = useState(null); const [availableUsers, setAvailableUsers] = useState([]); const [selectedUserIds, setSelectedUserIds] = useState([]); const [selectedRoleCode, setSelectedRoleCode] = useState<"admin" | "developer">("developer"); const [projectSearchTerm, setProjectSearchTerm] = useState(""); const canManage = user?.role_code === "admin"; const load = async (): Promise => { setLoading(true); try { setEmployees(await api.listEmployees()); onConnectionChange(true); } catch (error) { onConnectionChange(false); onNotify({ tone: "error", message: error instanceof Error ? error.message : "用户列表加载失败", }); } finally { setLoading(false); } }; const loadProjects = async (): Promise => { setProjectLoading(true); try { const workspaceList = await api.listWorkspaces(); setProjects(workspaceList); onConnectionChange(true); } catch (error) { onConnectionChange(false); onNotify({ tone: "error", message: error instanceof Error ? error.message : "项目列表加载失败", }); } finally { setProjectLoading(false); } }; const loadAvailableUsers = async (): Promise => { try { const list = await api.listEmployees(); setAvailableUsers(list); } catch (error) { onNotify({ tone: "error", message: error instanceof Error ? error.message : "用户列表加载失败", }); } }; useEffect(() => { void load(); }, []); useEffect(() => { if (activeTab === "projects") { void loadProjects(); } }, [activeTab]); const openCreate = (): void => { setEditing(null); setForm(EMPTY_FORM); setDialogOpen(true); }; const openEdit = (employee: Employee): void => { setEditing(employee); setForm({ username: employee.username, display_name: employee.display_name, email: employee.email ?? "", role_code: employee.role_code, password: "", status: employee.status, }); setDialogOpen(true); }; const submit = async (event: React.FormEvent): Promise => { event.preventDefault(); if (!form.display_name.trim() || (!editing && !form.username.trim())) return; // 新建用户时密码必填 if (!editing && !form.password.trim()) { onNotify({ tone: "error", message: "请输入密码" }); return; } // 密码长度校验 8~72 字符 if (form.password && (form.password.length < 8 || form.password.length > 72)) { onNotify({ tone: "error", message: "密码长度必须在 8~72 字符之间" }); return; } setSaving(true); try { if (editing) { const updated = await api.updateEmployee(editing.user_id, { display_name: form.display_name.trim(), email: form.email.trim() || null, role_code: form.role_code, status: form.status, }); setEmployees((current) => current.map( (item) => item.user_id === updated.user_id ? updated : item, )); onNotify({ tone: "success", message: "用户信息已更新" }); } else { const created = await api.createEmployee({ username: form.username.trim(), display_name: form.display_name.trim(), email: form.email.trim() || null, role_code: form.role_code, password: form.password, }); setEmployees((current) => [...current, created]); onNotify({ tone: "success", message: "用户已添加" }); } setDialogOpen(false); } catch (error) { onNotify({ tone: "error", message: error instanceof ApiRequestError ? error.message : "保存用户失败", }); } finally { setSaving(false); } }; const remove = async (employee: Employee): Promise => { if (!window.confirm(`确定从当前 Workspace 删除用户"${employee.display_name}"吗?`)) return; try { await api.deleteEmployee(employee.user_id); setEmployees((current) => current.filter((item) => item.user_id !== employee.user_id)); onNotify({ tone: "success", message: "用户已删除" }); } catch (error) { onNotify({ tone: "error", message: error instanceof Error ? error.message : "删除用户失败", }); } }; const openCreateProject = (): void => { setEditingProject(null); setProjectForm(EMPTY_PROJECT_FORM); setProjectDialogOpen(true); }; const openEditProject = (project: Workspace): void => { setEditingProject(project); setProjectForm({ workspace_code: project.workspace_code, workspace_name: project.workspace_name, quota_bytes: project.quota_bytes, description: project.description ?? "", }); setProjectDialogOpen(true); }; const submitProject = async (event: React.FormEvent): Promise => { event.preventDefault(); if (!projectForm.workspace_name.trim()) { onNotify({ tone: "error", message: "请输入项目名称" }); return; } if (!projectForm.workspace_code.trim() && !editingProject) { onNotify({ tone: "error", message: "请输入项目编码" }); return; } setSaving(true); try { if (editingProject) { // 编辑项目 const updated = await api.updateWorkspace(editingProject.workspace_id, { workspace_name: projectForm.workspace_name.trim(), quota_bytes: projectForm.quota_bytes, description: projectForm.description.trim() || undefined, }); setProjects((current) => current.map((p) => (p.workspace_id === updated.workspace_id ? updated : p)) ); onNotify({ tone: "success", message: "项目信息已更新" }); } else { // 创建项目 const generatedCode = projectForm.workspace_code.trim() || projectForm.workspace_name.trim().toLowerCase().replace(/[^a-z0-9-]/g, "-").slice(0, 32); const created = await api.createWorkspace({ workspace_code: generatedCode, workspace_name: projectForm.workspace_name.trim(), quota_bytes: projectForm.quota_bytes, description: projectForm.description.trim() || undefined, }); setProjects((current) => [...current, created]); onNotify({ tone: "success", message: "项目已创建" }); } setProjectDialogOpen(false); } catch (error) { onNotify({ tone: "error", message: error instanceof ApiRequestError ? error.message : (editingProject ? "更新项目失败" : "创建项目失败"), }); } finally { setSaving(false); } }; const deleteProject = async (project: Workspace): Promise => { if (!window.confirm(`确定要删除项目"${project.workspace_name}"吗?此操作将级联软删所有成员。`)) return; try { await api.deleteWorkspace(project.workspace_id); setProjects((current) => current.filter((p) => p.workspace_id !== project.workspace_id)); onNotify({ tone: "success", message: "项目已删除" }); } catch (error) { onNotify({ tone: "error", message: error instanceof Error ? error.message : "删除项目失败", }); } }; const openImportMemberDialog = (project: Workspace): void => { setSelectedProject(project); setSelectedUserIds([]); setSelectedRoleCode("developer"); void loadAvailableUsers(); setImportMemberDialogOpen(true); }; const importMember = async (): Promise => { if (!selectedProject || selectedUserIds.length === 0) { onNotify({ tone: "error", message: "请选择要添加的用户" }); return; } setSaving(true); try { // 批量添加选中的用户 await Promise.all( selectedUserIds.map((userId) => api.addWorkspaceMember(selectedProject.workspace_id, { user_id: userId, role_code: selectedRoleCode, }) ) ); setImportMemberDialogOpen(false); onNotify({ tone: "success", message: `已添加 ${selectedUserIds.length} 名成员` }); } catch (error) { onNotify({ tone: "error", message: error instanceof ApiRequestError ? error.message : "添加成员失败", }); } finally { setSaving(false); } }; return (
{/*
系统管理

{activeTab === "users" ? "用户管理" : "项目管理"}

{currentWorkspace?.workspace_name ?? "(未选择 Workspace)"} {activeTab === "users" ? ` · ${employees.length} 名用户` : ""}

{activeTab === "users" && ( )}
*/}
{activeTab === "users" && (
setUserSearchTerm(event.target.value)} />
)} {activeTab === "projects" && (
setProjectSearchTerm(event.target.value)} />
)} {activeTab === "users" ? ( <> {!canManage &&
当前为开发人员,只能查看用户列表。
}
用户账号角色状态操作
{loading ? (

正在加载用户…

) : ( employees .filter((employee) => { const term = userSearchTerm.toLowerCase().trim(); if (!term) return true; return ( employee.display_name.toLowerCase().includes(term) || employee.username.toLowerCase().includes(term) || (employee.email && employee.email.toLowerCase().includes(term)) ); }) .map((employee) => { const isProtectedAdmin = employee.role_code === "admin"; return (
{employee.display_name.slice(0, 1)}{employee.display_name}{employee.email ?? "未设置邮箱"} {employee.username} {employee.role_name} {employee.status === "active" ? "正常" : employee.status === "disabled" ? "已停用" : "已锁定"}
); }) )}
{dialogOpen && (
EMPLOYEE

{editing ? "编辑用户" : "添加用户"}

void submit(event)}> {!editing && ( )} {editing && ( )}
)} ) : ( <> {!canManage &&
当前为开发人员,只能查看项目列表。
}
项目名称 项目编码 状态 配额 操作
{projectLoading ? (

正在加载项目…

) : projects.length === 0 ? (

暂无项目

) : ( projects .filter((project) => { const term = projectSearchTerm.toLowerCase().trim(); if (!term) return true; return ( project.workspace_name.toLowerCase().includes(term) || project.workspace_code.toLowerCase().includes(term) || (project.description && project.description.toLowerCase().includes(term)) ); }) .map((project) => (
{project.workspace_name.slice(0, 1)} {project.workspace_name} {project.description ?? "无描述"} {project.workspace_code} {project.status === "active" ? "正常" : project.status === "archived" ? "已归档" : "已删除"} {project.quota_bytes > 0 ? `${(project.quota_bytes / 1024 / 1024 / 1024).toFixed(1)} GB` : "无限制"}
)) )}
)} {projectDialogOpen && (
PROJECT

{editingProject ? "编辑项目" : "新建项目"}

void submitProject(event)}> {!editingProject && ( )}