mirror of
https://github.com/lordmathis/llamactl.git
synced 2025-11-05 16:44:22 +00:00
Remove 'loading' and 'error' states
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import type { HealthStatus } from "@/types/instance";
|
import type { HealthStatus } from "@/types/instance";
|
||||||
import { CheckCircle, Loader2, XCircle, Clock, AlertCircle } from "lucide-react";
|
import { CheckCircle, Loader2, XCircle, Clock } from "lucide-react";
|
||||||
|
|
||||||
interface HealthBadgeProps {
|
interface HealthBadgeProps {
|
||||||
health?: HealthStatus;
|
health?: HealthStatus;
|
||||||
@@ -17,16 +17,12 @@ const HealthBadge: React.FC<HealthBadgeProps> = ({ health }) => {
|
|||||||
switch (health.state) {
|
switch (health.state) {
|
||||||
case "ready":
|
case "ready":
|
||||||
return <CheckCircle className="h-3 w-3" />;
|
return <CheckCircle className="h-3 w-3" />;
|
||||||
case "loading":
|
|
||||||
return <Loader2 className="h-3 w-3 animate-spin" />;
|
|
||||||
case "starting":
|
case "starting":
|
||||||
return <Loader2 className="h-3 w-3 animate-spin" />;
|
return <Loader2 className="h-3 w-3 animate-spin" />;
|
||||||
case "restarting":
|
case "restarting":
|
||||||
return <Loader2 className="h-3 w-3 animate-spin" />;
|
return <Loader2 className="h-3 w-3 animate-spin" />;
|
||||||
case "stopped":
|
case "stopped":
|
||||||
return <Clock className="h-3 w-3" />;
|
return <Clock className="h-3 w-3" />;
|
||||||
case "error":
|
|
||||||
return <AlertCircle className="h-3 w-3" />;
|
|
||||||
case "failed":
|
case "failed":
|
||||||
return <XCircle className="h-3 w-3" />;
|
return <XCircle className="h-3 w-3" />;
|
||||||
}
|
}
|
||||||
@@ -36,16 +32,12 @@ const HealthBadge: React.FC<HealthBadgeProps> = ({ health }) => {
|
|||||||
switch (health.state) {
|
switch (health.state) {
|
||||||
case "ready":
|
case "ready":
|
||||||
return "default";
|
return "default";
|
||||||
case "loading":
|
|
||||||
return "outline";
|
|
||||||
case "starting":
|
case "starting":
|
||||||
return "outline";
|
return "outline";
|
||||||
case "restarting":
|
case "restarting":
|
||||||
return "outline";
|
return "outline";
|
||||||
case "stopped":
|
case "stopped":
|
||||||
return "secondary";
|
return "secondary";
|
||||||
case "error":
|
|
||||||
return "destructive";
|
|
||||||
case "failed":
|
case "failed":
|
||||||
return "destructive";
|
return "destructive";
|
||||||
}
|
}
|
||||||
@@ -55,16 +47,12 @@ const HealthBadge: React.FC<HealthBadgeProps> = ({ health }) => {
|
|||||||
switch (health.state) {
|
switch (health.state) {
|
||||||
case "ready":
|
case "ready":
|
||||||
return "Ready";
|
return "Ready";
|
||||||
case "loading":
|
|
||||||
return "Loading";
|
|
||||||
case "starting":
|
case "starting":
|
||||||
return "Starting";
|
return "Starting";
|
||||||
case "restarting":
|
case "restarting":
|
||||||
return "Restarting";
|
return "Restarting";
|
||||||
case "stopped":
|
case "stopped":
|
||||||
return "Stopped";
|
return "Stopped";
|
||||||
case "error":
|
|
||||||
return "Error";
|
|
||||||
case "failed":
|
case "failed":
|
||||||
return "Failed";
|
return "Failed";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,10 @@ type HealthCallback = (health: HealthStatus) => void
|
|||||||
// Polling intervals based on health state (in milliseconds)
|
// Polling intervals based on health state (in milliseconds)
|
||||||
const POLLING_INTERVALS: Record<HealthState, number> = {
|
const POLLING_INTERVALS: Record<HealthState, number> = {
|
||||||
'starting': 5000, // 5 seconds - frequent during startup
|
'starting': 5000, // 5 seconds - frequent during startup
|
||||||
'loading': 5000, // 5 seconds - model loading
|
|
||||||
'restarting': 5000, // 5 seconds - restart in progress
|
'restarting': 5000, // 5 seconds - restart in progress
|
||||||
'ready': 60000, // 60 seconds - stable state
|
'ready': 60000, // 60 seconds - stable state
|
||||||
'stopped': 0, // No polling
|
'stopped': 0, // No polling
|
||||||
'failed': 0, // No polling
|
'failed': 0, // No polling
|
||||||
'error': 10000, // 10 seconds - retry on error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class HealthService {
|
class HealthService {
|
||||||
@@ -42,7 +40,7 @@ class HealthService {
|
|||||||
try {
|
try {
|
||||||
await instancesApi.getHealth(instanceName)
|
await instancesApi.getHealth(instanceName)
|
||||||
|
|
||||||
// HTTP health check succeeded
|
// HTTP health check succeeded - instance is ready
|
||||||
const health: HealthStatus = {
|
const health: HealthStatus = {
|
||||||
state: 'ready',
|
state: 'ready',
|
||||||
instanceStatus: 'running',
|
instanceStatus: 'running',
|
||||||
@@ -54,45 +52,21 @@ class HealthService {
|
|||||||
return health
|
return health
|
||||||
|
|
||||||
} catch (httpError) {
|
} catch (httpError) {
|
||||||
// HTTP health check failed while instance is running
|
// HTTP health check failed - instance is still starting
|
||||||
// Re-verify instance is still running
|
// Any error (503, connection refused, timeout, etc.) means "starting"
|
||||||
try {
|
const health: HealthStatus = {
|
||||||
const verifyInstance = await instancesApi.get(instanceName)
|
state: 'starting',
|
||||||
|
instanceStatus: 'running',
|
||||||
if (verifyInstance.status !== 'running') {
|
lastChecked: new Date(),
|
||||||
// Instance stopped/failed since our first check
|
error: httpError instanceof Error ? httpError.message : 'Health check failed',
|
||||||
const health: HealthStatus = {
|
source: 'http'
|
||||||
state: this.mapStatusToHealthState(verifyInstance.status),
|
|
||||||
instanceStatus: verifyInstance.status,
|
|
||||||
lastChecked: new Date(),
|
|
||||||
source: 'backend'
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateCache(instanceName, health)
|
|
||||||
return health
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instance still running but HTTP failed - classify error
|
|
||||||
const health = this.classifyHttpError(httpError as Error, 'running')
|
|
||||||
this.updateCache(instanceName, health)
|
|
||||||
return health
|
|
||||||
|
|
||||||
} catch (verifyError) {
|
|
||||||
// Failed to verify - return error state
|
|
||||||
const health: HealthStatus = {
|
|
||||||
state: 'error',
|
|
||||||
instanceStatus: 'running',
|
|
||||||
lastChecked: new Date(),
|
|
||||||
error: 'Failed to verify instance status',
|
|
||||||
source: 'error'
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateCache(instanceName, health)
|
|
||||||
return health
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.updateCache(instanceName, health)
|
||||||
|
return health
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Instance not running - return backend status
|
// Instance not running - map backend status directly
|
||||||
const health: HealthStatus = {
|
const health: HealthStatus = {
|
||||||
state: this.mapStatusToHealthState(instance.status),
|
state: this.mapStatusToHealthState(instance.status),
|
||||||
instanceStatus: instance.status,
|
instanceStatus: instance.status,
|
||||||
@@ -105,56 +79,11 @@ class HealthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Failed to get instance
|
// Failed to get instance status from backend
|
||||||
const health: HealthStatus = {
|
// This is a backend communication error, not an instance health error
|
||||||
state: 'error',
|
// Let the error propagate so polling can retry
|
||||||
instanceStatus: 'unknown',
|
console.error(`Failed to get instance status for ${instanceName}:`, error)
|
||||||
lastChecked: new Date(),
|
throw error
|
||||||
error: error instanceof Error ? error.message : 'Unknown error',
|
|
||||||
source: 'error'
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateCache(instanceName, health)
|
|
||||||
return health
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Classifies HTTP errors into appropriate health states
|
|
||||||
*/
|
|
||||||
private classifyHttpError(error: Error, instanceStatus: InstanceStatus): HealthStatus {
|
|
||||||
const errorMessage = error.message.toLowerCase()
|
|
||||||
|
|
||||||
// Parse HTTP status code from error message if available
|
|
||||||
if (errorMessage.includes('503')) {
|
|
||||||
return {
|
|
||||||
state: 'loading',
|
|
||||||
instanceStatus,
|
|
||||||
lastChecked: new Date(),
|
|
||||||
error: 'Service loading',
|
|
||||||
source: 'http'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errorMessage.includes('connection refused') ||
|
|
||||||
errorMessage.includes('econnrefused') ||
|
|
||||||
errorMessage.includes('network error')) {
|
|
||||||
return {
|
|
||||||
state: 'starting',
|
|
||||||
instanceStatus,
|
|
||||||
lastChecked: new Date(),
|
|
||||||
error: 'Connection refused',
|
|
||||||
source: 'http'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Other HTTP errors
|
|
||||||
return {
|
|
||||||
state: 'error',
|
|
||||||
instanceStatus,
|
|
||||||
lastChecked: new Date(),
|
|
||||||
error: error.message,
|
|
||||||
source: 'http'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,10 +93,9 @@ class HealthService {
|
|||||||
private mapStatusToHealthState(status: InstanceStatus): HealthState {
|
private mapStatusToHealthState(status: InstanceStatus): HealthState {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'stopped': return 'stopped'
|
case 'stopped': return 'stopped'
|
||||||
case 'running': return 'starting' // Unknown without HTTP check
|
case 'running': return 'starting' // Should not happen as we check HTTP for running
|
||||||
case 'failed': return 'failed'
|
case 'failed': return 'failed'
|
||||||
case 'restarting': return 'restarting'
|
case 'restarting': return 'restarting'
|
||||||
default: return 'error'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,15 +116,20 @@ class HealthService {
|
|||||||
// Invalidate cache
|
// Invalidate cache
|
||||||
this.healthCache.delete(instanceName)
|
this.healthCache.delete(instanceName)
|
||||||
|
|
||||||
const health = await this.performHealthCheck(instanceName)
|
try {
|
||||||
this.notifyCallbacks(instanceName, health)
|
const health = await this.performHealthCheck(instanceName)
|
||||||
|
this.notifyCallbacks(instanceName, health)
|
||||||
|
|
||||||
// Update last state and adjust polling interval if needed
|
// Update last state and adjust polling interval if needed
|
||||||
const previousState = this.lastHealthState.get(instanceName)
|
const previousState = this.lastHealthState.get(instanceName)
|
||||||
this.lastHealthState.set(instanceName, health.state)
|
this.lastHealthState.set(instanceName, health.state)
|
||||||
|
|
||||||
if (previousState !== health.state) {
|
if (previousState !== health.state) {
|
||||||
this.adjustPollingInterval(instanceName, health.state)
|
this.adjustPollingInterval(instanceName, health.state)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Error getting health - keep polling if active
|
||||||
|
console.error(`Failed to refresh health for ${instanceName}:`, error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +206,7 @@ class HealthService {
|
|||||||
|
|
||||||
const pollInterval = POLLING_INTERVALS[state]
|
const pollInterval = POLLING_INTERVALS[state]
|
||||||
|
|
||||||
// Don't poll for stable states (stopped, failed, ready has long interval)
|
// Don't poll for stable states (stopped, failed)
|
||||||
if (pollInterval === 0) {
|
if (pollInterval === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -293,6 +226,7 @@ class HealthService {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Health check failed for ${instanceName}:`, error)
|
console.error(`Health check failed for ${instanceName}:`, error)
|
||||||
|
// Continue polling even on error
|
||||||
}
|
}
|
||||||
}, pollInterval)
|
}, pollInterval)
|
||||||
|
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ export type BackendTypeValue = typeof BackendType[keyof typeof BackendType]
|
|||||||
|
|
||||||
export type InstanceStatus = 'running' | 'stopped' | 'failed' | 'restarting'
|
export type InstanceStatus = 'running' | 'stopped' | 'failed' | 'restarting'
|
||||||
|
|
||||||
export type HealthState = 'stopped' | 'starting' | 'loading' | 'ready' | 'error' | 'failed' | 'restarting'
|
export type HealthState = 'stopped' | 'starting' | 'ready' | 'failed' | 'restarting'
|
||||||
|
|
||||||
export interface HealthStatus {
|
export interface HealthStatus {
|
||||||
state: HealthState
|
state: HealthState
|
||||||
instanceStatus: InstanceStatus | 'unknown'
|
instanceStatus: InstanceStatus
|
||||||
lastChecked: Date
|
lastChecked: Date
|
||||||
error?: string
|
error?: string
|
||||||
source: 'backend' | 'http' | 'error'
|
source: 'backend' | 'http'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Instance {
|
export interface Instance {
|
||||||
|
|||||||
Reference in New Issue
Block a user