Implement bundled app management in RPC and UI

- Added new RPC methods for starting and stopping bundled apps, allowing management of pre-loaded container images.
- Enhanced container listing logic to include a fallback to Podman for bundled apps.
- Updated the UI to display bundled apps with their respective statuses, including start and stop functionality.
- Introduced a new Pinia store structure to manage loading states and app statuses for bundled applications.
- Refactored existing components to improve user experience and streamline app management.
This commit is contained in:
Dorian
2026-02-01 06:04:36 +00:00
parent 66c823e2fd
commit 00d1af12f0
8 changed files with 561 additions and 106 deletions

View File

@@ -20,6 +20,14 @@ export interface ContainerAppInfo {
health: 'healthy' | 'unhealthy' | 'unknown' | 'starting'
}
export interface BundledAppConfig {
id: string
name: string
image: string
ports: { host: number; container: number }[]
volumes: { host: string; container: string }[]
}
export const containerClient = {
/**
* Install a container app from a manifest file
@@ -94,10 +102,35 @@ export const containerClient = {
/**
* Get health status for all containers
*/
async getHealthStatus(): Promise<Record<string, 'healthy' | 'unhealthy' | 'unknown' | 'starting'>> {
async getHealthStatus(): Promise<Record<string, string>> {
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 },
})
},
}

View File

@@ -14,7 +14,6 @@ export class WebSocketClient {
private shouldReconnect = true
private url: string
private reconnectTimer: ReturnType<typeof setTimeout> | null = null
private isConnecting = false
constructor(url: string = '/ws/db') {
this.url = url
@@ -99,7 +98,6 @@ export class WebSocketClient {
this.ws.onopen = () => {
clearTimeout(connectionTimeout)
this.isConnecting = false
this.reconnectAttempts = 0
console.log('[WebSocket] Connected successfully')
resolve()
@@ -107,7 +105,6 @@ export class WebSocketClient {
this.ws.onerror = (error) => {
clearTimeout(connectionTimeout)
this.isConnecting = false
console.error('[WebSocket] Connection error:', error)
// Don't reject immediately - let onclose handle reconnection
// This prevents errors from blocking reconnection
@@ -124,7 +121,6 @@ export class WebSocketClient {
this.ws.onclose = (event) => {
clearTimeout(connectionTimeout)
this.isConnecting = false
console.log('[WebSocket] Closed', { code: event.code, reason: event.reason, wasClean: event.wasClean })
// Clear the WebSocket reference
@@ -195,7 +191,6 @@ export class WebSocketClient {
disconnect(): void {
this.shouldReconnect = false
this.reconnectAttempts = 0
this.isConnecting = false
// Clear reconnect timer
if (this.reconnectTimer) {
@@ -242,7 +237,7 @@ function getWebSocketClient(): WebSocketClient {
const existing = (window as any).__archipelago_ws_client
if (existing && existing instanceof WebSocketClient) {
// Check if the WebSocket is still valid
if (existing.ws && existing.ws.readyState === WebSocket.OPEN) {
if (existing.isConnected()) {
console.log('[WebSocket] Using existing connected client from HMR')
wsClientInstance = existing
return existing