import { create } from "zustand"; import { ApiRequestError, type Employee, type Workspace, type WorkspaceBoundApi, } from "../../../services/api"; import { useScriptWorkspaceStore } from "../../platform/state/scriptWorkspaceStore"; import { useUiStore } from "../../platform/state/uiStore"; type Notice = { tone: "success" | "error" | "info"; message: string; }; // ---- Users domain ---- export type UserForm = { username: string; display_name: string; email: string; role_code: "admin" | "developer"; password: string; status: "active" | "disabled" | "locked"; }; export const EMPTY_USER_FORM: UserForm = { username: "", display_name: "", email: "", role_code: "developer", password: "", status: "active", }; // ---- Projects domain ---- export type ProjectForm = { workspace_code: string; workspace_name: string; quota_bytes: number; description: string; }; export const EMPTY_PROJECT_FORM: ProjectForm = { workspace_code: "", workspace_name: "", quota_bytes: 0, description: "", }; // ---- Module-level non-reactive holder ---- let _api: WorkspaceBoundApi | null = null; export const bindAdminApi = (api: WorkspaceBoundApi | null) => { _api = api; }; function requireApi(): WorkspaceBoundApi { if (!_api) throw new Error("admin API 未绑定"); return _api; } function pushToast(notice: Notice) { useUiStore.getState().pushToast(notice); } function setApiOnline(online: boolean) { useScriptWorkspaceStore.getState().setApiOnline(online); } // ---- Store ---- type State = { // users employees: Employee[]; usersLoading: boolean; usersSaving: boolean; editingUser: Employee | null; userDialogOpen: boolean; userForm: UserForm; userSearchTerm: string; // projects projects: Workspace[]; projectsLoading: boolean; projectsSaving: boolean; editingProject: Workspace | null; projectDialogOpen: boolean; projectForm: ProjectForm; selectedProject: Workspace | null; availableUsers: Employee[]; selectedUserIds: string[]; selectedRoleCode: "admin" | "developer"; projectSearchTerm: string; importMemberDialogOpen: boolean; }; type Actions = { bindApi: (api: WorkspaceBoundApi | null) => void; reset: () => void; // users setters setEmployees: (updater: (current: Employee[]) => Employee[]) => void; setUsersLoading: (loading: boolean) => void; setUsersSaving: (saving: boolean) => void; setEditingUser: (e: Employee | null) => void; setUserDialogOpen: (open: boolean) => void; setUserForm: (form: UserForm) => void; setUserSearchTerm: (k: string) => void; // projects setters setProjects: (updater: (current: Workspace[]) => Workspace[]) => void; setProjectsLoading: (loading: boolean) => void; setProjectsSaving: (saving: boolean) => void; setEditingProject: (p: Workspace | null) => void; setProjectDialogOpen: (open: boolean) => void; setProjectForm: (form: ProjectForm) => void; setSelectedProject: (p: Workspace | null) => void; setAvailableUsers: (users: Employee[]) => void; setSelectedUserIds: (ids: string[]) => void; setSelectedRoleCode: (r: "admin" | "developer") => void; setProjectSearchTerm: (k: string) => void; setImportMemberDialogOpen: (open: boolean) => void; // users actions loadEmployees: () => Promise; createEmployee: ( input: { username: string; display_name: string; email?: string | null; role_code: "admin" | "developer"; password: string; status?: "active" | "disabled" | "locked"; }, onNotify: (n: Notice) => void, onConnectionChange: (online: boolean) => void, ) => Promise; updateEmployee: ( employee: Employee, input: { display_name: string; email?: string | null; role_code: "admin" | "developer"; status: "active" | "disabled" | "locked"; }, onNotify: (n: Notice) => void, onConnectionChange: (online: boolean) => void, ) => Promise; deleteEmployee: ( employee: Employee, onNotify: (n: Notice) => void, onConnectionChange: (online: boolean) => void, ) => Promise; // projects actions loadProjects: ( onNotify: (n: Notice) => void, onConnectionChange: (online: boolean) => void, ) => Promise; createProject: ( input: { workspace_code: string; workspace_name: string; quota_bytes: number; description?: string; }, onNotify: (n: Notice) => void, onConnectionChange: (online: boolean) => void, ) => Promise; updateProject: ( workspace: Workspace, input: { workspace_name: string; quota_bytes: number; description?: string; }, onNotify: (n: Notice) => void, onConnectionChange: (online: boolean) => void, ) => Promise; deleteProject: ( workspace: Workspace, onNotify: (n: Notice) => void, onConnectionChange: (online: boolean) => void, ) => Promise; openImportMembers: ( workspace: Workspace, onNotify: (n: Notice) => void, ) => Promise; importMembers: ( workspace: Workspace, user_ids: string[], role_code: "admin" | "developer", onNotify: (n: Notice) => void, ) => Promise; }; const initial: State = { employees: [], usersLoading: true, usersSaving: false, editingUser: null, userDialogOpen: false, userForm: EMPTY_USER_FORM, userSearchTerm: "", projects: [], projectsLoading: true, projectsSaving: false, editingProject: null, projectDialogOpen: false, projectForm: EMPTY_PROJECT_FORM, selectedProject: null, availableUsers: [], selectedUserIds: [], selectedRoleCode: "developer", projectSearchTerm: "", importMemberDialogOpen: false, }; async function handleError( error: unknown, fallback: string, onNotify: (n: Notice) => void, ): Promise { if (error instanceof ApiRequestError && error.status === 401) { onNotify({ tone: "error", message: "登录已失效,请重新登录" }); return; } if (error instanceof ApiRequestError && error.status === 403) { onNotify({ tone: "error", message: "没有权限执行该操作" }); return; } if (error instanceof ApiRequestError && error.status === 409) { onNotify({ tone: "error", message: error.message || "操作冲突,目标资源可能已存在或状态不一致", }); return; } onNotify({ tone: "error", message: error instanceof Error ? error.message : fallback, }); } export const useAdminStore = create((set, get) => ({ ...initial, bindApi: bindAdminApi, reset: () => set({ ...initial, usersLoading: true, projectsLoading: true }), // ---- users setters ---- setEmployees: (updater) => set((state) => ({ employees: updater(state.employees) })), setUsersLoading: (loading) => set({ usersLoading: loading }), setUsersSaving: (saving) => set({ usersSaving: saving }), setEditingUser: (e) => set({ editingUser: e }), setUserDialogOpen: (open) => set({ userDialogOpen: open }), setUserForm: (form) => set({ userForm: form }), setUserSearchTerm: (k) => set({ userSearchTerm: k }), // ---- projects setters ---- setProjects: (updater) => set((state) => ({ projects: updater(state.projects) })), setProjectsLoading: (loading) => set({ projectsLoading: loading }), setProjectsSaving: (saving) => set({ projectsSaving: saving }), setEditingProject: (p) => set({ editingProject: p }), setProjectDialogOpen: (open) => set({ projectDialogOpen: open }), setProjectForm: (form) => set({ projectForm: form }), setSelectedProject: (p) => set({ selectedProject: p }), setAvailableUsers: (users) => set({ availableUsers: users }), setSelectedUserIds: (ids) => set({ selectedUserIds: ids }), setSelectedRoleCode: (r) => set({ selectedRoleCode: r }), setProjectSearchTerm: (k) => set({ projectSearchTerm: k }), setImportMemberDialogOpen: (open) => set({ importMemberDialogOpen: open }), // ---- users actions ---- loadEmployees: async () => { const api = requireApi(); set({ usersLoading: true }); try { const list = await api.listEmployees(); set({ employees: list }); setApiOnline(true); } catch (error) { setApiOnline(false); pushToast({ tone: "error", message: error instanceof Error ? error.message : "用户列表加载失败", }); } finally { set({ usersLoading: false }); } }, createEmployee: async (form, onNotify, onConnectionChange) => { const api = requireApi(); if (get().usersSaving) return; set({ usersSaving: true }); try { const created = await api.createEmployee(form); set((state) => ({ employees: [created, ...state.employees] })); set({ userDialogOpen: false, editingUser: null }); onConnectionChange(true); onNotify({ tone: "success", message: "用户已创建" }); } catch (error) { await handleError(error, "创建用户失败", onNotify); } finally { set({ usersSaving: false }); } }, updateEmployee: async (employee, form, onNotify, onConnectionChange) => { const api = requireApi(); if (get().usersSaving) return; set({ usersSaving: true }); try { const updated = await api.updateEmployee(employee.user_id, form); set((state) => ({ employees: state.employees.map((e) => e.user_id === updated.user_id ? updated : e ), })); set({ userDialogOpen: false, editingUser: null }); onConnectionChange(true); onNotify({ tone: "success", message: "用户已更新" }); } catch (error) { await handleError(error, "更新用户失败", onNotify); } finally { set({ usersSaving: false }); } }, deleteEmployee: async (employee, onNotify, onConnectionChange) => { const api = requireApi(); if (get().usersSaving) return; set({ usersSaving: true }); try { await api.deleteEmployee(employee.user_id); set((state) => ({ employees: state.employees.filter((e) => e.user_id !== employee.user_id), })); onConnectionChange(true); onNotify({ tone: "success", message: "用户已删除" }); } catch (error) { await handleError(error, "删除用户失败", onNotify); } finally { set({ usersSaving: false }); } }, // ---- projects actions ---- loadProjects: async (onNotify, onConnectionChange) => { const api = requireApi(); set({ projectsLoading: true }); try { const list = await api.listWorkspaces(); set({ projects: list }); onConnectionChange(true); } catch (error) { onConnectionChange(false); await handleError(error, "项目列表加载失败", onNotify); } finally { set({ projectsLoading: false }); } }, createProject: async (form, onNotify, onConnectionChange) => { const api = requireApi(); if (get().projectsSaving) return; set({ projectsSaving: true }); try { const created = await api.createWorkspace(form); set((state) => ({ projects: [created, ...state.projects] })); set({ projectDialogOpen: false, editingProject: null }); onConnectionChange(true); onNotify({ tone: "success", message: "项目已创建" }); } catch (error) { await handleError(error, "创建项目失败", onNotify); } finally { set({ projectsSaving: false }); } }, updateProject: async (workspace, form, onNotify, onConnectionChange) => { const api = requireApi(); if (get().projectsSaving) return; set({ projectsSaving: true }); try { const updated = await api.updateWorkspace(workspace.workspace_id, form); set((state) => ({ projects: state.projects.map((p) => p.workspace_id === updated.workspace_id ? updated : p ), })); set({ projectDialogOpen: false, editingProject: null }); onConnectionChange(true); onNotify({ tone: "success", message: "项目已更新" }); } catch (error) { await handleError(error, "更新项目失败", onNotify); } finally { set({ projectsSaving: false }); } }, deleteProject: async (workspace, onNotify, onConnectionChange) => { const api = requireApi(); if (get().projectsSaving) return; set({ projectsSaving: true }); try { await api.deleteWorkspace(workspace.workspace_id); set((state) => ({ projects: state.projects.filter( (p) => p.workspace_id !== workspace.workspace_id, ), })); onConnectionChange(true); onNotify({ tone: "success", message: "项目已删除" }); } catch (error) { await handleError(error, "删除项目失败", onNotify); } finally { set({ projectsSaving: false }); } }, openImportMembers: async (workspace, onNotify) => { const api = requireApi(); set({ selectedProject: workspace, importMemberDialogOpen: true }); try { const all = await api.listEmployees(); const existing = await api.listWorkspaceMembers(workspace.workspace_id); const taken = new Set(existing.map((m) => m.user_id)); set({ availableUsers: all.filter((u) => !taken.has(u.user_id)), selectedUserIds: [], selectedRoleCode: "developer", }); } catch (error) { await handleError(error, "加载成员候选失败", onNotify); set({ importMemberDialogOpen: false }); } }, importMembers: async (workspace, user_ids, role_code, onNotify) => { const api = requireApi(); try { await Promise.all( user_ids.map((uid) => api.addWorkspaceMember(workspace.workspace_id, { user_id: uid, role_code, }), ), ); set({ importMemberDialogOpen: false }); onNotify({ tone: "success", message: `已添加 ${user_ids.length} 名成员`, }); } catch (error) { await handleError(error, "导入成员失败", onNotify); } }, }));