147 lines
4.9 KiB
TypeScript
147 lines
4.9 KiB
TypeScript
import type {
|
|
GameClientBridgeCommandDeclarationResponse,
|
|
GameClientBridgeQueryTemplateDeclarationResponse,
|
|
GameClientBridgeSnapshotDeclarationResponse,
|
|
GamePluginResponse,
|
|
PluginProductionLifecycleDeclaration,
|
|
RuntimeLogEventResponse,
|
|
RuntimeLogSourceResponse
|
|
} from "../api/types";
|
|
import {
|
|
isPluginBridgeAction,
|
|
isPluginPermission,
|
|
type PluginBridgeAction,
|
|
type PluginPermission
|
|
} from "./pluginBridge";
|
|
|
|
export const scumOperationsPluginId = "game.scum";
|
|
export const scumOperationsRouteKey = "files-config";
|
|
|
|
export interface ScumOperationsPageContract {
|
|
pluginId: typeof scumOperationsPluginId;
|
|
routeKey: typeof scumOperationsRouteKey;
|
|
serverInstanceId: string;
|
|
title: string;
|
|
permissions: PluginPermission[];
|
|
bridgeActions: PluginBridgeAction[];
|
|
commands: GameClientBridgeCommandDeclarationResponse[];
|
|
snapshots: GameClientBridgeSnapshotDeclarationResponse[];
|
|
queryTemplates: GameClientBridgeQueryTemplateDeclarationResponse[];
|
|
logSources: RuntimeLogSourceResponse[];
|
|
logEvents: RuntimeLogEventResponse[];
|
|
productionLifecycle: PluginProductionLifecycleDeclaration;
|
|
}
|
|
|
|
export interface ScumCompanionHealthView {
|
|
status: "online" | "degraded" | "offline" | "unknown";
|
|
version?: string;
|
|
observedAt?: string;
|
|
latencyMs?: number;
|
|
capabilities: string[];
|
|
}
|
|
|
|
export interface ScumSessionView {
|
|
sessionId: string;
|
|
playerName: string;
|
|
startedAt?: string;
|
|
}
|
|
|
|
export interface ScumPlayerView {
|
|
playerId: string;
|
|
playerName: string;
|
|
status: string;
|
|
squadId?: string;
|
|
pingMs?: number;
|
|
lastSeenAt?: string;
|
|
}
|
|
|
|
export interface ScumSquadView {
|
|
squadId: string;
|
|
name: string;
|
|
memberCount: number;
|
|
leaderPlayerId?: string;
|
|
lastActiveAt?: string;
|
|
}
|
|
|
|
export interface ScumVehicleView {
|
|
vehicleId: string;
|
|
vehicleType: string;
|
|
status: string;
|
|
ownerPlayerId?: string;
|
|
squadId?: string;
|
|
fuelPercent?: number;
|
|
healthPercent?: number;
|
|
lastSeenAt?: string;
|
|
}
|
|
|
|
export interface ScumFlagView {
|
|
flagId: string;
|
|
status: string;
|
|
ownerPlayerId?: string;
|
|
squadId?: string;
|
|
radiusMeters?: number;
|
|
lastUpdatedAt?: string;
|
|
}
|
|
|
|
export interface ScumSnapshotCollection<T> {
|
|
observedAt?: string;
|
|
total: number;
|
|
items: T[];
|
|
}
|
|
|
|
export interface ScumOperationsSnapshotView {
|
|
health?: ScumCompanionHealthView;
|
|
sessions: ScumSnapshotCollection<ScumSessionView>;
|
|
players: ScumSnapshotCollection<ScumPlayerView>;
|
|
squads: ScumSnapshotCollection<ScumSquadView>;
|
|
vehicles: ScumSnapshotCollection<ScumVehicleView>;
|
|
flags: ScumSnapshotCollection<ScumFlagView>;
|
|
}
|
|
|
|
export type ScumOperationsPageResolution =
|
|
| { available: true; contract: ScumOperationsPageContract }
|
|
| { available: false; reason: string };
|
|
|
|
type ScumPluginProjection = Pick<GamePluginResponse, "id" | "pages" | "gameClientBridge" | "runtimeProfiles" | "productionLifecycle" | "fileWorkspace">;
|
|
|
|
export function normalizeScumRouteKey(pluginId: string, routeKey: string): string { return pluginId === scumOperationsPluginId && ["overview", "operations", "config", "logs"].includes(routeKey) ? scumOperationsRouteKey : routeKey; }
|
|
|
|
export function resolveScumOperationsPageContract(plugin: ScumPluginProjection, serverInstanceId: string): ScumOperationsPageResolution {
|
|
if (plugin.id !== scumOperationsPluginId) {
|
|
return { available: false, reason: "该路由仅承载 game.scum 插件声明的运维页。" };
|
|
}
|
|
if (!serverInstanceId.trim()) {
|
|
return { available: false, reason: "缺少服务器实例上下文。" };
|
|
}
|
|
const page = plugin.pages.find((candidate) => candidate.key === scumOperationsRouteKey);
|
|
if (!page) {
|
|
return { available: false, reason: "SCUM 插件未声明 operations 页面。" };
|
|
}
|
|
const manifest = plugin.gameClientBridge;
|
|
const bridgePage = manifest?.pages?.find((candidate) => candidate.pageKey === scumOperationsRouteKey);
|
|
if (!manifest || !bridgePage) {
|
|
return { available: false, reason: "SCUM 插件未声明 operations 的 Game Client Bridge 契约。" };
|
|
}
|
|
|
|
const commandTypes = new Set(bridgePage.commandTypes ?? []);
|
|
const snapshotTypes = new Set(bridgePage.snapshotTypes ?? []);
|
|
const queryTemplateKeys = new Set(bridgePage.queryTemplateKeys ?? []);
|
|
return {
|
|
available: true,
|
|
contract: {
|
|
pluginId: scumOperationsPluginId,
|
|
routeKey: scumOperationsRouteKey,
|
|
serverInstanceId,
|
|
title: page.title,
|
|
permissions: page.permissions.filter(isPluginPermission),
|
|
bridgeActions: (page.bridgeActions ?? []).filter(isPluginBridgeAction),
|
|
commands: manifest.commands.filter((command) => commandTypes.has(command.type)),
|
|
snapshots: manifest.snapshots.filter((snapshot) => snapshotTypes.has(snapshot.type)),
|
|
queryTemplates: (manifest.queryTemplates ?? []).filter((template) => queryTemplateKeys.has(template.key)),
|
|
logSources: [...(plugin.runtimeProfiles?.logSources ?? [])],
|
|
logEvents: [...(plugin.runtimeProfiles?.logEvents ?? [])],
|
|
productionLifecycle: plugin.productionLifecycle
|
|
}
|
|
};
|
|
}
|