Files
browser/platform_web/routes/routes.ts
T
npc0-hue bc45188be9 Send user trajectories from the user list into a focused live map
- Open 「用户轨迹」 straight on the live map instead of a drawer, carrying the
  server, the focused user and a start/end window through the plugin page hash.
- Default that window to one hour before the user's last seen time and keep the
  plugin page query available to bundles through the host context.
- Replace the time range preset gate with always visible start/end datetime
  fields plus a quick-range select, so the user list rebuilds from the window.
- Drop the bulky trajectory list and draw one compact colour legend under the
  map, keeping a stable colour per identity and restricting vehicle tracks to
  the vehicles the selected users actually rode.
2026-09-16 19:35:31 +08:00

189 lines
5.7 KiB
TypeScript

import type { PageId, PageParams, PageRoute } from "../contracts/page";
import type { CurrentUserView } from "../contracts/workspace";
import { isPlatformAdmin } from "../contracts/workspace";
export const firstPartyRoutes: PageRoute[] = [
{
id: "profileSettings",
label: "个人设置",
path: "/profile",
hash: "#/profile",
description: "个人资料与界面偏好",
requiredCapability: "profile.settings.manage",
showInNav: false
},
{
id: "home",
label: "平台概览",
path: "/",
hash: "#/home",
description: "平台运行健康概览",
requiredCapability: "platform.overview.read",
showInNav: true
},
{
id: "servers",
label: "服务器管理",
path: "/servers",
hash: "#/servers",
description: "服务器列表、状态与生命周期",
requiredCapability: "servers.read",
showInNav: true
},
{
id: "serverDetail",
label: "服务器详情",
path: "/servers/:serverId",
hash: "#/servers/:serverId",
description: "单服务器日常运维工作台",
requiredCapability: "servers.read",
showInNav: false
},
{
id: "pluginPage",
label: "插件页面",
path: "/plugin-pages/:pluginId/:routeKey",
hash: "#/plugin-pages/:pluginId/:routeKey",
description: "平台托管的插件运维页面",
requiredCapability: "servers.read",
showInNav: false
},
{
id: "plugins",
label: "插件市场",
path: "/plugins",
hash: "#/plugins",
description: "插件发现、安装状态与文档",
requiredCapability: "plugins.market.read",
showInNav: true
},
{
id: "users",
label: "用户管理",
path: "/users",
hash: "#/users",
description: "用户、角色和访问审核",
requiredCapability: "users.manage",
showInNav: true
},
{
id: "aiProviders",
label: "AI 提供商管理",
path: "/ai-providers",
hash: "#/aiProviders",
description: "平台中介模型配置",
requiredCapability: "aiProviders.manage",
showInNav: true
},
{
id: "maintenance",
label: "系统维护",
path: "/maintenance",
hash: "#/maintenance",
description: "服务器异常与失败任务",
requiredCapability: "system.maintenance",
showInNav: true
}
];
const routesById = new Map(firstPartyRoutes.map((route) => [route.id, route]));
export interface ResolvedRoute {
route: PageRoute;
params: PageParams;
}
export function routeForPage(pageId: PageId): PageRoute {
return routesById.get(pageId) ?? firstPartyRoutes[0];
}
export function hashForPage(pageId: PageId, params: PageParams = {}): string {
const route = routeForPage(pageId);
if (pageId === "serverDetail" && params.serverId) {
const query = new URLSearchParams();
if (params.routeKey) query.set("focus", params.routeKey);
const suffix = query.toString();
return `#/servers/${encodeURIComponent(params.serverId)}${suffix ? `?${suffix}` : ""}`;
}
if (pageId === "pluginPage" && params.pluginId && params.routeKey) {
const query = new URLSearchParams();
if (params.serverId) {
query.set("serverInstanceId", params.serverId);
}
for (const [key, value] of Object.entries(params.pageQuery ?? {})) {
if (value) {
query.set(key, value);
}
}
const suffix = query.toString();
return `#/plugin-pages/${encodeURIComponent(params.pluginId)}/${encodeURIComponent(params.routeKey)}${suffix ? `?${suffix}` : ""}`;
}
return route.hash;
}
export function defaultPageForUser(user: CurrentUserView): PageId {
return isPlatformAdmin(user) ? "home" : "servers";
}
export function navigationRoutesForUser(user: CurrentUserView): PageRoute[] {
return firstPartyRoutes.filter((route) => route.showInNav && user.capabilities.includes(route.requiredCapability));
}
export function canAccessRoute(user: CurrentUserView, pageId: PageId): boolean {
const route = routesById.get(pageId);
if (!route) {
return false;
}
return user.capabilities.includes(route.requiredCapability);
}
export function resolveRouteHash(hash: string, user?: CurrentUserView): ResolvedRoute {
const fallback: ResolvedRoute = {
route: routeForPage(user ? defaultPageForUser(user) : "servers"),
params: {}
};
const normalized = hash.replace(/^#/, "");
if (!normalized || normalized === "/") {
return fallback;
}
const [normalizedPath, rawQuery = ""] = normalized.split("?", 2);
const segments = normalizedPath.replace(/^\//, "").split("/").filter(Boolean);
if (segments.length === 0) {
return fallback;
}
if (segments[0] === "servers" && segments.length > 1) {
const routeKey = new URLSearchParams(rawQuery).get("focus") ?? undefined;
return { route: routeForPage("serverDetail"), params: { serverId: decodeURIComponent(segments[1]), routeKey } };
}
if (segments[0] === "plugin-pages" && segments.length > 2) {
const query = new URLSearchParams(rawQuery);
const serverId = query.get("serverInstanceId") ?? undefined;
query.delete("serverInstanceId");
const pageQuery: Record<string, string> = {};
for (const [key, value] of query.entries()) {
if (value) {
pageQuery[key] = value;
}
}
return {
route: routeForPage("pluginPage"),
params: {
pluginId: decodeURIComponent(segments[1]),
routeKey: decodeURIComponent(segments[2]),
serverId,
...(Object.keys(pageQuery).length ? { pageQuery } : {})
}
};
}
const key = segments[0];
const byId = routesById.get(key as PageId);
if (byId) {
return { route: byId, params: {} };
}
const byPath = firstPartyRoutes.find((route) => route.path === `/${key}`);
if (byPath) {
return { route: byPath, params: {} };
}
return fallback;
}