Commit 60cc64d
committed
Resolves #8085 (FR-3230)
## Summary
Migrates the agent node lifecycle controls (start / stop / restart) from the Control Panel into the WebUI. Superadmins can now start, stop, and restart an agent node directly from the **Agent detail drawer**, next to the existing Settings action.
## Test server
A superadmin login is required for both.
- **Normal flow** (start / stop / restart on a healthy watcher): use the `10.82.0.130` test server (manager API on `:8090`).
- **Error handling** (watcher down, agent not loaded with systemctl, etc.): use the `10.82.0.151` test server (manager API on `:8090`).
## Changes
- **`AgentLifeCycleControlModal.tsx`** (new): a single modal that owns the start / stop / restart confirmation and request. It takes `open` + a `lifeCycleType` (`'start' | 'stop' | 'restart'`) and branches the title / success message internally; the body shows a shared warning plus the target agent's name in a surface box (borrowed from `BAIDeleteConfirmModal`). The three watcher calls run through `useTanMutation`.
- **`AgentActionButtons.tsx`**: renders Settings + Restart / Start / Stop trigger buttons in the agent detail drawer. Each watcher button sets a single `lifeCycleType` state that drives the modal (`open={!!lifeCycleType}`). Status-aware disabling: Start disabled when the agent is `ALIVE`, Stop disabled otherwise; Restart always available. Icons/colors follow `SessionActionButtons` — Stop uses `BAITerminateIcon` + `colorError`, Restart uses the `RefreshCw` (lucide) icon.
- **`backend.ai-client` (`client.ts`)**: added `start_watcher_agent` / `stop_watcher_agent` / `restart_watcher_agent` SDK methods (with matching `BackendAIClient` type signatures) so the watcher REST calls are encapsulated and reusable.
- **`AgentResources.tsx`** (drawer crash guard): a stopped agent's `live_stat` no longer contains `node.mem`, and three call sites accessed `parsedLiveStat?.node?.mem.capacity` without optional chaining after `mem`, throwing a `TypeError` that crashed the whole drawer. Added the missing `?.` at all three sites, and the **Utilization** section now renders `-` when `live_stat.node` is empty instead of misleading 0% / NaN values.
- **`AgentDetailDrawerContent.tsx`**: wrapped `<AgentResources>` with `ErrorBoundaryWithNullFallback` so a stat-parsing failure can never take down the entire drawer again.
- **i18n**: added `agent.Watcher*` keys (button labels, success toasts, a single action warning) across all 21 locales.
## API
Uses the manager REST watcher endpoints via the new client SDK methods. These were chosen over the legacy `/api/agents/watcher_agent_*` paths (removed on current backend.ai `main`):
- `POST /resource/watcher/agent/start`
- `POST /resource/watcher/agent/stop`
- `POST /resource/watcher/agent/restart`
Body: `{ agent_id }` (the agent's `row_id`).
## Error handling
The manager proxies these to the agent's watcher daemon, so several non-trivial outcomes are handled explicitly in the modal:
- **200 + `{ result: "ok" }`** → success toast.
- **200 + a body message** (e.g. `"Agent is not loaded with systemctl."`) → surfaced as a warning. HTTP 200 does **not** guarantee success here. The client also hands back a plain string for `text/*` responses, which is handled too.
- **403 (non-superadmin)** → the component is only rendered on superadmin-only pages and the endpoints are superadmin-only on the manager.
- **400 (invalid params) / 403 (watcher token mismatch) / 500 (agent not found, watcher unreachable, timeout, etc.)** → error toast resolved through `getErrorMessage`, logged via `useBAILogger`. A missing `row_id` short-circuits before the request.
## Post-action data refresh
- `AgentLifeCycleControlModalFragment` is now `@refetchable` (`id`, `status`, `status_changed`). After a successful watcher request the modal refetches it with `network-only`, so every component reading the same `AgentNode` record (table row, detail drawer) picks up the new status automatically via the normalized store.
- The refetch runs inside `startTransition` — without it the suspend during refetch collapsed the drawer's Suspense boundary to its skeleton fallback.
- `AgentList`'s manual reload button is replaced with `BAIFetchKeyButton` (`autoUpdateDelay` 15s, matching `StorageProxyList`). Node-level refetch cannot change connection membership/count (e.g. a stopped agent leaving the ALIVE tab), and the watcher actions are async on the backend, so the periodic list refresh is what makes the list itself converge.
## Verification
`bash scripts/verify.sh` → **Relay / Lint / Format / TypeScript all PASS**. (The single terminology warning is a pre-existing key, `error.UserHasNoGroup`, unrelated to this change.)
## Screenshots
_TODO: capture from the `10.82.0.130` test server with a superadmin login._
1 parent 488c36c commit 60cc64d
33 files changed
Lines changed: 880 additions & 218 deletions
File tree
- packages/backend.ai-client/src
- react/src
- __generated__
- components
- AgentNodeItems
- hooks
- resources/i18n
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1281 | 1281 | | |
1282 | 1282 | | |
1283 | 1283 | | |
| 1284 | + | |
| 1285 | + | |
| 1286 | + | |
| 1287 | + | |
| 1288 | + | |
| 1289 | + | |
| 1290 | + | |
| 1291 | + | |
| 1292 | + | |
| 1293 | + | |
| 1294 | + | |
| 1295 | + | |
| 1296 | + | |
| 1297 | + | |
| 1298 | + | |
| 1299 | + | |
| 1300 | + | |
| 1301 | + | |
| 1302 | + | |
| 1303 | + | |
| 1304 | + | |
| 1305 | + | |
| 1306 | + | |
| 1307 | + | |
| 1308 | + | |
| 1309 | + | |
| 1310 | + | |
| 1311 | + | |
| 1312 | + | |
| 1313 | + | |
| 1314 | + | |
| 1315 | + | |
| 1316 | + | |
| 1317 | + | |
| 1318 | + | |
| 1319 | + | |
| 1320 | + | |
| 1321 | + | |
| 1322 | + | |
| 1323 | + | |
| 1324 | + | |
| 1325 | + | |
| 1326 | + | |
| 1327 | + | |
| 1328 | + | |
1284 | 1329 | | |
1285 | 1330 | | |
1286 | 1331 | | |
| |||
Lines changed: 16 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 72 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 135 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
11 | 12 | | |
12 | 13 | | |
13 | 14 | | |
| |||
150 | 151 | | |
151 | 152 | | |
152 | 153 | | |
153 | | - | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
154 | 159 | | |
155 | 160 | | |
156 | 161 | | |
| |||
0 commit comments