feat: ship enterprise control and desktop governance

This commit is contained in:
AI Bot
2026-05-11 14:59:26 +08:00
parent 0757d07521
commit a311280238
285 changed files with 48574 additions and 2428 deletions

View File

@@ -0,0 +1,219 @@
import { NextRequest } from "next/server";
import { jsonNoStore } from "@/lib/api-response";
import { requireRequestSession } from "@/lib/boss-auth";
import { BOSS_PERMISSION_TEMPLATES } from "@/lib/boss-access-templates";
import { buildAdminOverview } from "@/lib/boss-admin-overview";
import { readState, type BossState } from "@/lib/boss-data";
const MENU_TREE = [
{ key: "workbench", label: "工作台" },
{ key: "tenant", label: "租户管理" },
{ key: "user", label: "账号管理" },
{ key: "role", label: "角色权限" },
{
key: "resource",
label: "资源授权",
children: [
{ key: "resource.devices", label: "设备资源" },
{ key: "resource.projects", label: "项目线程" },
{ key: "resource.skills", label: "Skill 资源" },
],
},
{ key: "skills", label: "Skill 中心" },
{ key: "risk", label: "风险告警" },
{ key: "audit", label: "审计日志" },
{ key: "system", label: "系统设置" },
] as const;
function companyNameMap(state: BossState) {
return new Map(state.adminCompanies.map((company) => [company.companyId, company.name]));
}
function companyNameFor(state: BossState, companyId?: string) {
if (!companyId || companyId === "default") return "默认公司";
return companyNameMap(state).get(companyId) ?? companyId;
}
function safeUsers(state: BossState) {
return state.authAccounts.map((account) => ({
id: account.id,
account: account.account,
displayName: account.displayName,
role: account.role,
status: account.status ?? "active",
companyId: account.companyId ?? "default",
companyName: companyNameFor(state, account.companyId),
primaryDeviceId: account.primaryDeviceId,
codexNodeId: account.codexNodeId,
codexNodeLabel: account.codexNodeLabel,
lastLoginAt: account.lastLoginAt,
lastLoginMethod: account.lastLoginMethod,
mfaRequired: Boolean(account.mfaRequired),
createdAt: account.createdAt,
updatedAt: account.updatedAt,
}));
}
function skillResources(state: BossState) {
const byName = new Map<
string,
{
skillId: string;
name: string;
description: string;
category?: string;
invocation?: string;
sourceType: "device" | "catalog";
deviceCount: number;
devices: Array<{ deviceId: string; updatedAt: string }>;
updatedAt: string;
}
>();
for (const skill of state.deviceSkills) {
const existing = byName.get(skill.name);
if (existing) {
existing.deviceCount += existing.devices.some((device) => device.deviceId === skill.deviceId) ? 0 : 1;
existing.devices.push({ deviceId: skill.deviceId, updatedAt: skill.updatedAt });
if (skill.updatedAt.localeCompare(existing.updatedAt) > 0) {
existing.updatedAt = skill.updatedAt;
}
continue;
}
byName.set(skill.name, {
skillId: skill.skillId,
name: skill.name,
description: skill.description,
category: skill.category,
invocation: skill.invocation,
sourceType: "device",
deviceCount: 1,
devices: [{ deviceId: skill.deviceId, updatedAt: skill.updatedAt }],
updatedAt: skill.updatedAt,
});
}
for (const catalogItem of state.skillCatalog) {
if (byName.has(catalogItem.name)) continue;
byName.set(catalogItem.name, {
skillId: catalogItem.skillId,
name: catalogItem.name,
description: catalogItem.description,
category: catalogItem.category,
sourceType: "catalog",
deviceCount: 0,
devices: [],
updatedAt: catalogItem.updatedAt,
});
}
return [...byName.values()].sort(
(left, right) => right.deviceCount - left.deviceCount || left.name.localeCompare(right.name, "zh-CN"),
);
}
function projectResources(state: BossState) {
return state.projects.map((project) => ({
id: project.id,
name: project.name,
deviceIds: project.deviceIds,
deviceCount: project.deviceIds.length,
folderName: project.threadMeta.folderName,
threadId: project.threadMeta.threadId,
threadDisplayName: project.threadMeta.threadDisplayName,
isGroup: project.isGroup,
collaborationMode: project.collaborationMode,
unreadCount: project.unreadCount,
riskLevel: project.riskLevel,
updatedAt: project.updatedAt,
lastMessageAt: project.lastMessageAt,
}));
}
function rolesContract() {
return {
builtInRoles: [
{
role: "highest_admin",
label: "超级管理员",
description: "平台侧最高权限可管理全部公司、账号、设备、项目、Skill、风险和审计。",
},
{
role: "admin",
label: "企业管理员",
description: "企业内管理员,按授权范围管理本公司资源。",
},
{
role: "member",
label: "成员账号",
description: "企业子账号,只能访问已分配的电脑、项目和 Skill。",
},
],
permissionTemplates: BOSS_PERMISSION_TEMPLATES,
};
}
function buildBackofficePayload(state: BossState) {
const overview = buildAdminOverview(state);
const skills = skillResources(state);
return {
ok: true,
menuTree: MENU_TREE,
workbench: {
summary: overview.summary,
companies: overview.companies.slice(0, 10),
devices: overview.devices.slice(0, 20),
risks: overview.risks.slice(0, 20),
notifications: overview.notifications,
grantsSummary: overview.grantsSummary,
},
tenants: overview.companies.map((company) => ({
...company,
lifecycleStatus: company.status ?? "active",
})),
users: safeUsers(state),
roles: rolesContract(),
resourceGroups: {
devices: overview.devices,
projects: projectResources(state),
skills,
grants: {
devices: state.accountDeviceGrants,
projects: state.accountProjectGrants,
skills: state.accountSkillGrants,
},
},
audit: {
risks: overview.risks,
notifications: overview.notifications,
riskTimeline: overview.riskTimeline,
permissionLogs: state.permissionAuditLogs
.slice()
.sort((left, right) => right.createdAt.localeCompare(left.createdAt))
.slice(0, 100),
},
yudaoMapping: {
tenant: "adminCompanies",
user: "authAccounts",
role: "BOSS_PERMISSION_TEMPLATES",
menu: "menuTree",
operateLog: "permissionAuditLogs",
resource: "devices/projects/deviceSkills",
risk: "opsFaults/threadContextAlerts/masterAgentTasks/adminNotifications",
},
};
}
export async function GET(request: NextRequest) {
const session = await requireRequestSession(request);
if (!session) {
return jsonNoStore({ ok: false, message: "UNAUTHORIZED" }, { status: 401 });
}
if (session.role !== "highest_admin") {
return jsonNoStore({ ok: false, message: "FORBIDDEN" }, { status: 403 });
}
const state = await readState();
return jsonNoStore(buildBackofficePayload(state));
}