- F1: cover the hook's riskiest path — a following stream that ends with an
unwritten tail fragment then resumes (tail:0 + nano-since), asserting the
fragment is dropped, resume params are correct, and the boundary line is
deduped to one; plus MAX_LOG_LINES head-trim and buffer reset on
resourceId/lineCount change.
- F2: clear the error banner on a SUCCESSFUL reconnect (via a new onOpen signal
on StreamLogsFn), not only when new lines arrive — an idle-but-healthy
reconnect no longer leaves a stuck 'unable to stream' banner.
- F4: update the stale comment in the React logs view registration (the React
logs migration is now complete).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the legacy AngularJS <log-viewer> on the container logs page with a
modern React log viewer, reusing the existing streaming (#6) and formatting/
coloring pipeline. Features: line-number gutter, zerolog level + key=value
coloring (from the existing formatter spans), from/to datetime range, Lines
limit, Line numbers / Timestamp / Wrap lines toggles, Auto refresh (live tail
on/off), Search + 'Filter search results', Copy, Download logs, and fullscreen.
The viewer is source-agnostic (StreamLogsFn), so service/task logs can adopt it
later; this PR wires container logs only. containerLogsController.js no longer
opens its own live stream (React owns fetching now), preventing a double stream.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When a container is opened from a stack, the detail tab kept the stack
trail (PR #7) but the attribute sub-tabs (Logs, Stats, Inspect, Console,
Attach) dropped it: those tabs were registered only under the global
docker.containers.container.* tree, so navigating to one left the stack
state (and its inherited params) behind, and each sub-view set a hardcoded
"Containers > ..." breadcrumb.
- Register stack-scoped child states docker.stacks.stack.container.{attach,
exec,inspect,logs,stats} mirroring the global ones, so the inherited stack
params survive and the trail can be kept.
- Centralize the breadcrumb logic in containerBreadcrumbs.ts (moved out of
ItemView, which re-exports it) and add isStackContainerState +
getContainerSubTabBreadcrumbs + buildStackContainerLinkParams.
- ActionLinksRow links sub-tabs into the stack tree (with stack+container
params) when opened from a stack, else the global states unchanged.
- InspectView + the logs/stats/console controllers render the stack-aware
trail; set up-front (no name) so it survives the load window and errors.
Covers regular/external/orphaned stacks and the non-stack fallback,
matching the existing ItemView breadcrumb behavior. New unit tests in
containerBreadcrumbs.test.ts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Maintainer pre-merge review follow-up:
F1: dedup reconnect redeliveries by EXACT boundary-line content, not just
timestamp <= resume — a new line that merely shares the boundary nanosecond
with a redelivered duplicate is no longer dropped (skipBoundaryContents +
pendingBoundary). Test proves line B survives while a real dup is dropped.
F2: flush the buffered partial line on intentional pause (not reconnect) and
strip those cosmetic lines on resume so since re-delivers the full line with
no stale-partial twin; resume point is not advanced past the partial.
F3: unify the since param to <unix>.<nanos> for initial and reconnect.
F4: fall back to 100 lines when the Lines field is cleared (avoid tail=all).
F5: memoize the API-version pin per session; warn on frame desync.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Maintainer pre-merge review follow-up:
F1: test the orphaned-stack breadcrumb branch (orphaned=true, no regular) —
href carries stackId/orphaned, not external.
F2: extract STACK_CONTAINER_STATE_NAME so code + test share one literal.
F4: type buildStackLinkParams' return as StackLinkParams (documents the real
shape; external stays boolean, serialized by ui-router — no runtime change).
F3 (legacy ?id= deep links) answered wontfix in the PR thread.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a test case driving the external-stack branch (external='true', no DB
stackId) and assert the back-link carries external=true/type and omits
stackId/regular. stackId/regular are set in the route params so the negative
assertions actually catch a fall-through-to-regular regression.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Opening a container from a stack's Containers table showed
"Home > Containers > <container>" instead of keeping the stack trail,
so the user could not navigate back to the stack.
Two root causes are addressed:
1. Route param collision: docker.stacks.stack used the query param `id`
for the numeric stack DB id, while its child docker.stacks.stack.container
uses the path param `id` for the container id. Navigating into a container
overwrote the stack id. The stack id param is renamed `id` -> `stackId`
everywhere it is read or written (route url, stacks datatable link,
create-stack redirect, gitops workflow card link, stack ItemView reader).
2. Hardcoded breadcrumbs: the container details ItemView always rendered the
global "Containers" crumb. Breadcrumbs are now state-aware: when reached
via docker.stacks.stack.container the stack trail
(Stacks > <stack> > <container>) is rebuilt from the inherited stack params,
honoring external/orphaned stacks.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Container log live-stream review fixes (frontend only):
- F1/F2: demux Docker's multiplexed (non-TTY) stream at the BYTE level by
frame length, decoding only payloads. Previously the stream was text-decoded
whole and cut on '\n' before stripping 8-byte headers, which desynced when a
length low-byte was 0x0a or a header byte was >= 0x80. streamContainerLogs
now hands the processor raw Uint8Array chunks; createLogStreamProcessor is
rewritten to parse frames, concatenate payloads, split lines on 0x0a, and
UTF-8-decode complete lines. formatLogs is called without stripHeaders so
headers are not stripped twice. Added explicit byte-frame tests.
- F3: request timestamps=1 internally and resume reconnects from the parsed
RFC3339 timestamp of the last line (not client wall-clock); strip the prefix
before display when the user's timestamps toggle is off; dedup the inclusive
`since` boundary lines on reconnect.
- F4: run the fetch stream URL through dockerMaxAPIVersionInterceptor so it
matches the axios getContainerLogs version pinning.
- F5: notify on stream error once per reconnect loop, not every 3s retry.
- F6: resuming Live no longer wipes the buffer (startStream(false)) and
continues from `since`.
- F7: service/task logs still poll; documented the re-render limitation
(out of scope: issue #2 is container logs).
- F8: flush the trailing partial line on the error path too (parity with onEnd).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the 3s $interval polling of container logs with a live HTTP
stream, and stop re-writing already-rendered lines (fixes selection bug).
- streamContainerLogs (containers.service.ts): fetch + ReadableStream
reader with follow=1, same-origin credentials:'include' (httpOnly JWT
cookie; CSRF only guards mutations), agent-target / manager-operation
headers replicated for Agent/Edge, AbortSignal-driven lifetime.
- containerLogsController: stream instead of poll; append parsed lines
into the buffer (push, never replace), cap at 5000 lines trimming from
the head; AbortController on pause/destroy/param-change; reconnect with
3s backoff resuming from `since` (dropping tail) on stream end/error;
Live toggle pauses/resumes the stream; tail/since/timestamps changes
restart the stream.
- log-viewer: `track by log.id` (was $index), filtering moved out of the
template into the controller (applyFilter via $watchCollection), removed
inert force-glue, decoupled auto-scroll from log collection, relabelled
"Auto-refresh logs" -> "Live logs", clearer empty states.
Backend unchanged (logs already stream transparently through the Docker
proxy). Shared task/service log views keep working via the new id'd lines.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove always-false isBE branches, BE-only teaser controls and the
now-dead imports across the Docker, Kubernetes and Edge-stack React
views. CE behaviour is preserved; only the Business Edition branches,
teasers and BE-only (non-functional) controls are removed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>