功能修改
This commit is contained in:
@@ -11,7 +11,7 @@ First-party routes must be declared here before page implementation.
|
||||
- `/users`: 用户管理.
|
||||
- `/ai-providers`: AI 提供商管理.
|
||||
- `/maintenance`: 系统维护(审计事件与运行节点).
|
||||
- `/plugin-pages/:pluginId/:routeKey`: platform-hosted plugin page route.
|
||||
- `/plugin-pages/:pluginId/:routeKey?serverInstanceId=:serverId`: platform-hosted plugin page route with optional server context; IDs are URL encoded and the route is not shown in primary navigation.
|
||||
|
||||
## Role Scoping
|
||||
|
||||
@@ -23,3 +23,6 @@ Navigation entries are generated from the current user's capability set (`contra
|
||||
- Server creation opens inline from 服务器管理 (no separate `/servers/new` page).
|
||||
|
||||
Server and plugin detail flows must use routes, modals, or drawers. Do not build a fixed left-list/right-detail page.
|
||||
# Server Detail lifecycle route
|
||||
|
||||
The `serverDetail` route owns the Client Manager workspace as an overview operations surface. It is a detail workflow, not a permanent split pane: list pages remain full-width and destructive lifecycle commands use shared confirmation dialogs. Route state does not contain component secrets or Run session material.
|
||||
|
||||
@@ -41,6 +41,14 @@ describe("console shell routes", () => {
|
||||
expect(hashForPage("serverDetail", { serverId: "server-example-1" })).toBe("#/servers/server-example-1");
|
||||
});
|
||||
|
||||
it("round-trips hosted plugin page hashes with server context", () => {
|
||||
const hash = hashForPage("pluginPage", { pluginId: "game.scum", routeKey: "operations", serverId: "server/scum-1" });
|
||||
expect(hash).toBe("#/plugin-pages/game.scum/operations?serverInstanceId=server%2Fscum-1");
|
||||
const resolved = resolveRouteHash(hash, platformAdmin);
|
||||
expect(resolved.route).toMatchObject({ id: "pluginPage", showInNav: false, requiredCapability: "servers.read" });
|
||||
expect(resolved.params).toEqual({ pluginId: "game.scum", routeKey: "operations", serverId: "server/scum-1" });
|
||||
});
|
||||
|
||||
it("keeps route metadata available for shell navigation", () => {
|
||||
expect(firstPartyRoutes.map((route) => route.id)).toContain("maintenance");
|
||||
expect(firstPartyRoutes.map((route) => route.id)).toContain("profileSettings");
|
||||
|
||||
@@ -39,6 +39,15 @@ export const firstPartyRoutes: PageRoute[] = [
|
||||
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: "插件市场",
|
||||
@@ -93,6 +102,14 @@ export function hashForPage(pageId: PageId, params: PageParams = {}): string {
|
||||
if (pageId === "serverDetail" && params.serverId) {
|
||||
return `#/servers/${encodeURIComponent(params.serverId)}`;
|
||||
}
|
||||
if (pageId === "pluginPage" && params.pluginId && params.routeKey) {
|
||||
const query = new URLSearchParams();
|
||||
if (params.serverId) {
|
||||
query.set("serverInstanceId", params.serverId);
|
||||
}
|
||||
const suffix = query.toString();
|
||||
return `#/plugin-pages/${encodeURIComponent(params.pluginId)}/${encodeURIComponent(params.routeKey)}${suffix ? `?${suffix}` : ""}`;
|
||||
}
|
||||
return route.hash;
|
||||
}
|
||||
|
||||
@@ -121,13 +138,25 @@ export function resolveRouteHash(hash: string, user?: CurrentUserView): Resolved
|
||||
if (!normalized || normalized === "/") {
|
||||
return fallback;
|
||||
}
|
||||
const segments = normalized.replace(/^\//, "").split("/").filter(Boolean);
|
||||
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) {
|
||||
return { route: routeForPage("serverDetail"), params: { serverId: decodeURIComponent(segments[1]) } };
|
||||
}
|
||||
if (segments[0] === "plugin-pages" && segments.length > 2) {
|
||||
const serverId = new URLSearchParams(rawQuery).get("serverInstanceId") ?? undefined;
|
||||
return {
|
||||
route: routeForPage("pluginPage"),
|
||||
params: {
|
||||
pluginId: decodeURIComponent(segments[1]),
|
||||
routeKey: decodeURIComponent(segments[2]),
|
||||
serverId
|
||||
}
|
||||
};
|
||||
}
|
||||
const key = segments[0];
|
||||
const byId = routesById.get(key as PageId);
|
||||
if (byId) {
|
||||
|
||||
Reference in New Issue
Block a user