Remove manual runtime controls from server detail
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
export const pluginPageBundle = { key: "dev-game-plugin", version: "1.0.0", integritySha256: "sha256:1111111111111111111111111111111111111111111111111111111111111111" };
|
||||
|
||||
type ReactLike = { createElement: (...args: any[]) => any };
|
||||
|
||||
export function renderPluginPage(react: ReactLike, input: any) {
|
||||
const e = react.createElement;
|
||||
const pageKey = input.page?.key ?? "overview";
|
||||
if (pageKey === "config") return renderConfigPage(e, input);
|
||||
if (pageKey === "logs") return renderLogsPage(e, input);
|
||||
return renderOverviewPage(e, input);
|
||||
}
|
||||
|
||||
function renderOverviewPage(e: ReactLike["createElement"], input: any) {
|
||||
return renderPanel(e, "插件概览", "Example 插件声明的服务器概览页面。", input, [
|
||||
["服务器", input.context?.serverInstanceId ?? "未绑定"],
|
||||
["桥接动作", (input.context?.bridgeActions ?? []).join(" / ") || "未声明"],
|
||||
["页面来源", "dev-game-plugin 页面 bundle"]
|
||||
]);
|
||||
}
|
||||
|
||||
function renderConfigPage(e: ReactLike["createElement"], input: any) {
|
||||
return renderPanel(e, "配置工作台", "配置入口由插件页面声明,平台只提供受控上下文。", input, [
|
||||
["文件权限", (input.context?.permissions ?? []).filter((value: string) => value.includes("files")).join(" / ") || "未声明"],
|
||||
["AI 能力", (input.context?.permissions ?? []).includes("ai.invoke") ? "可请求平台 AI" : "未声明"],
|
||||
["写入策略", "平台审查后派发"]
|
||||
]);
|
||||
}
|
||||
|
||||
function renderLogsPage(e: ReactLike["createElement"], input: any) {
|
||||
return renderPanel(e, "日志视图", "日志入口由插件声明并通过平台日志通道读取。", input, [
|
||||
["日志权限", (input.context?.permissions ?? []).includes("server.logs.read") ? "可读" : "未声明"],
|
||||
["Companion", input.availability?.available ? "可用" : input.availability?.reason ?? "等待运行端"],
|
||||
["桥接", (input.context?.bridgeActions ?? []).includes("logs.query") ? "logs.query" : "未声明"]
|
||||
]);
|
||||
}
|
||||
|
||||
function renderPanel(e: ReactLike["createElement"], title: string, summary: string, input: any, rows: Array<[string, string]>) {
|
||||
return e("section", { className: "console-panel", "aria-label": title },
|
||||
e("div", { className: "panel-header" },
|
||||
e("div", null, e("h2", null, title), e("p", { className: "provider-id" }, summary)),
|
||||
e("span", { className: "page-status" }, input.context?.serverInstanceId ? "已绑定服务器" : "未绑定服务器")
|
||||
),
|
||||
e("div", { className: "console-row-list" }, rows.map(([label, value]) => e("div", { key: label, className: "console-row" }, e("span", null, label), e("strong", null, value))))
|
||||
);
|
||||
}
|
||||
@@ -114,55 +114,61 @@
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"key": "overview",
|
||||
"title": "MC 概览",
|
||||
"path": "/overview",
|
||||
"key": "files",
|
||||
"title": "文件管理",
|
||||
"path": "/files",
|
||||
"bundleKey": "minecraft-server-plugin",
|
||||
"bundleVersion": "1.0.0",
|
||||
"bundleIntegritySha256": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
|
||||
"permissions": [
|
||||
"server.read",
|
||||
"server.lifecycle"
|
||||
],
|
||||
"bridgeActions": [
|
||||
"server.instances.read",
|
||||
"jobs.dispatch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "remote",
|
||||
"title": "MC 远程",
|
||||
"path": "/remote",
|
||||
"bundleKey": "minecraft-server-plugin",
|
||||
"bundleVersion": "1.0.0",
|
||||
"bundleIntegritySha256": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
|
||||
"permissions": [
|
||||
"server.remote.access",
|
||||
"server.files.read",
|
||||
"server.files.write",
|
||||
"server.logs.read"
|
||||
],
|
||||
"bridgeActions": [
|
||||
"remote.access.request",
|
||||
"server.instances.read",
|
||||
"files.request",
|
||||
"logs.query"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "rcon",
|
||||
"title": "MC RCON",
|
||||
"path": "/rcon",
|
||||
"key": "mods-market",
|
||||
"title": "mod/插件市场",
|
||||
"path": "/mods-market",
|
||||
"bundleKey": "minecraft-server-plugin",
|
||||
"bundleVersion": "1.0.0",
|
||||
"bundleIntegritySha256": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
|
||||
"permissions": [
|
||||
"server.remote.access"
|
||||
"server.read",
|
||||
"server.artifacts.read",
|
||||
"server.files.read"
|
||||
],
|
||||
"bridgeActions": [
|
||||
"remote.access.request"
|
||||
"server.instances.read",
|
||||
"artifacts.open"
|
||||
]
|
||||
}
|
||||
],
|
||||
"fileWorkspace": {
|
||||
"defaultDirectoryKey": "minecraft-config",
|
||||
"directories": [
|
||||
{ "key": "minecraft-config", "label": "服务器配置", "scope": "config" },
|
||||
{ "key": "minecraft-logs", "label": "日志文件", "scope": "logs" }
|
||||
],
|
||||
"files": [
|
||||
{ "key": "server-properties", "directoryKey": "minecraft-config", "label": "server.properties", "kind": "config", "editable": true },
|
||||
{ "key": "ops-json", "directoryKey": "minecraft-config", "label": "ops.json", "kind": "config", "editable": true },
|
||||
{ "key": "whitelist-json", "directoryKey": "minecraft-config", "label": "whitelist.json", "kind": "config", "editable": true },
|
||||
{ "key": "minecraft-latest-log", "directoryKey": "minecraft-logs", "label": "latest.log", "kind": "log", "streamKey": "minecraft.latest" }
|
||||
],
|
||||
"configFields": [
|
||||
{ "key": "motd", "fileKey": "server-properties", "configKey": "motd", "label": "服务器名称", "description": "显示在 Minecraft 服务器列表中的 MOTD。", "control": "text", "defaultValue": "Minecraft Server", "restartImpact": "restart-required" },
|
||||
{ "key": "server-port", "fileKey": "server-properties", "configKey": "server-port", "label": "游戏端口", "description": "Minecraft Java 客户端连接端口。", "control": "port", "minimum": 1, "maximum": 65535, "defaultValue": "25565", "restartImpact": "restart-required" },
|
||||
{ "key": "max-players", "fileKey": "server-properties", "configKey": "max-players", "label": "最大玩家数", "description": "允许同时进入服务器的玩家上限。", "control": "number", "minimum": 1, "maximum": 200, "defaultValue": "20", "restartImpact": "restart-required" },
|
||||
{ "key": "online-mode", "fileKey": "server-properties", "configKey": "online-mode", "label": "正版验证", "description": "是否启用 Mojang 在线身份验证。", "control": "boolean", "defaultValue": "true", "restartImpact": "restart-required" }
|
||||
]
|
||||
},
|
||||
"ai": {
|
||||
"purposes": [
|
||||
"config.suggest",
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
export const pluginPageBundle = { key: "minecraft-server-plugin", version: "1.0.0", integritySha256: "sha256:2222222222222222222222222222222222222222222222222222222222222222" };
|
||||
|
||||
type ReactLike = { createElement: (...args: any[]) => any };
|
||||
|
||||
export function renderPluginPage(react: ReactLike, input: any) {
|
||||
const e = react.createElement;
|
||||
const pageKey = input.page?.key ?? "files";
|
||||
const workspace = input.workspace;
|
||||
if (pageKey === "mods-market") return renderModsMarket(e, input);
|
||||
return renderFiles(e, input, workspace);
|
||||
}
|
||||
|
||||
function renderFiles(e: ReactLike["createElement"], input: any, workspace: any) {
|
||||
const files = workspace?.files ?? [];
|
||||
return e("section", { className: "console-panel", "aria-label": "Minecraft 文件管理" },
|
||||
e("div", { className: "panel-header" },
|
||||
e("div", null, e("h2", null, "文件管理"), e("p", { className: "provider-id" }, "server.properties、白名单、OP 列表和日志由 Minecraft 插件声明。")),
|
||||
e("span", { className: "page-status" }, input.context?.serverInstanceId ? "已绑定服务器" : "未绑定服务器")
|
||||
),
|
||||
e("div", { className: "console-row-list" },
|
||||
files.length ? files.map((file: any) => e("div", { key: file.key, className: "console-row" }, e("span", null, file.label), e("strong", null, file.kind === "config" ? file.editable === false ? "配置只读" : "配置可写" : "日志只读"))) : e("div", { className: "console-row" }, e("span", null, "声明文件"), e("strong", null, "等待插件工作区"))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function renderModsMarket(e: ReactLike["createElement"], input: any) {
|
||||
return e("section", { className: "console-panel", "aria-label": "Minecraft mod 插件市场" },
|
||||
e("div", { className: "panel-header" },
|
||||
e("div", null, e("h2", null, "mod/插件市场"), e("p", { className: "provider-id" }, "Minecraft 插件拥有自己的 mod/插件入口;平台只提供安全托管上下文。")),
|
||||
e("span", { className: "page-status" }, input.availability?.available ? "运行端可用" : input.availability?.reason ?? "等待运行端")
|
||||
),
|
||||
e("div", { className: "console-row-list" },
|
||||
e("div", { className: "console-row" }, e("span", null, "服务器"), e("strong", null, input.context?.serverInstanceId ?? "未绑定")),
|
||||
e("div", { className: "console-row" }, e("span", null, "权限"), e("strong", null, (input.context?.permissions ?? []).join(" / ") || "未声明")),
|
||||
e("div", { className: "console-row" }, e("span", null, "安装策略"), e("strong", null, "由 Minecraft 插件声明和审核"))
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -22,6 +22,8 @@ export type SCUMFileReadSnapshot = {
|
||||
};
|
||||
|
||||
export type SCUMPageContext = {
|
||||
pageKey?: string;
|
||||
pageTitle?: string;
|
||||
serverInstanceId?: string;
|
||||
permissions: string[];
|
||||
availability: { available: boolean; reason?: string };
|
||||
@@ -45,6 +47,7 @@ type DiffLine = { kind: "same" | "added" | "removed"; text: string };
|
||||
|
||||
export function renderSCUMFeaturePage(react: ReactLike, input: SCUMPageContext) {
|
||||
const e = react.createElement;
|
||||
if (input.pageKey && input.pageKey !== "files-config") return renderSCUMFeatureSurface(e, input);
|
||||
const workspace = normalizeWorkspace(input.workspace);
|
||||
const [selectedDirectoryKey, setSelectedDirectoryKey] = usePluginState(react, workspace.defaultDirectoryKey);
|
||||
const effectiveDirectoryKey = workspace.directories.some((directory) => directory.key === selectedDirectoryKey) ? selectedDirectoryKey : workspace.defaultDirectoryKey;
|
||||
@@ -199,6 +202,34 @@ export function renderSCUMFeaturePage(react: ReactLike, input: SCUMPageContext)
|
||||
);
|
||||
}
|
||||
|
||||
function renderSCUMFeatureSurface(e: ReactLike["createElement"], input: SCUMPageContext) {
|
||||
const meta = scumSurfaceMeta(input.pageKey ?? "");
|
||||
const featureRows = (input.featureAvailability ?? [])
|
||||
.filter((feature) => meta.features.includes(feature.key))
|
||||
.map((feature) => e("div", { key: feature.key, className: "console-row" }, e("span", null, feature.key), e("strong", null, feature.available ? "可用" : feature.reason ?? "等待 Companion")));
|
||||
return e("section", { className: "console-panel", "aria-label": input.pageTitle ?? meta.title },
|
||||
e("div", { className: "panel-header" },
|
||||
e("div", null, e("h2", null, input.pageTitle ?? meta.title), e("p", { className: "provider-id" }, meta.summary)),
|
||||
e("span", { className: "page-status" }, input.availability.available ? "Companion 在线" : input.availability.reason ?? "等待 Companion")
|
||||
),
|
||||
e("div", { className: "console-row-list" },
|
||||
e("div", { className: "console-row" }, e("span", null, "服务器"), e("strong", null, input.serverInstanceId ?? "未绑定")),
|
||||
e("div", { className: "console-row" }, e("span", null, "权限"), e("strong", null, input.permissions.join(" / ") || "未声明")),
|
||||
featureRows.length ? featureRows : e("div", { className: "console-row" }, e("span", null, "插件能力"), e("strong", null, meta.features.join(" / ") || "由插件声明"))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function scumSurfaceMeta(pageKey: string): { title: string; summary: string; features: string[] } {
|
||||
switch (pageKey) {
|
||||
case "players": return { title: "用户管理", summary: "玩家查询、在线状态、维护窗口和状态修正由 SCUM 插件 Companion 提供。", features: ["player.intelligence", "state.patch"] };
|
||||
case "squads": return { title: "队伍管理", summary: "队伍列表、成员关系和风险上下文来自插件声明的 squads 快照。", features: ["player.intelligence"] };
|
||||
case "live-map": return { title: "实时地图", summary: "玩家、载具和轨迹采样由插件事件流驱动。", features: ["trajectory.collect", "vehicle.spawn"] };
|
||||
case "gifts": return { title: "礼包管理", summary: "礼包目录、发放和玩家通知通过受保护插件命令执行。", features: ["reward.delivery"] };
|
||||
default: return { title: "SCUM 插件页面", summary: "该页面由 SCUM 插件声明。", features: [] };
|
||||
}
|
||||
}
|
||||
|
||||
function navigationPane(e: ReactLike["createElement"], workspace: NormalizedWorkspace, directoryKey: string, directoryFiles: readonly SCUMLogicalFile[], selectedFileKey: string | undefined, refreshState: WorkspaceRefreshState, canRefreshWorkspace: boolean, onDirectoryChange: (directoryKey: string) => void, onFileSelect: (file: SCUMLogicalFile) => void, onRefreshWorkspace: () => void) {
|
||||
const activeDirectory = workspace.directories.find((directory) => directory.key === directoryKey);
|
||||
const selectedFile = directoryFiles.find((file) => file.key === selectedFileKey) ?? directoryFiles[0];
|
||||
|
||||
@@ -373,7 +373,13 @@
|
||||
"dependencyPolicy": "required",
|
||||
"approvalRequired": ["disable", "rollback", "retire"]
|
||||
},
|
||||
"pages": [{ "key": "files-config", "title": "文件管理", "path": "/files-config", "bundleKey": "scum-server-plugin", "bundleVersion": "1.0.3", "bundleIntegritySha256": "sha256:797c4e303c102f0316e71e4e5bda50a6ca96506a7cb368a42ecf858b236609b2", "permissions": ["server.read", "server.files.read", "server.files.write", "server.logs.read"], "bridgeActions": ["server.instances.read", "files.request", "logs.query"], "featureKeys": ["config.manage"] }],
|
||||
"pages": [
|
||||
{ "key": "files-config", "title": "文件管理", "path": "/files-config", "bundleKey": "scum-server-plugin", "bundleVersion": "1.0.3", "bundleIntegritySha256": "sha256:797c4e303c102f0316e71e4e5bda50a6ca96506a7cb368a42ecf858b236609b2", "permissions": ["server.read", "server.files.read", "server.files.write", "server.logs.read"], "bridgeActions": ["server.instances.read", "files.request", "logs.query"], "featureKeys": ["config.manage"] },
|
||||
{ "key": "players", "title": "用户管理", "path": "/players", "bundleKey": "scum-server-plugin", "bundleVersion": "1.0.3", "bundleIntegritySha256": "sha256:797c4e303c102f0316e71e4e5bda50a6ca96506a7cb368a42ecf858b236609b2", "permissions": ["server.read", "server.game-client.read", "server.game-client.command"], "bridgeActions": ["server.instances.read"], "featureKeys": ["player.intelligence", "state.patch"] },
|
||||
{ "key": "squads", "title": "队伍管理", "path": "/squads", "bundleKey": "scum-server-plugin", "bundleVersion": "1.0.3", "bundleIntegritySha256": "sha256:797c4e303c102f0316e71e4e5bda50a6ca96506a7cb368a42ecf858b236609b2", "permissions": ["server.read", "server.game-client.read"], "bridgeActions": ["server.instances.read"], "featureKeys": ["player.intelligence"] },
|
||||
{ "key": "live-map", "title": "实时地图", "path": "/live-map", "bundleKey": "scum-server-plugin", "bundleVersion": "1.0.3", "bundleIntegritySha256": "sha256:797c4e303c102f0316e71e4e5bda50a6ca96506a7cb368a42ecf858b236609b2", "permissions": ["server.read", "server.game-client.read"], "bridgeActions": ["server.instances.read"], "featureKeys": ["trajectory.collect"] },
|
||||
{ "key": "gifts", "title": "礼包管理", "path": "/gifts", "bundleKey": "scum-server-plugin", "bundleVersion": "1.0.3", "bundleIntegritySha256": "sha256:797c4e303c102f0316e71e4e5bda50a6ca96506a7cb368a42ecf858b236609b2", "permissions": ["server.read", "server.game-client.read", "server.game-client.command"], "bridgeActions": ["server.instances.read"], "featureKeys": ["reward.delivery"] }
|
||||
],
|
||||
"fileWorkspace": {
|
||||
"defaultDirectoryKey": "scum-config",
|
||||
"directories": [{ "key": "scum-config", "label": "服务器配置", "scope": "config" }, { "key": "scum-logs", "label": "日志文件", "scope": "logs" }],
|
||||
|
||||
@@ -3,4 +3,4 @@ import type { SCUMFeatureWorkspace } from "../features/contracts.js";
|
||||
|
||||
export const pluginPageBundle = { key: "scum-server-plugin", version: "1.0.3", integritySha256: "sha256:797c4e303c102f0316e71e4e5bda50a6ca96506a7cb368a42ecf858b236609b2" };
|
||||
|
||||
export function renderPluginPage(react: any, input: any) { return renderSCUMFeaturePage(react, { serverInstanceId: input.context.serverInstanceId, permissions: input.context.permissions, availability: input.availability, featureAvailability: input.availability.features, workspace: input.workspace as SCUMFeatureWorkspace | undefined, workspaceActions: input.workspaceActions }); }
|
||||
export function renderPluginPage(react: any, input: any) { return renderSCUMFeaturePage(react, { pageKey: input.page?.key ?? "files-config", pageTitle: input.page?.title ?? "文件管理", serverInstanceId: input.context.serverInstanceId, permissions: input.context.permissions, availability: input.availability, featureAvailability: input.availability.features, workspace: input.workspace as SCUMFeatureWorkspace | undefined, workspaceActions: input.workspaceActions }); }
|
||||
|
||||
@@ -486,7 +486,7 @@ describe("plugin manifest validation", () => {
|
||||
"restart.prepare",
|
||||
"maintenance.prepare"
|
||||
]));
|
||||
expect(manifest.pages.map((page) => page.key)).toContain("files-config");
|
||||
expect(manifest.pages.map((page) => page.key)).toEqual(expect.arrayContaining(["files-config", "players", "squads", "live-map", "gifts"]));
|
||||
expect(manifest.fileWorkspace?.defaultDirectoryKey).toBe("scum-config");
|
||||
expect(manifest.fileWorkspace?.directories.map((directory) => directory.key)).toEqual(["scum-config", "scum-logs"]);
|
||||
expect(manifest.fileWorkspace?.files.map((file) => file.key)).toEqual(expect.arrayContaining(["scum-server-settings", "scum-game-config", "scum-engine-config", "scum-game-user-settings", "scum-admin-log", "scum-chat-log", "scum-kill-log", "scum-login-log", "scum-server-log"]));
|
||||
@@ -741,6 +741,9 @@ describe("plugin manifest validation", () => {
|
||||
|
||||
it("accepts the Minecraft server plugin manifest", () => {
|
||||
expect(validateManifestFile("examples/minecraft-server-plugin/manifest.json")).toEqual([]);
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(pluginsRoot, "examples/minecraft-server-plugin/manifest.json"), "utf8")) as { pages: Array<{ key: string; title: string }>; fileWorkspace?: { files?: Array<{ key: string }> } };
|
||||
expect(manifest.pages.map((page) => `${page.key}:${page.title}`)).toEqual(["files:文件管理", "mods-market:mod/插件市场"]);
|
||||
expect(manifest.fileWorkspace?.files?.map((file) => file.key)).toEqual(expect.arrayContaining(["server-properties", "ops-json", "whitelist-json", "minecraft-latest-log"]));
|
||||
});
|
||||
|
||||
it("validates typed game-client bridge catalogs", () => {
|
||||
|
||||
Reference in New Issue
Block a user