Remove terminal log replay

This commit is contained in:
npc0-hue
2026-08-25 22:25:09 +08:00
parent 725b71b6b6
commit 7ebe3bb3dd
9 changed files with 40 additions and 136 deletions
+3 -4
View File
@@ -805,8 +805,7 @@ describe("PlatformApiClient AI providers", () => {
it("builds encoded server log event stream URLs for the terminal drawer", () => {
const client = new PlatformApiClient("/api/v1");
expect(client.serverLogEventsUrl("server/scum 1", { historyLimit: 500 })).toBe("/api/v1/server-instances/server%2Fscum%201/logs/events?historyLimit=500");
expect(client.serverLogEventsUrl("server-1", { historyLimit: 0 })).toBe("/api/v1/server-instances/server-1/logs/events?historyLimit=0");
expect(client.serverLogEventsUrl("server/scum 1")).toBe("/api/v1/server-instances/server%2Fscum%201/logs/events");
expect(client.serverLogEventsUrl("server-1")).toBe("/api/v1/server-instances/server-1/logs/events");
});
@@ -830,7 +829,7 @@ describe("PlatformApiClient AI providers", () => {
vi.stubGlobal("fetch", fetchMock);
const client = new PlatformApiClient("/api/v1", () => "terminal-session");
const events: string[] = [];
const stream = client.openServerLogEvents("server-1", { historyLimit: 2 });
const stream = client.openServerLogEvents("server-1");
stream.addEventListener("session", (event) => events.push(`session:${event.data}`));
stream.addEventListener("stream", (event) => events.push(`stream:${event.data}`));
stream.addEventListener("log", (event) => events.push(`log:${event.data}`));
@@ -842,7 +841,7 @@ describe("PlatformApiClient AI providers", () => {
'log:{"streamId":"log-1","entry":{"seq":3,"line":"live line"}}',
'ready:{"serverInstanceId":"server-1"}'
]));
expect(String(fetchMock.mock.calls[0]?.[0])).toBe("/api/v1/server-instances/server-1/logs/events?historyLimit=2");
expect(String(fetchMock.mock.calls[0]?.[0])).toBe("/api/v1/server-instances/server-1/logs/events");
stream.close();
});
+4 -8
View File
@@ -53,7 +53,6 @@ import type {
LlmConfigSuggestionResponse,
LogStreamCursorRequest,
LogStreamCursorResponse,
LogStreamEventOptions,
LogStreamListResponse,
LoginRequest,
MarketplacePluginFilterRequest,
@@ -634,8 +633,8 @@ export class PlatformApiClient {
return this.request<LogStreamListResponse>(`/log-streams${query}`);
}
openServerLogEvents(id: string, options: LogStreamEventOptions = {}): PlatformEventStream {
const url = this.serverLogEventsUrl(id, options);
openServerLogEvents(id: string): PlatformEventStream {
const url = this.serverLogEventsUrl(id);
const sessionToken = this.sessionTokenProvider();
if (!sessionToken) {
return new EventSource(url, { withCredentials: true });
@@ -643,11 +642,8 @@ export class PlatformApiClient {
return new FetchServerSentEventStream(url, sessionToken);
}
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}` : ""}`;
serverLogEventsUrl(id: string): string {
return `${this.baseUrl}/server-instances/${encodeURIComponent(id)}/logs/events`;
}
async queryLogStream(request: LogStreamCursorRequest): Promise<LogStreamCursorResponse> {
+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}`, and `POST /api/v1/jobs/{id}/cancel`; 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.
- Server-scoped terminal log streaming (`GET /api/v1/server-instances/{id}/logs/events`) is used by the server detail terminal drawer for platform-accepted live SSE output by default, with bounded current-session replay only when the caller explicitly sends `historyLimit`. Raw log list/backfill routes (`GET .../logs/live`, `POST .../logs/backfill`) and direct management-terminal/RCON input routes remain removed from product clients; internal log ingest and cursor query remain available to platform services and maintenance/debug flows.
- Server-scoped terminal log streaming (`GET /api/v1/server-instances/{id}/logs/events`) is used by the server detail terminal drawer for platform-accepted live SSE output only. It does not replay retained log entries; raw log list/backfill routes (`GET .../logs/live`, `POST .../logs/backfill`) and direct management-terminal/RCON input routes remain removed from product clients, and internal log ingest and cursor query remain available to platform services and maintenance/debug flows.
# 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.
-4
View File
@@ -1631,10 +1631,6 @@ export interface LogStreamEventsSessionResponse {
serverTime: string;
}
export interface LogStreamEventOptions {
historyLimit?: number;
}
export interface JobCreateRequest {
id: string;
serverInstanceId?: string;
@@ -74,16 +74,16 @@ describe("ServerManagementTerminalDrawer", () => {
await emitSession("session-a");
await emitStream(logStream("stdout-a", "session-a", "process.stdout"));
await emitLog("stdout-a", "session-a", "process.stdout", logEntry(1, "generation A current replay"));
await emitLog("stdout-a", "session-a", "process.stdout", logEntry(1, "generation A live output"));
await emitReady();
expect(container?.textContent).toContain("generation A current replay");
expect(container?.textContent).toContain("generation A live output");
expect(container?.textContent).toContain("当前受管进程会话 · SSE 实时推送");
await emitSession("session-a");
await emitLog("stdout-a", "session-a", "process.stdout", logEntry(1, "generation A current replay"));
expect(container?.textContent).toContain("generation A current replay");
expect(Array.from(container?.querySelectorAll(".terminal-text") ?? []).filter((node) => node.textContent === "generation A current replay")).toHaveLength(1);
await emitLog("stdout-a", "session-a", "process.stdout", logEntry(1, "generation A live output"));
expect(container?.textContent).toContain("generation A live output");
expect(Array.from(container?.querySelectorAll(".terminal-text") ?? []).filter((node) => node.textContent === "generation A live output")).toHaveLength(1);
expect(apiMocks.openServerLogEvents).toHaveBeenCalledTimes(1);
expect(apiMocks.listLogStreams).not.toHaveBeenCalled();
expect(apiMocks.dispatchSourceRCONCommand).not.toHaveBeenCalled();
@@ -211,7 +211,7 @@ async function renderDrawer() {
await act(async () => {
root?.render(<ServerManagementTerminalDrawer open serverId="server-1" serverName="SCUM Alpha" pluginId="game.scum" canManage onClose={() => undefined} />);
});
expect(apiMocks.openServerLogEvents).toHaveBeenCalledWith("server-1", { historyLimit: 0 });
expect(apiMocks.openServerLogEvents).toHaveBeenCalledWith("server-1");
}
async function emitSession(logSessionId?: string, serverTime = "2026-08-14T00:00:00Z") {
@@ -16,7 +16,6 @@ type TerminalQuickCommand = { label: string; command: string; hint: string };
const terminalJobResultPollMs = 1000;
const terminalJobResultPollAttempts = 30;
const terminalLiveReplayWindow = 0;
const terminalHistoryWindow = 500;
const maxTerminalLines = 10000;
const terminalQuickCommandCatalog: Record<string, TerminalQuickCommand[]> = {
@@ -158,7 +157,7 @@ export function ServerManagementTerminalDrawer({ open, serverId, serverName, plu
useEffect(() => {
if (!open) return undefined;
let ready = false;
const events = platformApiClient.openServerLogEvents(serverId, { historyLimit: terminalLiveReplayWindow });
const events = platformApiClient.openServerLogEvents(serverId);
events.addEventListener("session", (event) => {
const session = parseLogSessionEvent(event);
if (!session) return;