Stream live server logs over SSE

This commit is contained in:
npc0-hue
2026-08-03 22:28:54 +08:00
parent 5d4fca14f9
commit 7eac1926dd
48 changed files with 1526 additions and 263 deletions
+12
View File
@@ -55,6 +55,7 @@ import type {
LlmConfigSuggestionRequest,
LlmConfigSuggestionResponse,
LogBackfillRequest,
LogStreamEventOptions,
LogStreamCursorRequest,
LogStreamCursorResponse,
LogStreamListResponse,
@@ -446,6 +447,17 @@ export class PlatformApiClient {
return this.request<LogStreamListResponse>(`/server-instances/${encodeURIComponent(id)}/logs/live`);
}
openServerLogEvents(id: string, options: LogStreamEventOptions = {}): EventSource {
return new EventSource(this.serverLogEventsUrl(id, options), { withCredentials: true });
}
serverLogEventsUrl(id: string, options: LogStreamEventOptions = {}): string {
const params = new URLSearchParams();
if (options.historyLimit !== undefined) params.set("historyLimit", String(options.historyLimit));
const query = params.toString();
return `${this.baseUrl}/server-instances/${encodeURIComponent(id)}/logs/events${query ? `?${query}` : ""}`;
}
async requestLogBackfill(id: string, request: LogBackfillRequest): Promise<JobResponse> {
return this.request<JobResponse>(`/server-instances/${encodeURIComponent(id)}/logs/backfill`, {
method: "POST",
+1 -1
View File
@@ -60,7 +60,7 @@ Existing platform APIs already cover server lifecycle, jobs, log stream metadata
- Operation/job traceability reuses `GET /api/v1/jobs`, `GET /api/v1/jobs/{id}`, `POST /api/v1/jobs/{id}/cancel`, and `GET /api/v1/audit-events`; the frontend wraps these in one visible operation lifecycle per user intent.
Browser Job contracts explicitly exclude raw or hashed lease tokens, Run session tokens/generations, secret refs, host paths, sockets, and credentials. The safe schema rejects those keys, and existing API client 401/403 behavior remains authoritative for expired sessions and cross-owner access.
- Log filtering by level/keyword/time/source is applied client-side over `POST /api/v1/log-streams/query` (`LogStreamCursorRequest`) results until the platform exposes server-side filters.
- Live log and management-terminal output uses `GET /api/v1/server-instances/{id}/logs/events` as a single `EventSource`/SSE stream with bounded initial history. `POST /api/v1/log-streams/query` remains available for explicit historical cursor reads and reconnect repair, not periodic browser polling.
# Client Manager API projection
`PlatformApiClient` exposes list/detail and typed deploy, control, update, retry, revoke-session, and uninstall methods. `schemas/clientManagerLifecycle.ts` validates status/action/job/health fields and rejects forbidden machine or credential fields before rendering. Lifecycle commands carry profile, distribution, expected deployment generation, approval/confirmation, and idempotency only. Artifact bytes remain in the platform-owned artifact transfer client.
+21 -2
View File
@@ -13,10 +13,10 @@ export type DependencyState = "unknown" | "present" | "missing" | "installing" |
export type RunUpdatePhase = "queued" | "downloading" | "staged" | "restart-requested" | "activating" | "succeeded" | "rolled-back" | "failed";
export type ServerLifecycleAction = "create" | "start" | "stop" | "status";
export type GameClientBridgeCommandState = "pending" | "claimed" | "succeeded" | "failed" | "cancelled" | "expired";
export type GameClientBridgeCommandState = "pending" | "claimed" | "succeeded" | "failed" | "cancelled" | "expired" | "unknown";
export type GameClientBridgeApprovalState = "not_required" | "pending" | "approved" | "rejected";
export type GameClientBridgeApprovalLevel = "none" | "operator" | "platform-admin";
export type GameClientBridgeResultStatus = "succeeded" | "failed" | "cancelled";
export type GameClientBridgeResultStatus = "succeeded" | "failed" | "cancelled" | "unknown";
export type GameClientBridgeJsonValue = string | number | boolean | null | GameClientBridgeJsonValue[] | GameClientBridgeJsonObject;
export interface GameClientBridgeJsonObject {
@@ -1494,6 +1494,25 @@ export interface LogStreamCursorResponse {
latestSeq: number;
}
export interface LogStreamEventResponse {
serverInstanceId: string;
streamId: string;
source: string;
streamKey: string;
latestSeq: number;
entry: LogEntryBody;
}
export interface LogStreamEventsReadyResponse {
serverInstanceId: string;
streamCount: number;
serverTime: string;
}
export interface LogStreamEventOptions {
historyLimit?: number;
}
export interface AuditEventResponse {
id: string;
actorId: string;