@flame0510/project-aether 1.1.14 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/api/agents/route.ts +16 -12
- package/app/api/credentials/[id]/sync/route.ts +3 -3
- package/app/api/credentials/detect/route.ts +126 -176
- package/app/api/credentials/route.ts +3 -0
- package/app/components/Skeleton.tsx +132 -0
- package/app/credentials/PageClient.tsx +460 -139
- package/app/credentials/loading.tsx +19 -5
- package/docs/ARCHITECTURE.md +78 -31
- package/docs/FRONTEND-ARCHITECTURE.md +13 -5
- package/docs/REV4A.md +23 -14
- package/docs/dev/API-REFERENCE.md +356 -184
- package/docs/dev/DATABASE.md +8 -3
- package/docs/dev/GATEWAY.md +9 -7
- package/docs/dev/PROVIDERS.md +22 -35
- package/docs/rag/GLOSSARY.md +8 -8
- package/docs/rag/REV4A-OVERVIEW.md +7 -10
- package/docs/rag/WHAT-I-CAN-ANSWER.md +6 -6
- package/lib/credentials/delivery.ts +212 -119
- package/lib/credentials/detect.ts +229 -97
- package/lib/credentials/providers.ts +38 -7
- package/lib/credentials/vault.ts +78 -13
- package/lib/docker-exec.ts +50 -14
- package/package.json +1 -1
|
@@ -1,10 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Page, PageHeader } from '../components/ui';
|
|
2
|
+
import { CredentialCardsSkeleton } from '../components/Skeleton';
|
|
2
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Route-level loading UI, shown while the page chunk streams in.
|
|
6
|
+
*
|
|
7
|
+
* It renders the same chrome and the same skeleton as PageClient's own loading
|
|
8
|
+
* state, so the two hand over without a visible change. The previous version
|
|
9
|
+
* used a generic five-row card list at maxWidth 1080, while the real page is a
|
|
10
|
+
* card grid inside the default 1280 — the skeleton and the page it stood for
|
|
11
|
+
* did not line up.
|
|
12
|
+
*/
|
|
3
13
|
const PageSkeleton = () => (
|
|
4
|
-
<
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
|
|
14
|
+
<Page>
|
|
15
|
+
<PageHeader
|
|
16
|
+
eyebrow=""
|
|
17
|
+
title="Credentials"
|
|
18
|
+
description="Store third-party credentials once, sync to all agent containers."
|
|
19
|
+
/>
|
|
20
|
+
<CredentialCardsSkeleton />
|
|
21
|
+
</Page>
|
|
8
22
|
);
|
|
9
23
|
|
|
10
24
|
export default PageSkeleton;
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Rev4a Architecture — Design & Vision
|
|
2
2
|
|
|
3
3
|
> **Status:** Active — `main` branch
|
|
4
|
-
> **Last updated:** 2026-
|
|
4
|
+
> **Last updated:** 2026-08-28
|
|
5
5
|
> **Goal:** Transform Rev4a from a monitoring dashboard into a central orchestrator for a distributed multi-container agency.
|
|
6
6
|
|
|
7
7
|
---
|
|
@@ -64,15 +64,15 @@
|
|
|
64
64
|
|
|
65
65
|
---
|
|
66
66
|
|
|
67
|
-
## 2. Current Architecture (as of 2026-
|
|
67
|
+
## 2. Current Architecture (as of 2026-08-28)
|
|
68
68
|
|
|
69
69
|
### What's already been implemented
|
|
70
70
|
|
|
71
71
|
**Container separation:** `openclaw-atlas` already runs as a standalone container with `AGENT_ID=atlas`, discovered dynamically by Rev4a. This proves the container-per-agent model works.
|
|
72
72
|
|
|
73
|
-
**
|
|
73
|
+
**Credentials vault:** The `/credentials` page stores third-party service tokens in `credentials.db` and installs them into containers on explicit sync. Provider API keys live separately in `provider-keys.json`, managed from the Gateway UI.
|
|
74
74
|
|
|
75
|
-
**Container management UI:** The `/containers` page lists all running Docker containers with resource usage
|
|
75
|
+
**Container management UI:** The `/containers` page lists all running Docker containers with resource usage and quick links, and opens a web terminal into any of them. Fully functional.
|
|
76
76
|
|
|
77
77
|
**Workspace API:** Lazy-loaded file tree explorer with real-time reads (no caching), supports both host and container workspaces via `docker exec`.
|
|
78
78
|
|
|
@@ -80,8 +80,8 @@
|
|
|
80
80
|
|
|
81
81
|
| Feature | Status | Notes |
|
|
82
82
|
|---|---|---|
|
|
83
|
-
|
|
|
84
|
-
| Container page | 🟢 Functional | Lists all containers,
|
|
83
|
+
| Credentials (UI + API) | 🟢 Implemented | SQLite, no encryption yet |
|
|
84
|
+
| Container page | 🟢 Functional | Lists all containers, resources, web terminal |
|
|
85
85
|
| Agent creation wizard | 🟢 Implemented | One-click create with model/template selection |
|
|
86
86
|
| Provider proxy | 🟢 Implemented | Agents route through Rev4a provider gateway |
|
|
87
87
|
| Shared volumes | 🟢 Implemented | Skills + repos mounted on all agents |
|
|
@@ -103,7 +103,7 @@ The central container, running the Next.js dashboard + orchestration API.
|
|
|
103
103
|
- Centralized credential management
|
|
104
104
|
- Docker socket access for container management
|
|
105
105
|
- SQLite DB for cross-container event monitoring
|
|
106
|
-
- **
|
|
106
|
+
- **First-run wizard** — setup flow at `/wizard`; completion is recorded in `<data dir>/data/wizard.json` and read through `GET /api/wizard/status`
|
|
107
107
|
|
|
108
108
|
**Volume mounts (target):**
|
|
109
109
|
```
|
|
@@ -120,6 +120,49 @@ The central container, running the Next.js dashboard + orchestration API.
|
|
|
120
120
|
- Immutable by agents — enforced by the `:ro` mount
|
|
121
121
|
- See `lib/agent-setup.ts` for the centralized volume + config guarantee logic
|
|
122
122
|
|
|
123
|
+
**Running commands inside containers** (`lib/docker-exec.ts`):
|
|
124
|
+
|
|
125
|
+
This module is how a route reaches an agent container. The migration to it is
|
|
126
|
+
not finished — routes predating it still shell out directly — so treat these as
|
|
127
|
+
the rules for anything you touch, not as a description of the whole tree:
|
|
128
|
+
|
|
129
|
+
- **Never `execSync`/`execFileSync` in a request path.** They block Node's single
|
|
130
|
+
thread: while one runs, no other request is served and no agent's stream
|
|
131
|
+
advances. A `docker exec` costs ~90 ms warm and the OpenClaw CLI costs
|
|
132
|
+
seconds, so a route that walks the fleet freezes the event loop for the sum of
|
|
133
|
+
all of them. Use `dockerExec` / `dockerExecShell` and their `…NoFail` variants.
|
|
134
|
+
- **`mapWithConcurrency` is fail-fast, like `Promise.all`.** If the mapped
|
|
135
|
+
function rejects for one item, the whole call rejects and results already
|
|
136
|
+
computed for other containers are discarded. Fanning out over a fleet requires
|
|
137
|
+
the `…NoFail` variants, or an explicit per-item try/catch, so one unreachable
|
|
138
|
+
container cannot blank an entire page. Callers: `app/api/skills/route.js`,
|
|
139
|
+
`app/api/gateway/route.ts`, `app/api/agents/channels-summary/route.ts`,
|
|
140
|
+
`lib/openclaw-cron.ts`, `app/api/credentials/detect/route.ts`,
|
|
141
|
+
`lib/credentials/delivery.ts`.
|
|
142
|
+
- **Never interpolate a secret into a command string.** Pass it through the `env`
|
|
143
|
+
option and reference it as `"$KEY"`; command substitution inside an
|
|
144
|
+
interpolated value would otherwise execute on the host. Only the *name* reaches
|
|
145
|
+
argv, as `docker exec -e KEY` — docker forwards the value from its own
|
|
146
|
+
environment, so it appears neither in the host process table nor in any
|
|
147
|
+
rejection built from that argv.
|
|
148
|
+
- **A timeout does not reject.** `docker exec` exits 0 on the SIGTERM Node sends
|
|
149
|
+
when the deadline passes, so a command killed halfway *resolves* with whatever
|
|
150
|
+
it had already printed. A `try/catch` therefore cannot tell a truncated read
|
|
151
|
+
from a complete one. Where that distinction matters, make the script prove it
|
|
152
|
+
finished — `detect.ts` emits a `probe:complete` sentinel before its slow calls,
|
|
153
|
+
`containerRead` prints a terminator after the file — and treat its absence as
|
|
154
|
+
failure. Anything that writes back what it read must refuse to write when the
|
|
155
|
+
proof is missing.
|
|
156
|
+
- **A rejection carries `stderr`, never the command.** Node's own message is
|
|
157
|
+
`Command failed: <argv…>`, which reproduces whatever was interpolated into the
|
|
158
|
+
script. `dockerExec` replaces it with `docker exec <container>: <cause> —
|
|
159
|
+
<stderr>`, because these messages travel: `syncProfileToAgents` puts one into
|
|
160
|
+
`SyncResult.error` and the sync route returns it to the browser.
|
|
161
|
+
|
|
162
|
+
`dockerExecWithInput` uses `spawn` rather than `execFile` because
|
|
163
|
+
`promisify(exec)` silently ignores an `input` option: the process starts, stdin
|
|
164
|
+
is never written, and the command hangs with no error to point at.
|
|
165
|
+
|
|
123
166
|
### 3.2 Agent Container Template (`openclaw-agent-base`)
|
|
124
167
|
|
|
125
168
|
Docker image for every agent container.
|
|
@@ -196,30 +239,34 @@ detailed documentation.
|
|
|
196
239
|
|
|
197
240
|
## 4. Credential Management (Vault)
|
|
198
241
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
242
|
+
Two separate stores:
|
|
243
|
+
|
|
244
|
+
- **Provider API keys** — `<data dir>/data/provider-keys.json`, used by the Rev4a
|
|
245
|
+
provider proxy.
|
|
246
|
+
- **Third-party service credentials** — `<data dir>/data/credentials.db` (SQLite),
|
|
247
|
+
managed from the Credentials page. Five providers: `github-pat`, `trello`,
|
|
248
|
+
`vercel`, `supabase`, `notion` (`lib/credentials/providers.ts`).
|
|
249
|
+
|
|
250
|
+
**Secrets are stored unencrypted.** `credential_secrets.payload` holds raw JSON.
|
|
251
|
+
Anything able to read that file, or to reach `POST /api/credentials/[id]/reveal`
|
|
252
|
+
with a valid session, obtains them in the clear. Encryption (AES-256-GCM, or an
|
|
253
|
+
external vault) is not implemented.
|
|
254
|
+
|
|
255
|
+
**Delivery is explicit, not automatic.** Creating an agent installs nothing: a
|
|
256
|
+
credential reaches a container only when the user syncs it
|
|
257
|
+
(`POST /api/credentials/[id]/sync`), and leaves only on de-sync
|
|
258
|
+
(`DELETE /api/credentials/[id]/sync`). There is no per-agent permission model —
|
|
259
|
+
any stored credential can be synced to any container. Delivery writes each CLI's
|
|
260
|
+
own config inside the container (`lib/credentials/delivery.ts`); OpenClaw's
|
|
261
|
+
`secrets` subsystem covers only OpenClaw's own configuration credentials and
|
|
262
|
+
offers nothing for third-party CLIs.
|
|
263
|
+
|
|
264
|
+
`GET /api/credentials/detect` reports which credentials are actually installed,
|
|
265
|
+
by SHA-256 hashing the token found in each container and matching it against the
|
|
266
|
+
stored profiles.
|
|
221
267
|
|
|
222
|
-
**
|
|
268
|
+
**Planned:** encryption at rest, and a per-agent permission model so a credential
|
|
269
|
+
can be restricted to a subset of agents.
|
|
223
270
|
|
|
224
271
|
---
|
|
225
272
|
|
|
@@ -568,7 +615,7 @@ for the full rationale.
|
|
|
568
615
|
| Database | SQLite (WAL mode) | Current `events.db`, may need to scale |
|
|
569
616
|
| Memory | Per-agent SQLite + central index | New |
|
|
570
617
|
| Config | Generated YAML/JSON | New |
|
|
571
|
-
|
|
|
618
|
+
| Credential stores | SQLite + JSON | credentials.db for service tokens, provider-keys.json for provider API keys. Neither is encrypted |
|
|
572
619
|
| Reverse proxy | Traefik | Already in use |
|
|
573
620
|
| Monitoring | Rev4a daemon (extended) | Evolution of current daemon |
|
|
574
621
|
| Version control | Git + GitHub | `github.com/Flame0510/rev4a.git` |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Rev4a Frontend Architecture
|
|
2
2
|
|
|
3
|
-
> **Last updated:** 2026-
|
|
3
|
+
> **Last updated:** 2026-08-28
|
|
4
4
|
|
|
5
5
|
## Layering
|
|
6
6
|
|
|
@@ -41,11 +41,12 @@ All shared UI primitives live in `app/components/ui/` and are exported from `app
|
|
|
41
41
|
| `Page` / `PageHeader` | `Page.tsx` | Full-page layout shell. |
|
|
42
42
|
| `Icons` | `Icons.tsx` | SVG icons (`EyeIcon`, `EyeOffIcon`), 16/20px shared. |
|
|
43
43
|
| `PasswordInput` | `PasswordInput.tsx` | Password input with inline show/hide toggle (`<button type="button">` with `aria-label`). |
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
46
|
-
|
|
|
44
|
+
| `WizardPageClient` + step components | `app/wizard/PageClient.tsx` | Multi-step first-run wizard plus its frame shell, with mobile-first CSS. |
|
|
45
|
+
| `useWizard` | `app/wizard/useWizard.ts` | Shared hook: wizard state, step transitions, provider save, restart. Receives server-side initial state to avoid a loading flash. |
|
|
46
|
+
| Wizard icons | `app/wizard/icons.tsx` | Shared SVG icons (flyweight pattern): `ArrowRightIcon`, `CheckIcon`, `CheckCircleIcon`, `DockerIcon`, `GatewayIcon`, `AgentIcon`, `TemplateIcon`, `LinkIcon`, `ConfigIcon`, `InformationIcon`. |
|
|
47
47
|
| `tokens` | `tokens.ts` | TypeScript types for `Tone` and related token values. |
|
|
48
48
|
| `Badge` | `Badge.tsx` | Inline status tag with tone variants (success, danger, warning, neutral). Used for channel chips, pairing labels, error/success messages. |
|
|
49
|
+
| `Skeleton` + shape helpers | `app/components/Skeleton.tsx` | Shimmer placeholders. `Skeleton` is the primitive; the rest mirror one specific layout each: `TreeSkeleton`, `CodeSkeleton`, `CronJobsSkeleton`, `CronRunsSkeleton`, `CredentialCardsSkeleton`, `AgentSyncRowsSkeleton`, `SkeletonLines`, `SkeletonMetric`, `CardRowSkeleton`. A shape helper must match the real markup it stands in for — same row structure, same paddings, same element count where the count is known — so nothing reflows when data replaces it. |
|
|
49
50
|
|
|
50
51
|
### Rules
|
|
51
52
|
|
|
@@ -53,8 +54,12 @@ All shared UI primitives live in `app/components/ui/` and are exported from `app
|
|
|
53
54
|
2. **No inline styles on interactive elements.** Buttons, inputs, selects use the `app/components/ui/*` component with component props. Only layout/wrapping containers use inline style.
|
|
54
55
|
3. **No Italian in code, labels, or comments.** UI strings, error messages, aria-labels — all English.
|
|
55
56
|
4. **Clickable/custom interactive elements must be `<button type="button">`.** Not `<span onClick>`, not `<div onClick>`. Always include `aria-label` for icon-only buttons.
|
|
57
|
+
4a. **A control gated on another field stays visible and disabled** — never conditionally unmounted. Rendering it only once its dependency is filled makes it appear out of nowhere, and any hint that references it is pointing at something not on screen. Show it disabled, with the reason beside it, so the shape of the form is stable from the first render.
|
|
56
58
|
5. **Modals inside forms** — close button has `type="button"` to prevent accidental form submission.
|
|
57
|
-
6. **Loading buttons** — set `loading={true}` on `Button`, do not render separate loaders next to the button. The spinner is built-in.
|
|
59
|
+
6. **Loading buttons** — set `loading={true}` on `Button`, do not render separate loaders next to the button. The spinner is built-in. The label must also change for the duration (`Saving…`, `Deleting…`, `Syncing…`).
|
|
60
|
+
6a. **One in-flight mutation, keyed.** Hold a single `busy` string identifying the running action (`delete:<id>`, `sync:<id>:<container>`), plus a `useRef` mirror as the authoritative guard — state is read from the render that produced the click, so two fast clicks would otherwise both pass. `loading` compares against the exact key; `disabled` is set for any busy value, so a control that would be refused looks refused. Never key a mutation on one dimension when the same control is rendered per row of another: a key holding only a container name puts every profile's button for that container into a spinner.
|
|
61
|
+
6b. **Do not hold the lock across a slow refresh.** Release it when the mutation itself completes and refresh derived state afterwards, unawaited. Show the pending state by fading the stale values only — never the buttons, since `opacity` on an ancestor cannot be undone by a child and makes live controls read as disabled.
|
|
62
|
+
6c. **An optimistic update moves every field the render derives from, together.** A row that decides its state by reading two fields against each other flips to a third, wrong state if the update touches only one of them — and holds it until the slow refresh lands. Update the whole set the derivation reads, or none of it.
|
|
58
63
|
7. **New UI component** — add it to `app/components/ui/`, export from `index.ts`, document it here. If it's specific to one page, keep it page-local unless another page needs it.
|
|
59
64
|
|
|
60
65
|
## GoF pattern mapping
|
|
@@ -108,6 +113,9 @@ For interactive pages that can change view/file/tab quickly:
|
|
|
108
113
|
2. Guard against stale updates after `await` boundaries.
|
|
109
114
|
3. On navigation/context switch, kill in-flight requests before starting new ones.
|
|
110
115
|
4. Mobile layout changes must not wait on network completion.
|
|
116
|
+
5. After a mutation, refresh the cheap endpoint and await it; fire the expensive one without awaiting. A page that re-reads everything makes the user wait on work their change did not need.
|
|
117
|
+
6. A refresh helper invoked from a mutation must never reject. The mutation has already succeeded by then, so a failed re-read surfacing as the caller's error reports a completed action as failed.
|
|
118
|
+
7. Check `res.ok` before reading the body. An error response is still valid JSON, so `json.items ?? []` silently turns a 401 into an empty result presented as fact.
|
|
111
119
|
|
|
112
120
|
## Migration rule
|
|
113
121
|
|
package/docs/REV4A.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Rev4a — VPS Dashboard
|
|
2
2
|
|
|
3
|
-
> **Last updated:** 2026-
|
|
3
|
+
> **Last updated:** 2026-09-05
|
|
4
4
|
|
|
5
5
|
A Next.js 16 dashboard for monitoring and managing the OpenClaw ecosystem.
|
|
6
6
|
|
|
@@ -107,22 +107,21 @@ When environment variables are saved via the Config page (Save & Restart):
|
|
|
107
107
|
|
|
108
108
|
| Route | Description |
|
|
109
109
|
|---|---|
|
|
110
|
-
|
|
|
110
|
+
| `/` | System overview — containers, resources, quick links |
|
|
111
111
|
| `/agents` | Running agents (Docker containers with `AGENT_ID`), gateway token management, agent creation wizard, channel manager (Telegram pairing) |
|
|
112
112
|
| `/containers` | All Docker containers on the host |
|
|
113
|
-
| `/
|
|
113
|
+
| `/wizard` | First-run setup wizard (Welcome → Providers → Agent → Ready) |
|
|
114
114
|
| `/workspace` | File explorer with tree view + editor — VPS host or container workspaces |
|
|
115
115
|
| `/lineage` | Agent lineage / orchestration tree |
|
|
116
116
|
| `/memory` | Agent memory browser |
|
|
117
117
|
| `/crons` | Scheduled cron jobs — auto-discovered from the host gateway and every running agent container (Docker containers with `AGENT_ID` label), on/off toggle, run history per job |
|
|
118
|
-
| `/credentials` | Third-party tool credentials (GitHub, Trello, Vercel, Supabase) —
|
|
118
|
+
| `/credentials` | Third-party tool credentials (GitHub, Trello, Vercel, Supabase, Notion) — SQLite vault, synced into agent containers as CLI config or, for the REST-only providers, as a config file the agent reads |
|
|
119
|
+
| `/setup` | First-run password setup — sets `REV4A_PASSWORD` and the JWT secret before the dashboard is reachable |
|
|
119
120
|
| `/plugins` | Plugin manager |
|
|
120
|
-
| `/plugins-skills` | Plugin skills |
|
|
121
121
|
| `/skills` | Skill registry — browse/edit all skills (shared, per-agent workspace, bundled). Promote agent skills to shared with one click. |
|
|
122
122
|
| `/tools` | Tool configuration |
|
|
123
123
|
| `/gateway` | LLM provider sync + agent model configuration — see [GATEWAY.md](dev/GATEWAY.md) |
|
|
124
124
|
| `/config` | Environment management and server restart |
|
|
125
|
-
| `/vault` | Credential storage (per-agent permissions) — see [PROVIDERS.md](dev/PROVIDERS.md) |
|
|
126
125
|
| `/login` | Authentication page |
|
|
127
126
|
|
|
128
127
|
---
|
|
@@ -261,19 +260,29 @@ Container workspaces are discovered dynamically — any Docker container with an
|
|
|
261
260
|
|
|
262
261
|
## Features (current)
|
|
263
262
|
|
|
264
|
-
###
|
|
265
|
-
Store
|
|
266
|
-
|
|
267
|
-
- **SQLite storage:** `credentials.db` in the data directory
|
|
268
|
-
|
|
269
|
-
- **
|
|
270
|
-
|
|
263
|
+
### Credentials (`/credentials`)
|
|
264
|
+
Store third-party service credentials and install them into agent containers.
|
|
265
|
+
|
|
266
|
+
- **SQLite storage:** `credentials.db` in the data directory. Secrets are held
|
|
267
|
+
**unencrypted**; anything that can read the file has them in the clear.
|
|
268
|
+
- **Provider keys** are a separate store — `provider-keys.json`, managed from the
|
|
269
|
+
Gateway UI.
|
|
270
|
+
- **Five providers.** GitHub, Vercel and Supabase are handed to their CLI —
|
|
271
|
+
`gh auth login --with-token`, `~/.local/share/com.vercel.cli/auth.json`,
|
|
272
|
+
`supabase login --token`. Trello and Notion have no CLI in the loop: their
|
|
273
|
+
credentials are written to `~/.config/trello/config.json` and
|
|
274
|
+
`~/.config/notion/config.json`, and TOOLS.md tells the agent how to call each
|
|
275
|
+
REST API with them.
|
|
276
|
+
- **Delivery is explicit.** A credential reaches a container only on sync, and
|
|
277
|
+
leaves only on de-sync. There is no per-agent permission model: any stored
|
|
278
|
+
credential can be synced to any container.
|
|
279
|
+
- **Live detection** shows which credentials are actually installed, matching
|
|
280
|
+
SHA-256 hashes of the tokens found in each container against the stored ones.
|
|
271
281
|
|
|
272
282
|
### Container management (`/containers`)
|
|
273
283
|
View all running Docker containers with:
|
|
274
284
|
- Name, image, status, ports, uptime
|
|
275
285
|
- Resource usage (CPU, memory)
|
|
276
|
-
- Per-container logs
|
|
277
286
|
- Quick links to agent control UIs
|
|
278
287
|
|
|
279
288
|
### Agent management (`/agents`)
|