feat: 完整游戏运维功能
This commit is contained in:
@@ -21,6 +21,7 @@ import type {
|
||||
ComponentKeyResponse,
|
||||
ComponentKeyResetRequest,
|
||||
CurrentUserResponse,
|
||||
DependencyCatalogResponse,
|
||||
DependencyJobRequest,
|
||||
FileOperationDispatchRequest,
|
||||
FileOperationDispatchResponse,
|
||||
@@ -50,7 +51,10 @@ import type {
|
||||
RunDistributionResponse,
|
||||
RunEndpointListResponse,
|
||||
RunUpdateJobResponse,
|
||||
RunUpdateJobListResponse,
|
||||
RunUpdateRequest,
|
||||
RuntimeBindingResponse,
|
||||
RuntimeBindingUpdateRequest,
|
||||
ServerConfigResponse,
|
||||
ServerConfigDiffPreviewRequest,
|
||||
ServerConfigDiffPreviewResponse,
|
||||
@@ -65,6 +69,12 @@ import type {
|
||||
ServerMemberListResponse,
|
||||
ServerMemberRequest,
|
||||
ServerMetricsListResponse,
|
||||
MetricSampleListResponse,
|
||||
BackupListResponse,
|
||||
BackupResponse,
|
||||
RemoteAdapterDeclarationListResponse,
|
||||
RemoteAdapterRequest,
|
||||
RemoteAdapterResponse,
|
||||
ServerRuntimeActionsResponse,
|
||||
UserCreateRequest,
|
||||
UserListResponse,
|
||||
@@ -75,13 +85,30 @@ import type {
|
||||
UserUpdateRequest
|
||||
} from "./types";
|
||||
import { readWebRuntimeEnv } from "../schemas/env";
|
||||
import { parseSafeDependencyCatalog, parseSafeRunUpdate, parseSafeRunUpdateList } from "../schemas/runtimeUpdates";
|
||||
|
||||
let platformApiSessionToken: string | null = null;
|
||||
let platformApiAuthFailureHandler: ((error: PlatformApiError) => void) | null = null;
|
||||
|
||||
export function setPlatformApiSessionToken(token: string | null) {
|
||||
platformApiSessionToken = token;
|
||||
}
|
||||
|
||||
export function setPlatformApiAuthFailureHandler(handler: ((error: PlatformApiError) => void) | null) {
|
||||
platformApiAuthFailureHandler = handler;
|
||||
}
|
||||
|
||||
export class PlatformApiError extends Error {
|
||||
constructor(
|
||||
readonly status: number,
|
||||
readonly code: string,
|
||||
message: string
|
||||
) {
|
||||
super(message);
|
||||
this.name = "PlatformApiError";
|
||||
}
|
||||
}
|
||||
|
||||
export class PlatformApiClient {
|
||||
constructor(private readonly baseUrl = "/api/v1", private readonly sessionTokenProvider: () => string | null = () => platformApiSessionToken) {}
|
||||
|
||||
@@ -119,6 +146,14 @@ export class PlatformApiClient {
|
||||
});
|
||||
}
|
||||
|
||||
async getServerRuntimeBinding(id: string): Promise<RuntimeBindingResponse> {
|
||||
return this.request<RuntimeBindingResponse>(`/server-instances/${encodeURIComponent(id)}/runtime-binding`);
|
||||
}
|
||||
|
||||
async updateServerRuntimeBinding(id: string, request: RuntimeBindingUpdateRequest): Promise<RuntimeBindingResponse> {
|
||||
return this.request<RuntimeBindingResponse>(`/server-instances/${encodeURIComponent(id)}/runtime-binding`, { method: "PUT", body: request });
|
||||
}
|
||||
|
||||
async startServerInstance(id: string, request: ServerLifecycleCommandRequest): Promise<ServerLifecycleResponse> {
|
||||
return this.request<ServerLifecycleResponse>(`/server-instances/${encodeURIComponent(id)}/start`, {
|
||||
method: "POST",
|
||||
@@ -126,12 +161,19 @@ export class PlatformApiClient {
|
||||
});
|
||||
}
|
||||
|
||||
async stopServerInstance(id: string, request: ServerLifecycleCommandRequest): Promise<ServerLifecycleResponse> {
|
||||
async stopServerInstance(id: string, request: ServerLifecycleCommandRequest): Promise<ServerLifecycleResponse> {
|
||||
return this.request<ServerLifecycleResponse>(`/server-instances/${encodeURIComponent(id)}/stop`, {
|
||||
method: "POST",
|
||||
body: request
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async queryServerProcessStatus(id: string, request: ServerLifecycleCommandRequest): Promise<ServerLifecycleResponse> {
|
||||
return this.request<ServerLifecycleResponse>(`/server-instances/${encodeURIComponent(id)}/process/status`, {
|
||||
method: "POST",
|
||||
body: request
|
||||
});
|
||||
}
|
||||
|
||||
async listServerAdministratorCandidates(id: string): Promise<ServerMemberListResponse> {
|
||||
return this.request<ServerMemberListResponse>(`/server-instances/${encodeURIComponent(id)}/administrators/candidates`);
|
||||
@@ -177,10 +219,9 @@ export class PlatformApiClient {
|
||||
if (sessionToken) {
|
||||
headers.set("Authorization", `Bearer ${sessionToken}`);
|
||||
}
|
||||
const response = await fetch(`${this.baseUrl}/artifacts/${encodeURIComponent(id)}/content?${params.toString()}`, { headers });
|
||||
const response = await fetch(`${this.baseUrl}/artifacts/${encodeURIComponent(id)}/content?${params.toString()}`, { headers, credentials: "same-origin" });
|
||||
if (!response.ok) {
|
||||
const apiError = await safeReadError(response);
|
||||
throw new Error(apiError?.message ?? `request failed: ${response.status}`);
|
||||
throw await responseError(response);
|
||||
}
|
||||
const payload = await response.arrayBuffer();
|
||||
return {
|
||||
@@ -225,10 +266,15 @@ export class PlatformApiClient {
|
||||
}
|
||||
|
||||
async pushRunUpdate(id: string, request: RunUpdateRequest): Promise<RunUpdateJobResponse> {
|
||||
return this.request<RunUpdateJobResponse>(`/server-instances/${encodeURIComponent(id)}/run/update`, {
|
||||
const response = await this.request<unknown>(`/server-instances/${encodeURIComponent(id)}/run/update`, {
|
||||
method: "POST",
|
||||
body: request
|
||||
});
|
||||
return parseSafeRunUpdate(response);
|
||||
}
|
||||
|
||||
async listRunUpdates(id: string): Promise<RunUpdateJobListResponse> {
|
||||
return parseSafeRunUpdateList(await this.request<unknown>(`/server-instances/${encodeURIComponent(id)}/run/update`));
|
||||
}
|
||||
|
||||
async generateClientManager(id: string, request: ClientManagerBuildRequest): Promise<ClientManagerDistributionResponse> {
|
||||
@@ -259,6 +305,10 @@ export class PlatformApiClient {
|
||||
});
|
||||
}
|
||||
|
||||
async getDependencyCatalog(id: string): Promise<DependencyCatalogResponse> {
|
||||
return parseSafeDependencyCatalog(await this.request<unknown>(`/server-instances/${encodeURIComponent(id)}/dependencies`));
|
||||
}
|
||||
|
||||
async installDependencies(id: string, request: DependencyJobRequest): Promise<JobResponse> {
|
||||
return this.request<JobResponse>(`/server-instances/${encodeURIComponent(id)}/dependencies/install`, {
|
||||
method: "POST",
|
||||
@@ -340,6 +390,27 @@ export class PlatformApiClient {
|
||||
return this.request<ServerMetricsListResponse>("/metrics/server-instances");
|
||||
}
|
||||
|
||||
async listMetricHistory(serverInstanceId: string, limit = 100): Promise<MetricSampleListResponse> {
|
||||
const params = new URLSearchParams({ serverInstanceId, limit: String(limit) });
|
||||
return this.request<MetricSampleListResponse>(`/metrics/server-instances/history?${params.toString()}`);
|
||||
}
|
||||
|
||||
async listBackups(serverInstanceId: string): Promise<BackupListResponse> {
|
||||
return this.request<BackupListResponse>(`/backups?serverInstanceId=${encodeURIComponent(serverInstanceId)}`);
|
||||
}
|
||||
|
||||
async getBackup(id: string): Promise<BackupResponse> {
|
||||
return this.request<BackupResponse>(`/backups/${encodeURIComponent(id)}`);
|
||||
}
|
||||
|
||||
async listRemoteAdapters(serverInstanceId: string): Promise<RemoteAdapterDeclarationListResponse> {
|
||||
return this.request<RemoteAdapterDeclarationListResponse>(`/server-instances/${encodeURIComponent(serverInstanceId)}/remote-adapters`);
|
||||
}
|
||||
|
||||
async requestRemoteAdapter(serverInstanceId: string, request: RemoteAdapterRequest): Promise<RemoteAdapterResponse> {
|
||||
return this.request<RemoteAdapterResponse>(`/server-instances/${encodeURIComponent(serverInstanceId)}/remote-adapters`, { method: "POST", body: request });
|
||||
}
|
||||
|
||||
async getServerConfig(id: string): Promise<ServerConfigResponse> {
|
||||
return this.request<ServerConfigResponse>(`/server-instances/${encodeURIComponent(id)}/config`);
|
||||
}
|
||||
@@ -446,14 +517,14 @@ export class PlatformApiClient {
|
||||
|
||||
const response = await fetch(options.absolute ? path : `${this.baseUrl}${path}`, {
|
||||
...options.init,
|
||||
credentials: options.init?.credentials ?? "same-origin",
|
||||
method: options.method ?? options.init?.method ?? "GET",
|
||||
headers,
|
||||
body: options.body === undefined ? options.init?.body : JSON.stringify(options.body)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const apiError = await safeReadError(response);
|
||||
throw new Error(apiError?.message ?? `request failed: ${response.status}`);
|
||||
throw await responseError(response);
|
||||
}
|
||||
|
||||
if (options.parseJson === false || response.status === 204) {
|
||||
@@ -480,6 +551,21 @@ async function safeReadError(response: Response): Promise<ApiErrorResponse | nul
|
||||
}
|
||||
}
|
||||
|
||||
async function responseError(response: Response): Promise<PlatformApiError> {
|
||||
const apiError = await safeReadError(response);
|
||||
const message = response.status === 401
|
||||
? "会话已失效,请重新登录。"
|
||||
: response.status === 403
|
||||
? "没有权限访问该资源。"
|
||||
: apiError?.message ?? `request failed: ${response.status}`;
|
||||
const error = new PlatformApiError(response.status, apiError?.code ?? "request_failed", message);
|
||||
if (response.status === 401) {
|
||||
platformApiSessionToken = null;
|
||||
platformApiAuthFailureHandler?.(error);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
function marketplaceQuery(filter: MarketplacePluginFilterRequest): string {
|
||||
const params = new URLSearchParams();
|
||||
if (filter.status && filter.status !== "all") {
|
||||
|
||||
Reference in New Issue
Block a user