diff --git a/dashboard/src/dashboard/BotCard.tsx b/dashboard/src/dashboard/BotCard.tsx index fc458df..6e587d3 100644 --- a/dashboard/src/dashboard/BotCard.tsx +++ b/dashboard/src/dashboard/BotCard.tsx @@ -19,12 +19,15 @@ interface BotCardProps { function getEffectiveStatus(bot: Bot): BotStatus { const containerState = bot.container_status?.state; if (containerState === 'running') { - // Check if recently started (within 8 seconds) - const startedAt = bot.container_status?.startedAt; - if (startedAt) { - const elapsed = Date.now() - new Date(startedAt).getTime(); - if (elapsed < 8000) return 'starting'; + // Use Docker health check status to determine if bot is ready + const health = bot.container_status?.health; + if (health === 'starting') { + return 'starting'; } + if (health === 'unhealthy') { + return 'error'; + } + // 'healthy' or 'none' (no healthcheck configured) = running return 'running'; } if (containerState === 'exited' || containerState === 'dead') { diff --git a/dashboard/src/types.ts b/dashboard/src/types.ts index d0dd984..4deea7f 100644 --- a/dashboard/src/types.ts +++ b/dashboard/src/types.ts @@ -1,5 +1,7 @@ export type BotStatus = 'created' | 'starting' | 'running' | 'stopped' | 'error'; +export type HealthStatus = 'none' | 'starting' | 'healthy' | 'unhealthy'; + export interface ContainerStatus { id: string; state: string; @@ -7,6 +9,7 @@ export interface ContainerStatus { exitCode: number; startedAt: string; finishedAt: string; + health: HealthStatus; } export interface Bot { diff --git a/src/services/DockerService.ts b/src/services/DockerService.ts index 193eef8..2cc92f2 100644 --- a/src/services/DockerService.ts +++ b/src/services/DockerService.ts @@ -55,6 +55,13 @@ export class DockerService { [LABEL_BOT_ID]: botId, [LABEL_BOT_HOSTNAME]: hostname }, + Healthcheck: { + Test: ['CMD', 'wget', '-q', '--spider', `http://localhost:${config.port}/health`], + Interval: 2_000_000_000, // 2s in nanoseconds + Timeout: 3_000_000_000, // 3s in nanoseconds + Retries: 30, + StartPeriod: 5_000_000_000, // 5s in nanoseconds + }, HostConfig: { Binds: [ `${config.hostSecretsPath}:/run/secrets:ro`, @@ -182,13 +189,21 @@ export class DockerService { const container = this.docker.getContainer(containerName); const info = await container.inspect(); + // Extract health status from Docker's Health field + const healthState = info.State.Health?.Status; + let health: ContainerStatus['health'] = 'none'; + if (healthState === 'starting') health = 'starting'; + else if (healthState === 'healthy') health = 'healthy'; + else if (healthState === 'unhealthy') health = 'unhealthy'; + return { id: info.Id, state: info.State.Status as ContainerStatus['state'], running: info.State.Running, exitCode: info.State.ExitCode, startedAt: info.State.StartedAt, - finishedAt: info.State.FinishedAt + finishedAt: info.State.FinishedAt, + health }; } catch (err) { const dockerErr = err as { statusCode?: number }; diff --git a/src/types/container.ts b/src/types/container.ts index bff5ad4..3c44c3c 100644 --- a/src/types/container.ts +++ b/src/types/container.ts @@ -14,6 +14,9 @@ export type ContainerState = | 'removing' | 'dead'; +/** Docker health check status */ +export type HealthStatus = 'none' | 'starting' | 'healthy' | 'unhealthy'; + /** Container status from Docker inspect */ export interface ContainerStatus { id: string; @@ -22,6 +25,7 @@ export interface ContainerStatus { exitCode: number; startedAt: string; finishedAt: string; + health: HealthStatus; } /** Container info from Docker list (human-readable) */