console: 项目导航移到左侧栏,工作台不再承担项目模块
左侧栏 ProjectNav:项目完整列表(名 + 短 id + 我的角色, 选中高亮,点击切换)+ 创建项目(名/GitHub 仓库/repo_url) + 选中项目成员(邀请/改角色/新增移除);工作台删除 sec-projects 一节与任务列表里的项目下拉框,侧栏为唯一 项目选择器,自动选第一个项目逻辑不变。 薄模块补齐(均为已核实后端路由):成员移除 DELETE、 自动化详情(GET 单条 + 产出)与编辑 PATCH、分诊条目点进 任务/自动化详情、Workers 搜索与在线筛选、管理区设置配额 POST。主线新功能(Todos、Browser、taskMeta 等)原样保留, 未做任何回退。console 标记单测本地 53 项通过。
This commit is contained in:
1 parent
d6369154c2
commit
08484302ac
16 files changed
+293
-100
No files matched your search
@@ -253,6 +253,17 @@ export function useCatalog() {
|
||||
} catch (e: any) { setMemberMsg(memberErr(e)); }
|
||||
}, []);
|
||||
|
||||
const removeMember = useCallback(async (uid: string) => {
|
||||
const pid = projectIdRef.current;
|
||||
if (!pid) return;
|
||||
if (!window.confirm("确定移除该成员吗?移除后其项目权限立即失效(唯一 owner 不可移除,服务端会拒绝)。")) return;
|
||||
try {
|
||||
await api(paths.projectMember(pid, uid), "DELETE");
|
||||
setMemberMsg("成员已移除");
|
||||
await refreshMembers(pid);
|
||||
} catch (e: any) { setMemberMsg(memberErr(e)); }
|
||||
}, [refreshMembers]);
|
||||
|
||||
const reloadMcp = useCallback(async () => {
|
||||
const pid = projectIdRef.current;
|
||||
if (!pid) { setMcpMsg("先选项目"); return; }
|
||||
@@ -289,6 +300,24 @@ export function useCatalog() {
|
||||
} catch (e: any) { setAdminResult(adminErr(e)); }
|
||||
}, [adminUserId]);
|
||||
|
||||
const adminSetQuota = useCallback(async (uid: string, usdText: string) => {
|
||||
const target = uid.trim();
|
||||
if (!target) { setAdminResult("请填要设置配额的用户 id"); return; }
|
||||
// POST /admin/quotas {user_id, quota_usd_micros}: integer micros or
|
||||
// null for unlimited, >= 0. The console takes dollars and converts.
|
||||
let micros: number | null;
|
||||
if (!usdText.trim()) micros = null;
|
||||
else {
|
||||
const usd = Number(usdText.trim());
|
||||
if (!Number.isFinite(usd) || usd < 0) { setAdminResult("配额金额无效:请输入 >= 0 的美元数,留空表示不限额"); return; }
|
||||
micros = Math.round(usd * 1000000);
|
||||
}
|
||||
try {
|
||||
const d = await api<{ used_usd_micros?: number; quota_usd_micros?: number | null; remaining_usd_micros?: number | null; month?: string }>(paths.adminQuotas, "POST", { user_id: target, quota_usd_micros: micros });
|
||||
setAdminResult("配额已设置:用户 " + target.slice(0, 8) + ":本月用量 " + fmtUsd(d.used_usd_micros) + " / 配额 " + fmtUsd(d.quota_usd_micros) + (d.remaining_usd_micros !== null && d.remaining_usd_micros !== undefined ? "(剩余 " + fmtUsd(d.remaining_usd_micros) + ")" : "") + " | 月份 " + (d.month || ""));
|
||||
} catch (e: any) { setAdminResult(adminErr(e)); }
|
||||
}, []);
|
||||
|
||||
const adminSweep = useCallback(async () => {
|
||||
if (!window.confirm("确定手动触发 sweep 吗?超阈值的陈旧 running 任务会被标为 interrupted。")) return;
|
||||
try {
|
||||
@@ -314,6 +343,6 @@ export function useCatalog() {
|
||||
models, modelCustomVisible,
|
||||
refreshHealth, loadQuota, probeAdmin, refreshProjects, loadGithubRepos, loadRelayModels, refreshWorkers,
|
||||
refreshEnvs, refreshMembers, refreshSkills, saveKeyInput, clearKeyInput, createProject, createEnv,
|
||||
archiveEnv, saveEnvRepo, inviteMember, changeMemberRole, reloadMcp, mcpOauth, adminQuota, adminSweep, adminBackup,
|
||||
archiveEnv, saveEnvRepo, inviteMember, changeMemberRole, removeMember, reloadMcp, mcpOauth, adminQuota, adminSetQuota, adminSweep, adminBackup,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user