* feat(stacks): per-stack action tracking, optimistic status, and bulk status endpoint
Replace global loadingAction mutex with per-stack tracking so users can
fire actions on multiple stacks concurrently. Add optimistic status
updates to fix sidebar showing "--" after stop/start. Add bulk
GET /api/stacks/statuses endpoint using a single docker.listContainers
call instead of N docker compose ps invocations (~21s → ~110ms for 3
stacks). Falls back to per-stack queries for remote nodes on older
versions.
* fix(stacks): remove stale 'start' action check from deploy button label
* feat(resources): add network management with create, inspect, and topology visualization
Add full Docker network CRUD: create networks with custom drivers, subnets,
and IPAM config; inspect network details including connected containers and
IP addresses; interactive topology graph visualization (Pro-gated to
Skipper/Admiral tiers). Includes backend routes, DockerController methods,
unit tests, documentation with screenshots, and updated changelog.
* refactor(resources): address code review findings for network management
- Add batch GET /api/system/networks/topology endpoint to eliminate N+1
HTTP calls from the topology view
- Export DockerNetwork type from ResourcesView, remove duplicate in
NetworkTopologyView
- Wire up isInspectLoading state to Eye button (spinner + disabled)
- Remove unnecessary wrapper div around FilterToggle
- NetworkTopologyView is now self-contained (fetches from batch endpoint,
no longer needs networks prop)
* fix(resources): align pre-existing UI with design system standards
- Replace hardcoded red-500 on image/volume delete buttons with
text-destructive/60 hover:bg-destructive tokens
- Replace shadow-sm with shadow-card-bevel on all cards (Disk Footprint,
Quick Clean, Resource Tabs, Unmanaged Containers)
- Add strokeWidth={1.5} to all action button Trash2 icons
- Type network drivers as union type instead of raw strings (backend
NetworkDriver type + frontend NETWORK_DRIVERS constant)
* fix(resources): add generic type args to useNodesState/useEdgesState
Fixes TS2345 build error in CI where tsc -b (strict mode via
tsconfig.app.json) infers never[] from untyped empty array literals
passed to React Flow hooks.
* fix(resources): replace explicit any in catch blocks with unknown narrowing
ESLint no-explicit-any errors in CI for three catch blocks added by the
network management feature.
Add console.warn/console.error logging to 22 silent catch blocks across
10 files. Errors in cleanup, migrations, SSO, fleet snapshots, shutdown,
and validation are now visible in logs. ENOENT guards added to
file-system catches to distinguish missing files from permission errors.
No control flow changes.
- Dispatch error alerts via NotificationService when scheduled tasks fail,
with info-level recovery notifications when a previously-failing task succeeds
- Per-service restart targeting: scheduled stack restarts can target specific
services instead of restarting the entire stack
- Prune label filter: scheduled prune operations can be scoped to resources
matching a specific Docker label
- CSV export button in the execution history panel for one-click download
- Fix: prune_targets was silently dropped on task creation (missing in INSERT)
- Classify all Docker images, volumes, and networks as managed (Sencho
stack), external (other Compose project), or unused/system via a new
getClassifiedResources() method and GET /api/system/resources endpoint
- Add pruneManagedOnly() + getDiskUsageClassified() to DockerController
- Prune buttons now default to Sencho-managed scope; "All Docker" is
hidden in a ⋮ dropdown with a distinct destructive confirm dialog
- Replace Reclaimable Space donut with interactive Docker Disk Footprint
widget (stacked bar with clickable segments that filter resource tabs)
- Add managed/external filter toggles and classification badges per tab
- GET /api/stats now returns managed + unmanaged container counts; Home
Dashboard Active Containers card subtitle shows "N managed · N external"
- Rename "Ghost Containers" tab/copy to "Unmanaged Containers" throughout
- Destroy Docker stats stream on WS close to prevent orphaned daemon polling
- Guard all ws.send() calls with readyState === OPEN check
- Add .catch() to unawaited streamStats/execContainer calls to prevent
unhandled rejections crashing the process (Node >= 15)
- Close per-connection WebSocket.Server instances after handleUpgrade to
prevent listener accumulation over many connections
- Invert auth token precedence to bearerToken || cookieToken in both
authMiddleware and the WS upgrade handler so node-to-node Bearer tokens
are never shadowed by a stale browser cookie
- Narrow proxyRes type in remoteNodeProxy error handler before calling
.status() to avoid throwing on raw Socket (WS/TCP-level proxy errors)
Add Node modal — type selector & state reset:
- Restored a Local/Remote <Select> dropdown in renderFormFields so users can
explicitly choose the node type instead of it defaulting silently to 'remote'.
- Switching type clears api_url and api_token so no stale remote credentials
carry over if a user switches from Remote to Local mid-form.
- Replaced the static "Add Remote Node" title with a dynamic one that reflects
the currently selected type ("Add Local Node" / "Add Remote Node").
- onOpenChange now resets formData to defaultFormData whenever the dialog
opens, preventing stale values from a previous session leaking in.
Remote connection details — real metrics:
- testRemoteConnection previously returned hard-coded '-' for containers,
images, and cpus after a successful auth/check ping.
- Now fires three parallel requests (Promise.allSettled) after auth passes:
/api/stats → containers total + running count
/api/system/stats → cpu.cores
/api/system/images → image list length
- Each field falls back to '-' gracefully if an endpoint is unavailable,
so a slow or older remote instance never breaks the connection test.
DEP0060 util._extend suppression:
- http-proxy@1.18.1 calls util._extend when createProxyServer() is first
invoked at runtime (NOT at import time). A process.emitWarning override
placed before the proxy instantiations intercepts only DEP0060 without
suppressing any other warnings. No package version changes needed.
Also includes linter/formatter normalisation across multiple files.