Restore terminal log event stream
This commit is contained in:
@@ -20,7 +20,7 @@ const (
|
|||||||
logEventHeartbeatInterval = 15 * time.Second
|
logEventHeartbeatInterval = 15 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
// serverLogEvents is kept as legacy service plumbing but is not registered as a browser product route.
|
// serverLogEvents streams platform-accepted server log history and live append events for the terminal drawer.
|
||||||
func (h *coreHandlers) serverLogEvents(w http.ResponseWriter, r *http.Request) {
|
func (h *coreHandlers) serverLogEvents(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
writeMethodNotAllowed(w, http.MethodGet)
|
writeMethodNotAllowed(w, http.MethodGet)
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -41,7 +44,12 @@ func TestLogEventsSSEReplaysHistory(t *testing.T) {
|
|||||||
batch := validLogBatchRequest(t, hello.SessionToken, 1, 2)
|
batch := validLogBatchRequest(t, hello.SessionToken, 1, 2)
|
||||||
assertStatus(t, performJSON(t, router, http.MethodPost, "/api/v1/run/logs/batches", batch), http.StatusOK)
|
assertStatus(t, performJSON(t, router, http.MethodPost, "/api/v1/run/logs/batches", batch), http.StatusOK)
|
||||||
|
|
||||||
assertStatus(t, performJSON(t, router, http.MethodGet, "/api/v1/server-instances/server-1/logs/events?historyLimit=2", nil), http.StatusNotFound)
|
recorder := performCancelledSSE(t, router, "/api/v1/server-instances/server-1/logs/events?historyLimit=2")
|
||||||
|
assertStatus(t, recorder, http.StatusOK)
|
||||||
|
body := recorder.Body.String()
|
||||||
|
if !strings.Contains(recorder.Header().Get("Content-Type"), "text/event-stream") || !strings.Contains(body, "event: stream") || !strings.Contains(body, "event: log") || !strings.Contains(body, "event: ready") || !strings.Contains(body, `"seq":1`) || !strings.Contains(body, `"seq":2`) {
|
||||||
|
t.Fatalf("expected stream, history log, and ready SSE events, headers=%v body=%s", recorder.Header(), body)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLogEventsSSEUsesServerWideNewestHistory(t *testing.T) {
|
func TestLogEventsSSEUsesServerWideNewestHistory(t *testing.T) {
|
||||||
@@ -54,7 +62,22 @@ func TestLogEventsSSEUsesServerWideNewestHistory(t *testing.T) {
|
|||||||
assertStatus(t, performJSON(t, router, http.MethodPost, "/api/v1/run/logs/batches", validLogBatchRequestForStream(t, hello.SessionToken, "log-1", "stdout", 1, 2, 0)), http.StatusOK)
|
assertStatus(t, performJSON(t, router, http.MethodPost, "/api/v1/run/logs/batches", validLogBatchRequestForStream(t, hello.SessionToken, "log-1", "stdout", 1, 2, 0)), http.StatusOK)
|
||||||
assertStatus(t, performJSON(t, router, http.MethodPost, "/api/v1/run/logs/batches", validLogBatchRequestForStream(t, hello.SessionToken, "log-2", "stderr", 1, 2, 10)), http.StatusOK)
|
assertStatus(t, performJSON(t, router, http.MethodPost, "/api/v1/run/logs/batches", validLogBatchRequestForStream(t, hello.SessionToken, "log-2", "stderr", 1, 2, 10)), http.StatusOK)
|
||||||
|
|
||||||
assertStatus(t, performJSON(t, router, http.MethodGet, "/api/v1/server-instances/server-1/logs/events?historyLimit=2", nil), http.StatusNotFound)
|
recorder := performCancelledSSE(t, router, "/api/v1/server-instances/server-1/logs/events?historyLimit=2")
|
||||||
|
assertStatus(t, recorder, http.StatusOK)
|
||||||
|
body := recorder.Body.String()
|
||||||
|
if strings.Count(body, "event: log") != 2 || !strings.Contains(body, `"streamId":"log-2"`) || strings.Contains(body, `"streamId":"log-1"`) {
|
||||||
|
t.Fatalf("expected server-wide newest history across streams, body=%s", body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func performCancelledSSE(t *testing.T, router http.Handler, path string) *httptest.ResponseRecorder {
|
||||||
|
t.Helper()
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
cancel()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, path, nil).WithContext(ctx)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
return rec
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLogIngestAPIDuplicateAndErrors(t *testing.T) {
|
func TestLogIngestAPIDuplicateAndErrors(t *testing.T) {
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ func (h *coreHandlers) register(mux *http.ServeMux) {
|
|||||||
mux.HandleFunc("/api/v1/server-instances/{id}/dependencies/check", h.serverDependenciesCheck)
|
mux.HandleFunc("/api/v1/server-instances/{id}/dependencies/check", h.serverDependenciesCheck)
|
||||||
mux.HandleFunc("/api/v1/server-instances/{id}/dependencies/install", h.serverDependenciesInstall)
|
mux.HandleFunc("/api/v1/server-instances/{id}/dependencies/install", h.serverDependenciesInstall)
|
||||||
mux.HandleFunc("/api/v1/server-instances/{id}/dependencies", h.serverDependencies)
|
mux.HandleFunc("/api/v1/server-instances/{id}/dependencies", h.serverDependencies)
|
||||||
|
mux.HandleFunc("/api/v1/server-instances/{id}/logs/events", h.serverLogEvents)
|
||||||
mux.HandleFunc("/api/v1/server-instances/{id}/administrators/candidates", h.serverAdministratorCandidates)
|
mux.HandleFunc("/api/v1/server-instances/{id}/administrators/candidates", h.serverAdministratorCandidates)
|
||||||
mux.HandleFunc("/api/v1/server-instances/{id}/administrators", h.serverAdministrators)
|
mux.HandleFunc("/api/v1/server-instances/{id}/administrators", h.serverAdministrators)
|
||||||
mux.HandleFunc("/api/v1/server-instances/{id}/administrators/{userId}", h.serverAdministratorDetail)
|
mux.HandleFunc("/api/v1/server-instances/{id}/administrators/{userId}", h.serverAdministratorDetail)
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ Lifecycle workflow responses include accepted status, action, bounded server ins
|
|||||||
- `GET /api/v1/server-instances/{id}/dependencies`: returns the target-matched plugin/profile dependency catalog, current safe probe status/evidence, typed plan summaries, and deterministic immutable plan digests.
|
- `GET /api/v1/server-instances/{id}/dependencies`: returns the target-matched plugin/profile dependency catalog, current safe probe status/evidence, typed plan summaries, and deterministic immutable plan digests.
|
||||||
- `POST /api/v1/server-instances/{id}/dependencies/check`: accepts `DependencyJobRequest` and queues a `dependencies.check` run job for a declared logical probe key.
|
- `POST /api/v1/server-instances/{id}/dependencies/check`: accepts `DependencyJobRequest` and queues a `dependencies.check` run job for a declared logical probe key.
|
||||||
- `POST /api/v1/server-instances/{id}/dependencies/install`: accepts `DependencyJobRequest` with an install plan key and the exact catalog `planDigest`; stale/missing digests are denied before job creation.
|
- `POST /api/v1/server-instances/{id}/dependencies/install`: accepts `DependencyJobRequest` with an install plan key and the exact catalog `planDigest`; stale/missing digests are denied before job creation.
|
||||||
Server-scoped raw log routes (`logs/live`, `logs/events`, and `logs/backfill`) are intentionally not registered as product APIs. Internal log ingest and cursor query remain available for run/platform maintenance flows.
|
Server-scoped terminal log streaming (`GET /api/v1/server-instances/{id}/logs/events`) is registered for the server detail terminal drawer and emits platform-accepted log SSE history/live events only. The raw log list/backfill routes (`logs/live` and `logs/backfill`) remain unavailable as product APIs; internal log ingest and cursor query remain available for run/platform maintenance flows.
|
||||||
|
|
||||||
Runtime distribution and client-manager APIs require the current bearer session, server visibility, plugin-declared permissions, complete runtime bindings only for actions that truly depend on external logical bindings, and platform-builder readiness. Run-side lifecycle commands separately require run endpoint capability support and use plugin-declared lifecycle actions without making manual runtime-profile binding a user prerequisite. Responses and audit summaries expose artifact IDs, job IDs, checksums, key generations, fingerprints, status, and redacted `secret://runtime-keys/.../current` refs only. They do not expose raw run keys, client-manager keys, FTP passwords, database DSNs, RCON passwords, host paths, direct sockets, run endpoint private addresses, build workspace paths, or large inline logs.
|
Runtime distribution and client-manager APIs require the current bearer session, server visibility, plugin-declared permissions, complete runtime bindings only for actions that truly depend on external logical bindings, and platform-builder readiness. Run-side lifecycle commands separately require run endpoint capability support and use plugin-declared lifecycle actions without making manual runtime-profile binding a user prerequisite. Responses and audit summaries expose artifact IDs, job IDs, checksums, key generations, fingerprints, status, and redacted `secret://runtime-keys/.../current` refs only. They do not expose raw run keys, client-manager keys, FTP passwords, database DSNs, RCON passwords, host paths, direct sockets, run endpoint private addresses, build workspace paths, or large inline logs.
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ func TestSCUMProjectionOperationAndWorkflowAPIsExposeSafeTypedSurfaces(t *testin
|
|||||||
for _, legacy := range []struct{ method, path string }{
|
for _, legacy := range []struct{ method, path string }{
|
||||||
{http.MethodPost, "/api/v1/server-instances/server-scum-api/rcon/commands"},
|
{http.MethodPost, "/api/v1/server-instances/server-scum-api/rcon/commands"},
|
||||||
{http.MethodGet, "/api/v1/server-instances/server-scum-api/logs/live"},
|
{http.MethodGet, "/api/v1/server-instances/server-scum-api/logs/live"},
|
||||||
{http.MethodGet, "/api/v1/server-instances/server-scum-api/logs/events"},
|
|
||||||
{http.MethodPost, "/api/v1/server-instances/server-scum-api/logs/backfill"},
|
{http.MethodPost, "/api/v1/server-instances/server-scum-api/logs/backfill"},
|
||||||
{http.MethodGet, "/api/v1/server-instances/server-scum-api/files/read-snapshot?key=scum-server-log"},
|
{http.MethodGet, "/api/v1/server-instances/server-scum-api/files/read-snapshot?key=scum-server-log"},
|
||||||
{http.MethodGet, "/api/v1/server-instances/server-scum-api/config"},
|
{http.MethodGet, "/api/v1/server-instances/server-scum-api/config"},
|
||||||
|
|||||||
@@ -59,7 +59,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.
|
- 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.
|
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 raw log routes (`GET /api/v1/server-instances/{id}/logs/live`, `GET .../logs/events`, `POST .../logs/backfill`) and management-terminal/RCON input routes are 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 SSE history/live output. 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.
|
||||||
# Client Manager API projection
|
# 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.
|
`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.
|
||||||
|
|||||||
Reference in New Issue
Block a user