archy/neode-ui/src/api/container-client.ts

161 lines
3.4 KiB
TypeScript
Raw Normal View History

2026-01-24 22:01:51 +00:00
// Container management API client
// Extends RPC client with container-specific methods
import { rpcClient } from './rpc-client'
export interface ContainerStatus {
id: string
name: string
fix(ui): single-button lifecycle control with transitional labels The app card and details view previously used a pair of Start/Stop buttons whose labels were driven off isAppLoading(), a client-side "I just clicked the button" flag. When the backend's graceful stop took longer than the RPC round-trip (up to 600s on bitcoin-core), the flag cleared while the container was still shutting down, the UI flipped back to "Running" as soon as the next 10s scan saw the still-alive container, and the user had no indication the stop was still in flight. Now that the backend flips PackageState to Stopping / Starting / Restarting / Installing / Updating / Removing for the duration of each lifecycle operation and the scan loop preserves those states, the UI can drive its label off the container state itself. A single full-width primary button replaces the Start/Stop pair. Its label, color, and disabled state come from getAppVisualState(), which collapses resting states (exited/created/paused/installed) into "stopped" and passes transitional states through untouched. Changes: - container-client.ts: widen ContainerStatus.state union to include the six transitional variants plus "installed". Add restartContainer() calling the new container-restart RPC. - stores/container.ts: add getAppVisualState() computed and the restartContainer() action. - ContainerApps.vue: single primary button (Start / Stop / Starting / Stopping / Restarting etc.) plus a separate circular Restart button visible only when running. Critically, handleStartApp and handleStopApp now route through store.startContainer and stopContainer (which call container-start / container-stop, the async RPCs) instead of the legacy synchronous bundled-app-start / bundled-app-stop path. Transitional-state polling widened from just "created" to the full set of transitional variants. - ContainerAppDetails.vue: same single-button pattern, Restart button now calls container-restart instead of the old stop-sleep-start sequence, added 2s polling interval for transitional states. - components/ContainerStatus.vue: widen state prop to match the shared union, render transitional labels with a trailing ellipsis and a yellow dot. No new tests — this is presentation logic. Manual verification on .228 will confirm the end-to-end async path: click Stop on LND, button becomes "Stopping" in under a second, stays that way for roughly 5 minutes, then flips to "Start" with a grey dot. The UI must never revert to "Running" mid-stop.
2026-04-23 05:20:15 -04:00
state:
| 'created'
| 'running'
| 'stopped'
| 'exited'
| 'paused'
| 'unknown'
| 'stopping'
| 'starting'
| 'restarting'
| 'installing'
| 'updating'
| 'removing'
| 'installed'
2026-01-24 22:01:51 +00:00
image: string
created: string
ports: string[]
lan_address?: string // Launch URL for the app's UI
2026-01-24 22:01:51 +00:00
}
export interface ContainerAppInfo {
id: string
name: string
version: string
status: ContainerStatus
health: 'healthy' | 'unhealthy' | 'unknown' | 'starting'
}
export interface BundledAppConfig {
id: string
name: string
image: string
ports: { host: number; container: number }[]
volumes: { host: string; container: string }[]
}
2026-01-24 22:01:51 +00:00
export const containerClient = {
/**
* Install a container app from a manifest file
*/
async installApp(manifestPath: string): Promise<string> {
return rpcClient.call<string>({
method: 'container-install',
params: { manifest_path: manifestPath },
})
},
/**
* Start a container
*/
async startContainer(appId: string): Promise<void> {
return rpcClient.call<void>({
method: 'container-start',
params: { app_id: appId },
})
},
/**
* Stop a container
*/
async stopContainer(appId: string): Promise<void> {
return rpcClient.call<void>({
method: 'container-stop',
params: { app_id: appId },
})
},
fix(ui): single-button lifecycle control with transitional labels The app card and details view previously used a pair of Start/Stop buttons whose labels were driven off isAppLoading(), a client-side "I just clicked the button" flag. When the backend's graceful stop took longer than the RPC round-trip (up to 600s on bitcoin-core), the flag cleared while the container was still shutting down, the UI flipped back to "Running" as soon as the next 10s scan saw the still-alive container, and the user had no indication the stop was still in flight. Now that the backend flips PackageState to Stopping / Starting / Restarting / Installing / Updating / Removing for the duration of each lifecycle operation and the scan loop preserves those states, the UI can drive its label off the container state itself. A single full-width primary button replaces the Start/Stop pair. Its label, color, and disabled state come from getAppVisualState(), which collapses resting states (exited/created/paused/installed) into "stopped" and passes transitional states through untouched. Changes: - container-client.ts: widen ContainerStatus.state union to include the six transitional variants plus "installed". Add restartContainer() calling the new container-restart RPC. - stores/container.ts: add getAppVisualState() computed and the restartContainer() action. - ContainerApps.vue: single primary button (Start / Stop / Starting / Stopping / Restarting etc.) plus a separate circular Restart button visible only when running. Critically, handleStartApp and handleStopApp now route through store.startContainer and stopContainer (which call container-start / container-stop, the async RPCs) instead of the legacy synchronous bundled-app-start / bundled-app-stop path. Transitional-state polling widened from just "created" to the full set of transitional variants. - ContainerAppDetails.vue: same single-button pattern, Restart button now calls container-restart instead of the old stop-sleep-start sequence, added 2s polling interval for transitional states. - components/ContainerStatus.vue: widen state prop to match the shared union, render transitional labels with a trailing ellipsis and a yellow dot. No new tests — this is presentation logic. Manual verification on .228 will confirm the end-to-end async path: click Stop on LND, button becomes "Stopping" in under a second, stays that way for roughly 5 minutes, then flips to "Start" with a grey dot. The UI must never revert to "Running" mid-stop.
2026-04-23 05:20:15 -04:00
/**
* Restart a container (async; returns immediately with restarting state)
*/
async restartContainer(appId: string): Promise<void> {
return rpcClient.call<void>({
method: 'container-restart',
params: { app_id: appId },
})
},
2026-01-24 22:01:51 +00:00
/**
* Remove a container
*/
async removeContainer(appId: string): Promise<void> {
return rpcClient.call<void>({
method: 'container-remove',
params: { app_id: appId },
})
},
/**
* Get container status
*/
async getContainerStatus(appId: string): Promise<ContainerStatus> {
return rpcClient.call<ContainerStatus>({
method: 'container-status',
params: { app_id: appId },
})
},
/**
* Get container logs
*/
async getContainerLogs(appId: string, lines: number = 100): Promise<string[]> {
return rpcClient.call<string[]>({
method: 'container-logs',
params: { app_id: appId, lines },
})
},
/**
* List all containers
*/
async listContainers(): Promise<ContainerStatus[]> {
return rpcClient.call<ContainerStatus[]>({
method: 'container-list',
params: {},
})
},
/**
* Get health status for all containers
*/
async getHealthStatus(): Promise<Record<string, string>> {
2026-01-24 22:01:51 +00:00
return rpcClient.call<Record<string, string>>({
method: 'container-health',
params: {},
})
},
/**
* Start a bundled app (creates container if needed, then starts it)
*/
async startBundledApp(app: BundledAppConfig): Promise<void> {
return rpcClient.call<void>({
method: 'bundled-app-start',
params: {
app_id: app.id,
image: app.image,
ports: app.ports,
volumes: app.volumes,
},
})
},
/**
* Stop a bundled app
*/
async stopBundledApp(appId: string): Promise<void> {
return rpcClient.call<void>({
method: 'bundled-app-stop',
params: { app_id: appId },
})
},
2026-01-24 22:01:51 +00:00
}