feat(api): 访问日志与 HTTP/运行时指标、安全响应头与面板 CSP、跨站写拦截、请求体读截止、SSE 定期重鉴权与续传、404/405/413 信封、status 按所有权裁剪、停机并发排空

This commit is contained in:
Lemon-miaow committed 2026-09-25 02:14:08 +08:00
1 parent cc85aac906
commit f8f112b8ca
24 files changed
+1453 -55

No files matched your search

+22
View File
@@ -18,6 +18,7 @@ class FakeEventSource implements EventSourceLike {
readyState = 0; // CONNECTING
closed = false;
readonly url: string;
private readonly listeners = new Map<string, Array<(ev: unknown) => void>>();
constructor(url: string) {
this.url = url;
@@ -28,6 +29,13 @@ class FakeEventSource implements EventSourceLike {
this.readyState = 2; // CLOSED
}
addEventListener(type: string, listener: (ev: unknown) => void): void {
this.listeners.set(type, [...(this.listeners.get(type) ?? []), listener]);
}
emitEvent(type: string): void {
for (const fn of this.listeners.get(type) ?? []) fn({ data: "" });
}
// Test drivers mirroring what the browser would invoke.
emitOpen(): void {
this.readyState = 1; // OPEN
@@ -181,4 +189,18 @@ describe("LogStreamController", () => {
expect(ctrl.getSnapshot().lines).toHaveLength(0);
expect(ctrl.getSnapshot().status).toBe("open");
});
it("ends for good when the server revokes the stream", () => {
const { factory, created } = makeFactory();
const ctrl = new LogStreamController({ url: "/api/v1/servers/s/console", factory });
ctrl.open();
const es = created[0];
es.emitOpen();
es.emitMessage("[12:00:00] [Server thread/INFO]: hello");
es.emitEvent("revoked");
expect(ctrl.getSnapshot().status).toBe("ended");
expect(es.closed).toBe(true);
expect(ctrl.getSnapshot().lines).toHaveLength(1);
expect(created).toHaveLength(1);
});
});
+9
View File
@@ -59,6 +59,7 @@ export interface EventSourceLike {
onerror: ((ev: any) => void) | null;
readyState: number;
close(): void;
addEventListener(type: string, listener: (ev: any) => void): void;
}
/** Builds an EventSource for a URL (production: the browser ctor; tests: a fake). */
@@ -175,6 +176,14 @@ export class LogStreamController {
this.es = es;
es.onopen = () => this.setStatus("open");
es.onmessage = (ev) => this.pushLine(ev.data);
// The server re-checks a running stream's grant (session still live, caller
// still owns the server) and sends "revoked" before it closes. That close is
// final: an auto-retry would only be refused again.
es.addEventListener("revoked", () => {
if (this.es !== es) return;
this.disconnect();
this.setStatus("ended");
});
es.onerror = () => {
// Native EventSource auto-reconnects on a transient drop (readyState
// returns to CONNECTING); only a fatal response — non-200 / wrong