Align SCUM operations with reference tools
This commit is contained in:
@@ -47,6 +47,14 @@ import type {
|
||||
GameClientBridgeSnapshotListResponse,
|
||||
GameClientBridgeSnapshotQuery,
|
||||
GameClientBridgeStatusResponse,
|
||||
GameGiftCatalogListResponse,
|
||||
GameGiftCatalogRequest,
|
||||
GameGiftCatalogResponse,
|
||||
GameGiftGrantListResponse,
|
||||
GameGiftGrantRequest,
|
||||
GameGiftGrantResponse,
|
||||
GameGiftRevisionListResponse,
|
||||
GameGiftRevisionResponse,
|
||||
GamePluginListResponse,
|
||||
HealthResponse,
|
||||
JobCreateRequest,
|
||||
@@ -628,6 +636,34 @@ export class PlatformApiClient {
|
||||
return this.request<SCUMListResponse>(`/server-instances/${encodeURIComponent(serverInstanceId)}/scum/positions`);
|
||||
}
|
||||
|
||||
async listGameGiftCatalogs(serverInstanceId: string): Promise<GameGiftCatalogListResponse> {
|
||||
return this.request<GameGiftCatalogListResponse>(`/server-instances/${encodeURIComponent(serverInstanceId)}/game-gifts`);
|
||||
}
|
||||
|
||||
async saveGameGiftCatalog(serverInstanceId: string, request: GameGiftCatalogRequest): Promise<GameGiftCatalogResponse> {
|
||||
return this.request<GameGiftCatalogResponse>(`/server-instances/${encodeURIComponent(serverInstanceId)}/game-gifts`, { method: "POST", body: request });
|
||||
}
|
||||
|
||||
async publishGameGiftCatalog(serverInstanceId: string, catalogId: string): Promise<GameGiftRevisionResponse> {
|
||||
return this.request<GameGiftRevisionResponse>(`/server-instances/${encodeURIComponent(serverInstanceId)}/game-gifts/${encodeURIComponent(catalogId)}/publish`, { method: "POST", body: {} });
|
||||
}
|
||||
|
||||
async listGameGiftRevisions(serverInstanceId: string, catalogId: string): Promise<GameGiftRevisionListResponse> {
|
||||
return this.request<GameGiftRevisionListResponse>(`/server-instances/${encodeURIComponent(serverInstanceId)}/game-gifts/${encodeURIComponent(catalogId)}/revisions`);
|
||||
}
|
||||
|
||||
async listGameGiftGrants(serverInstanceId: string): Promise<GameGiftGrantListResponse> {
|
||||
return this.request<GameGiftGrantListResponse>(`/server-instances/${encodeURIComponent(serverInstanceId)}/game-gift-grants`);
|
||||
}
|
||||
|
||||
async requestGameGiftGrant(serverInstanceId: string, request: GameGiftGrantRequest): Promise<GameGiftGrantResponse> {
|
||||
return this.request<GameGiftGrantResponse>(`/server-instances/${encodeURIComponent(serverInstanceId)}/game-gift-grants`, { method: "POST", body: request });
|
||||
}
|
||||
|
||||
async approveGameGiftGrant(serverInstanceId: string, grantId: string): Promise<GameGiftGrantResponse> {
|
||||
return this.request<GameGiftGrantResponse>(`/server-instances/${encodeURIComponent(serverInstanceId)}/game-gift-grants/${encodeURIComponent(grantId)}/approve`, { method: "POST", body: {} });
|
||||
}
|
||||
|
||||
async listSCUMOperations(serverInstanceId: string): Promise<SCUMOperationListResponse> {
|
||||
return this.request<SCUMOperationListResponse>(`/server-instances/${encodeURIComponent(serverInstanceId)}/scum/operations`);
|
||||
}
|
||||
|
||||
@@ -388,9 +388,12 @@ export interface GamePluginResponse {
|
||||
validationViolations?: string[];
|
||||
runtimeProfiles?: GamePluginRuntimeProfilesResponse;
|
||||
gameClientBridge?: GameClientBridgeManifestResponse;
|
||||
mapTrajectories?: GameMapGeometryResponse;
|
||||
status: GamePluginStatus;
|
||||
}
|
||||
|
||||
export interface GameMapGeometryResponse { mapId: string; mapVersion: string; worldMinX: number; worldMinY: number; worldMaxX: number; worldMaxY: number; imageWidth: number; imageHeight: number; precision: number; sampleDistance: number; sampleIntervalSeconds: number; retentionSeconds: number; }
|
||||
|
||||
export type PluginCreateFieldType = "text" | "number" | "boolean" | "select" | "port";
|
||||
|
||||
export interface PluginCreateFieldResponse {
|
||||
@@ -1386,6 +1389,15 @@ export type SCUMSquadsListResponse = SCUMListResponse<SCUMSquadRecord>;
|
||||
export type SCUMActivityListResponse = SCUMListResponse<SCUMActivityEventRecord>;
|
||||
export type SCUMGiftsListResponse = SCUMListResponse<SCUMGiftRecord>;
|
||||
export type SCUMMapPointsListResponse = SCUMListResponse<SCUMMapPointRecord>;
|
||||
export interface GameGiftItemResponse { catalogItemKey: string; label: string; quantity: number; }
|
||||
export interface GameGiftCatalogResponse { id: string; name: string; gameVersion: string; draftItems: GameGiftItemResponse[]; latestRevisionId?: string; updatedAt: string; }
|
||||
export interface GameGiftCatalogListResponse { items: GameGiftCatalogResponse[]; }
|
||||
export interface GameGiftRevisionResponse { id: string; catalogId: string; revision: number; gameVersion: string; items: GameGiftItemResponse[]; publishedBy: string; publishedAt: string; }
|
||||
export interface GameGiftRevisionListResponse { items: GameGiftRevisionResponse[]; }
|
||||
export interface GameGiftGrantResponse { id: string; revisionId: string; revisionNumber: number; gameVersion: string; items: GameGiftItemResponse[]; gamePlayerRecordId: string; playerDisplayName: string; notice: string; requesterId: string; approverId?: string; status: string; deliverySummary?: string; notificationSummary?: string; createdAt: string; approvedAt?: string; completedAt?: string; }
|
||||
export interface GameGiftGrantListResponse { items: GameGiftGrantResponse[]; }
|
||||
export interface GameGiftCatalogRequest { id?: string; name: string; gameVersion: string; items: Array<{ catalogItemKey: string; quantity: number }>; }
|
||||
export interface GameGiftGrantRequest { revisionId: string; gamePlayerRecordId: string; notice: string; idempotencyKey: string; }
|
||||
export interface SCUMWorkflowCreateRequest { templateKey: string; idempotencyKey: string; input?: SCUMJsonRecord; }
|
||||
export interface SCUMOperationRequest { templateKey: string; playerId?: string; payload?: SCUMJsonRecord; guard?: SCUMJsonRecord; reason: string; idempotencyKey: string; }
|
||||
export interface SCUMWorkflowResponse { id: string; serverInstanceId: string; pluginId: string; templateKey: string; requestedBy?: string; idempotencyKey?: string; status: string; currentStepKey?: string; input?: SCUMJsonRecord; safeSummary?: SCUMJsonRecord; blockerReason?: string; auditReferences?: string[]; createdAt: string; updatedAt: string; completedAt?: string; }
|
||||
|
||||
@@ -5,6 +5,7 @@ export interface PluginPageWorkspaceActions {
|
||||
listSCUMActivity?: () => Promise<unknown>;
|
||||
listSCUMGifts?: () => Promise<unknown>;
|
||||
listSCUMMapPoints?: () => Promise<unknown>;
|
||||
getSCUMMapGeometry?: () => unknown;
|
||||
listSCUMSquadMembers?: () => Promise<unknown>;
|
||||
listSCUMVehicles?: () => Promise<unknown>;
|
||||
listSCUMFlags?: () => Promise<unknown>;
|
||||
@@ -15,4 +16,10 @@ export interface PluginPageWorkspaceActions {
|
||||
listSCUMWorkflows?: () => Promise<unknown>;
|
||||
createSCUMWorkflow?: (request: unknown) => Promise<unknown>;
|
||||
listSCUMWorkflowSteps?: (workflowId?: string) => Promise<unknown>;
|
||||
listGameGiftCatalogs?: () => Promise<unknown>;
|
||||
saveGameGiftCatalog?: (request: unknown) => Promise<unknown>;
|
||||
publishGameGiftCatalog?: (catalogId: string) => Promise<unknown>;
|
||||
listGameGiftGrants?: () => Promise<unknown>;
|
||||
requestGameGiftGrant?: (request: unknown) => Promise<unknown>;
|
||||
approveGameGiftGrant?: (grantId: string) => Promise<unknown>;
|
||||
}
|
||||
|
||||
@@ -85,10 +85,10 @@ describe("PluginPageHostPage", () => {
|
||||
expect(html).not.toMatch(/sessionToken|componentKey|hostPath|dsn|runSocket|credential/i);
|
||||
});
|
||||
|
||||
it("renders the persisted SCUM dataset surface without a browser-side bundle dependency", () => {
|
||||
it("loads the declared SCUM bundle without a browser-side game branch", () => {
|
||||
const html = renderToStaticMarkup(<PluginPageHostPage {...props("")} initialPlugin={plugin} />);
|
||||
expect(html).toContain("未绑定服务器");
|
||||
expect(html).toContain("正在读取用户同步数据");
|
||||
expect(html).toContain("正在校验并加载插件页面 bundle");
|
||||
});
|
||||
|
||||
it("remains a manifest-driven host without SCUM component imports or game branches", () => {
|
||||
@@ -104,7 +104,7 @@ describe("PluginPageHostPage", () => {
|
||||
expect(hostSource).toContain("listSCUMActivity: () => platformApiClient.listSCUMActivity(serverId)");
|
||||
expect(hostSource).toContain("listSCUMGifts: () => platformApiClient.listSCUMGifts(serverId)");
|
||||
expect(hostSource).toContain("listSCUMMapPoints: () => platformApiClient.listSCUMMapPoints(serverId)");
|
||||
expect(hostSource).toContain("SCUMPersistedDataView");
|
||||
expect(hostSource).not.toContain("SCUMPersistedDataView");
|
||||
expect(hostSource).toContain("createSCUMWorkflow: (request) => platformApiClient.createSCUMWorkflow(serverId, request as never)");
|
||||
expect(hostSource).toContain("createSCUMOperation: (request) => platformApiClient.createSCUMOperation(serverId, request as never)");
|
||||
expect(hostSource).toContain("listSCUMWorkflowSteps: (workflowId) => platformApiClient.listSCUMWorkflowSteps(serverId, workflowId)");
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useCallback, useEffect, useMemo, useRef, useState, type ComponentType }
|
||||
import { platformApiClient } from "../api/client";
|
||||
import type { GamePluginResponse } from "../api/types";
|
||||
import { PageFrame } from "../components/PageFrame";
|
||||
import { SCUMPersistedDataView, scumDatasetForPluginRoute } from "../components/SCUMPersistedDataView";
|
||||
import { EmptyState, ErrorState, LoadingState } from "../components/StateViews";
|
||||
import type { PageComponentProps } from "../contracts/page";
|
||||
import { pluginBridgeManifestContractFromResponse } from "../contracts/pluginBridge";
|
||||
@@ -76,6 +75,7 @@ export function PluginPageHostPage({ params, onNavigate, initialPlugin, embedded
|
||||
listSCUMActivity: () => platformApiClient.listSCUMActivity(serverId),
|
||||
listSCUMGifts: () => platformApiClient.listSCUMGifts(serverId),
|
||||
listSCUMMapPoints: () => platformApiClient.listSCUMMapPoints(serverId),
|
||||
getSCUMMapGeometry: () => readyPluginRef.current?.mapTrajectories,
|
||||
listSCUMSquadMembers: () => platformApiClient.listSCUMSquadMembers(serverId),
|
||||
listSCUMVehicles: () => platformApiClient.listSCUMVehicles(serverId),
|
||||
listSCUMFlags: () => platformApiClient.listSCUMFlags(serverId),
|
||||
@@ -85,7 +85,13 @@ export function PluginPageHostPage({ params, onNavigate, initialPlugin, embedded
|
||||
approveSCUMOperation: (operationId) => platformApiClient.approveSCUMOperation(serverId, operationId),
|
||||
listSCUMWorkflows: () => platformApiClient.listSCUMWorkflows(serverId),
|
||||
createSCUMWorkflow: (request) => platformApiClient.createSCUMWorkflow(serverId, request as never),
|
||||
listSCUMWorkflowSteps: (workflowId) => platformApiClient.listSCUMWorkflowSteps(serverId, workflowId)
|
||||
listSCUMWorkflowSteps: (workflowId) => platformApiClient.listSCUMWorkflowSteps(serverId, workflowId),
|
||||
listGameGiftCatalogs: () => platformApiClient.listGameGiftCatalogs(serverId),
|
||||
saveGameGiftCatalog: (request) => platformApiClient.saveGameGiftCatalog(serverId, request as never),
|
||||
publishGameGiftCatalog: (catalogId) => platformApiClient.publishGameGiftCatalog(serverId, catalogId),
|
||||
listGameGiftGrants: () => platformApiClient.listGameGiftGrants(serverId),
|
||||
requestGameGiftGrant: (request) => platformApiClient.requestGameGiftGrant(serverId, request as never),
|
||||
approveGameGiftGrant: (grantId) => platformApiClient.approveGameGiftGrant(serverId, grantId)
|
||||
};
|
||||
}, [pluginId, serverId]);
|
||||
const bundleLoadKey = declaredBundlePage ? [declaredBundlePage.bundleKey, declaredBundlePage.bundleVersion, declaredBundlePage.bundleIntegritySha256, declaredBundlePage.path].join(":") : "";
|
||||
@@ -117,14 +123,12 @@ export function PluginPageHostPage({ params, onNavigate, initialPlugin, embedded
|
||||
if (!hostContext) {
|
||||
return <ErrorState title="插件页面不可用" reason="插件页面上下文初始化失败。" />;
|
||||
}
|
||||
const scumDataset = state.plugin.serverType === "scum" ? scumDatasetForPluginRoute(routeKey) : null;
|
||||
if (embedded) {
|
||||
return (
|
||||
<>
|
||||
{scumDataset && <SCUMPersistedDataView serverInstanceId={serverId} dataset={scumDataset} />}
|
||||
{!scumDataset && bundleError && <ErrorState title="插件页面不可用" reason={bundleError} />}
|
||||
{!scumDataset && !bundle && !bundleError && <LoadingState label="正在校验并加载插件页面 bundle…" compact />}
|
||||
{!scumDataset && bundle && React.createElement(bundle, { context: hostContext, workspaceActions, availability })}
|
||||
{bundleError && <ErrorState title="插件页面不可用" reason={bundleError} />}
|
||||
{!bundle && !bundleError && <LoadingState label="正在校验并加载插件页面 bundle…" compact />}
|
||||
{bundle && React.createElement(bundle, { context: hostContext, workspaceActions, availability })}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -152,10 +156,9 @@ export function PluginPageHostPage({ params, onNavigate, initialPlugin, embedded
|
||||
|
||||
<div className="action-list" aria-label="plugin page declarations"><span><strong>Bundle</strong> {page.bundleKey ? `${page.bundleKey}@${page.bundleVersion}` : "未声明"}</span><span><strong>完整性</strong> {page.bundleIntegritySha256 ? `${page.bundleIntegritySha256.slice(0, 18)}…` : "未声明"}</span><span><strong>Companion</strong> {availability.available ? "可用" : "不可用"}</span></div>
|
||||
</section>
|
||||
{scumDataset && <SCUMPersistedDataView serverInstanceId={serverId} dataset={scumDataset} />}
|
||||
{!scumDataset && bundleError && <ErrorState title="插件页面不可用" reason={bundleError} />}
|
||||
{!scumDataset && !bundle && !bundleError && <LoadingState label="正在校验并加载插件页面 bundle…" />}
|
||||
{!scumDataset && bundle && React.createElement(bundle, { context: hostContext, workspaceActions, availability })}
|
||||
{bundleError && <ErrorState title="插件页面不可用" reason={bundleError} />}
|
||||
{!bundle && !bundleError && <LoadingState label="正在校验并加载插件页面 bundle…" />}
|
||||
{bundle && React.createElement(bundle, { context: hostContext, workspaceActions, availability })}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user