mirror of
https://github.com/lordmathis/llamactl.git
synced 2025-11-06 09:04:27 +00:00
Implement working health check
This commit is contained in:
@@ -1,49 +1,74 @@
|
|||||||
// ui/src/components/HealthBadge.tsx
|
// ui/src/components/HealthBadge.tsx
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { HealthStatus } from '@/types/instance'
|
import { HealthStatus } from "@/types/instance";
|
||||||
import { CheckCircle, Loader2, XCircle } from 'lucide-react'
|
import { CheckCircle, Loader2, XCircle } from "lucide-react";
|
||||||
|
|
||||||
interface HealthBadgeProps {
|
interface HealthBadgeProps {
|
||||||
health?: HealthStatus
|
health?: HealthStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
const HealthBadge: React.FC<HealthBadgeProps> = ({ health }) => {
|
const HealthBadge: React.FC<HealthBadgeProps> = ({ health }) => {
|
||||||
if (!health) return null
|
if (!health) {
|
||||||
|
health = {
|
||||||
|
status: "unknown", // Default to unknown if not provided
|
||||||
|
lastChecked: new Date(), // Default to current date
|
||||||
|
message: undefined, // No message by default
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const getIcon = () => {
|
const getIcon = () => {
|
||||||
switch (health.status) {
|
switch (health.status) {
|
||||||
case 'ok': return <CheckCircle className="h-3 w-3" />
|
case "ok":
|
||||||
case 'loading': return <Loader2 className="h-3 w-3 animate-spin" />
|
return <CheckCircle className="h-3 w-3" />;
|
||||||
case 'error': return <XCircle className="h-3 w-3" />
|
case "loading":
|
||||||
|
return <Loader2 className="h-3 w-3 animate-spin" />;
|
||||||
|
case "error":
|
||||||
|
return <XCircle className="h-3 w-3" />;
|
||||||
|
case "unknown":
|
||||||
|
return <Loader2 className="h-3 w-3 animate-spin" />;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const getVariant = () => {
|
const getVariant = () => {
|
||||||
switch (health.status) {
|
switch (health.status) {
|
||||||
case 'ok': return 'default'
|
case "ok":
|
||||||
case 'loading': return 'outline'
|
return "default";
|
||||||
case 'error': return 'destructive'
|
case "loading":
|
||||||
|
return "outline";
|
||||||
|
case "error":
|
||||||
|
return "destructive";
|
||||||
|
case "unknown":
|
||||||
|
return "secondary";
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const getText = () => {
|
const getText = () => {
|
||||||
switch (health.status) {
|
switch (health.status) {
|
||||||
case 'ok': return 'Ready'
|
case "ok":
|
||||||
case 'loading': return 'Loading'
|
return "Ready";
|
||||||
case 'error': return 'Error'
|
case "loading":
|
||||||
|
return "Loading";
|
||||||
|
case "error":
|
||||||
|
return "Error";
|
||||||
|
case "unknown":
|
||||||
|
return "Unknown";
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Badge
|
<Badge
|
||||||
variant={getVariant()}
|
variant={getVariant()}
|
||||||
className={`flex items-center gap-1.5 ${health.status === 'ok' ? 'bg-green-100 text-green-800 border-green-200 dark:bg-green-900 dark:text-green-200 dark:border-green-800' : ''}`}
|
className={`flex items-center gap-1.5 ${
|
||||||
|
health.status === "ok"
|
||||||
|
? "bg-green-100 text-green-800 border-green-200 dark:bg-green-900 dark:text-green-200 dark:border-green-800"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
{getIcon()}
|
{getIcon()}
|
||||||
<span className="text-xs">{getText()}</span>
|
<span className="text-xs">{getText()}</span>
|
||||||
</Badge>
|
</Badge>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default HealthBadge
|
export default HealthBadge;
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
|
// ui/src/components/InstanceCard.tsx
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Instance } from "@/types/instance";
|
import { Instance } from "@/types/instance";
|
||||||
import { Edit, FileText, Play, Square, Trash2 } from "lucide-react";
|
import { Edit, FileText, Play, Square, Trash2 } from "lucide-react";
|
||||||
import LogsModal from "@/components/LogModal";
|
import LogsModal from "@/components/LogModal";
|
||||||
import { useState } from "react";
|
|
||||||
import HealthBadge from "@/components/HealthBadge";
|
import HealthBadge from "@/components/HealthBadge";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useInstanceHealth } from "@/hooks/useInstanceHealth";
|
||||||
|
|
||||||
interface InstanceCardProps {
|
interface InstanceCardProps {
|
||||||
instance: Instance;
|
instance: Instance;
|
||||||
@@ -22,8 +23,8 @@ function InstanceCard({
|
|||||||
deleteInstance,
|
deleteInstance,
|
||||||
editInstance,
|
editInstance,
|
||||||
}: InstanceCardProps) {
|
}: InstanceCardProps) {
|
||||||
|
|
||||||
const [isLogsOpen, setIsLogsOpen] = useState(false);
|
const [isLogsOpen, setIsLogsOpen] = useState(false);
|
||||||
|
const health = useInstanceHealth(instance.name, instance.running);
|
||||||
|
|
||||||
const handleStart = () => {
|
const handleStart = () => {
|
||||||
startInstance(instance.name);
|
startInstance(instance.name);
|
||||||
@@ -55,7 +56,7 @@ function InstanceCard({
|
|||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<CardTitle className="text-lg">{instance.name}</CardTitle>
|
<CardTitle className="text-lg">{instance.name}</CardTitle>
|
||||||
{instance.running ? <HealthBadge health={instance.health} /> : <Badge variant="secondary">Stopped</Badge>}
|
{instance.running && <HealthBadge health={health} />}
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
|
// ui/src/components/InstanceList.tsx
|
||||||
import { useInstances } from '@/contexts/InstancesContext'
|
import { useInstances } from '@/contexts/InstancesContext'
|
||||||
import InstanceCard from '@/components/InstanceCard'
|
import InstanceCard from '@/components/InstanceCard'
|
||||||
import { Instance } from '@/types/instance'
|
import { Instance } from '@/types/instance'
|
||||||
|
import { memo } from 'react'
|
||||||
|
|
||||||
interface InstanceListProps {
|
interface InstanceListProps {
|
||||||
editInstance: (instance: Instance) => void
|
editInstance: (instance: Instance) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Memoize InstanceCard to prevent re-renders when other instances change
|
||||||
|
const MemoizedInstanceCard = memo(InstanceCard)
|
||||||
|
|
||||||
function InstanceList({ editInstance }: InstanceListProps) {
|
function InstanceList({ editInstance }: InstanceListProps) {
|
||||||
const { instances, loading, error, startInstance, stopInstance, deleteInstance } = useInstances()
|
const { instances, loading, error, startInstance, stopInstance, deleteInstance } = useInstances()
|
||||||
|
|
||||||
@@ -48,7 +53,7 @@ function InstanceList({ editInstance }: InstanceListProps) {
|
|||||||
|
|
||||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||||
{instances.map((instance) => (
|
{instances.map((instance) => (
|
||||||
<InstanceCard
|
<MemoizedInstanceCard
|
||||||
key={instance.name}
|
key={instance.name}
|
||||||
instance={instance}
|
instance={instance}
|
||||||
startInstance={startInstance}
|
startInstance={startInstance}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react'
|
import { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react'
|
||||||
import { CreateInstanceOptions, Instance, HealthStatus } from '@/types/instance'
|
import { CreateInstanceOptions, Instance } from '@/types/instance'
|
||||||
import { instancesApi } from '@/lib/api'
|
import { instancesApi } from '@/lib/api'
|
||||||
import { healthService } from '@/lib/healthService'
|
|
||||||
|
|
||||||
interface InstancesContextState {
|
interface InstancesContextState {
|
||||||
instances: Instance[]
|
instances: Instance[]
|
||||||
@@ -29,45 +28,29 @@ interface InstancesProviderProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const InstancesProvider = ({ children }: InstancesProviderProps) => {
|
export const InstancesProvider = ({ children }: InstancesProviderProps) => {
|
||||||
const [instances, setInstances] = useState<Instance[]>([])
|
const [instancesMap, setInstancesMap] = useState<Map<string, Instance>>(new Map())
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
|
// Convert map to array for consumers
|
||||||
|
const instances = Array.from(instancesMap.values())
|
||||||
|
|
||||||
const clearError = useCallback(() => {
|
const clearError = useCallback(() => {
|
||||||
setError(null)
|
setError(null)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const updateInstanceHealth = useCallback((instanceName: string, health: HealthStatus) => {
|
|
||||||
setInstances(prev => prev.map(instance =>
|
|
||||||
instance.name === instanceName
|
|
||||||
? { ...instance, health }
|
|
||||||
: instance
|
|
||||||
))
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
instances.forEach(instance => {
|
|
||||||
if (instance.running) {
|
|
||||||
healthService.startHealthCheck(
|
|
||||||
instance.name,
|
|
||||||
(health) => updateInstanceHealth(instance.name, health)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
healthService.stopHealthCheck(instance.name)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [instances, updateInstanceHealth])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => healthService.stopAll()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const fetchInstances = useCallback(async () => {
|
const fetchInstances = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setError(null)
|
setError(null)
|
||||||
const data = await instancesApi.list()
|
const data = await instancesApi.list()
|
||||||
setInstances(data)
|
|
||||||
|
// Convert array to map
|
||||||
|
const newMap = new Map<string, Instance>()
|
||||||
|
data.forEach(instance => {
|
||||||
|
newMap.set(instance.name, instance)
|
||||||
|
})
|
||||||
|
setInstancesMap(newMap)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to fetch instances')
|
setError(err instanceof Error ? err.message : 'Failed to fetch instances')
|
||||||
} finally {
|
} finally {
|
||||||
@@ -75,67 +58,100 @@ export const InstancesProvider = ({ children }: InstancesProviderProps) => {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const updateInstanceInMap = useCallback((name: string, updates: Partial<Instance>) => {
|
||||||
|
setInstancesMap(prev => {
|
||||||
|
const newMap = new Map(prev)
|
||||||
|
const existing = newMap.get(name)
|
||||||
|
if (existing) {
|
||||||
|
newMap.set(name, { ...existing, ...updates })
|
||||||
|
}
|
||||||
|
return newMap
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
const createInstance = useCallback(async (name: string, options: CreateInstanceOptions) => {
|
const createInstance = useCallback(async (name: string, options: CreateInstanceOptions) => {
|
||||||
try {
|
try {
|
||||||
setError(null)
|
setError(null)
|
||||||
await instancesApi.create(name, options)
|
const newInstance = await instancesApi.create(name, options)
|
||||||
await fetchInstances()
|
|
||||||
|
// Add to map directly
|
||||||
|
setInstancesMap(prev => {
|
||||||
|
const newMap = new Map(prev)
|
||||||
|
newMap.set(name, newInstance)
|
||||||
|
return newMap
|
||||||
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to create instance')
|
setError(err instanceof Error ? err.message : 'Failed to create instance')
|
||||||
}
|
}
|
||||||
}, [fetchInstances])
|
}, [])
|
||||||
|
|
||||||
const updateInstance = useCallback(async (name: string, options: CreateInstanceOptions) => {
|
const updateInstance = useCallback(async (name: string, options: CreateInstanceOptions) => {
|
||||||
try {
|
try {
|
||||||
setError(null)
|
setError(null)
|
||||||
await instancesApi.update(name, options)
|
const updatedInstance = await instancesApi.update(name, options)
|
||||||
await fetchInstances()
|
|
||||||
|
// Update in map directly
|
||||||
|
setInstancesMap(prev => {
|
||||||
|
const newMap = new Map(prev)
|
||||||
|
newMap.set(name, updatedInstance)
|
||||||
|
return newMap
|
||||||
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to update instance')
|
setError(err instanceof Error ? err.message : 'Failed to update instance')
|
||||||
}
|
}
|
||||||
}, [fetchInstances])
|
}, [])
|
||||||
|
|
||||||
const startInstance = useCallback(async (name: string) => {
|
const startInstance = useCallback(async (name: string) => {
|
||||||
try {
|
try {
|
||||||
setError(null)
|
setError(null)
|
||||||
await instancesApi.start(name)
|
await instancesApi.start(name)
|
||||||
await fetchInstances()
|
|
||||||
|
// Update only this instance's running status
|
||||||
|
updateInstanceInMap(name, { running: true })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to start instance')
|
setError(err instanceof Error ? err.message : 'Failed to start instance')
|
||||||
}
|
}
|
||||||
}, [fetchInstances])
|
}, [updateInstanceInMap])
|
||||||
|
|
||||||
const stopInstance = useCallback(async (name: string) => {
|
const stopInstance = useCallback(async (name: string) => {
|
||||||
try {
|
try {
|
||||||
setError(null)
|
setError(null)
|
||||||
healthService.stopHealthCheck(name)
|
|
||||||
await instancesApi.stop(name)
|
await instancesApi.stop(name)
|
||||||
await fetchInstances()
|
|
||||||
|
// Update only this instance's running status
|
||||||
|
updateInstanceInMap(name, { running: false })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to stop instance')
|
setError(err instanceof Error ? err.message : 'Failed to stop instance')
|
||||||
}
|
}
|
||||||
}, [fetchInstances])
|
}, [updateInstanceInMap])
|
||||||
|
|
||||||
const restartInstance = useCallback(async (name: string) => {
|
const restartInstance = useCallback(async (name: string) => {
|
||||||
try {
|
try {
|
||||||
setError(null)
|
setError(null)
|
||||||
await instancesApi.restart(name)
|
await instancesApi.restart(name)
|
||||||
await fetchInstances()
|
|
||||||
|
// Update only this instance's running status
|
||||||
|
updateInstanceInMap(name, { running: true })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to restart instance')
|
setError(err instanceof Error ? err.message : 'Failed to restart instance')
|
||||||
}
|
}
|
||||||
}, [fetchInstances])
|
}, [updateInstanceInMap])
|
||||||
|
|
||||||
const deleteInstance = useCallback(async (name: string) => {
|
const deleteInstance = useCallback(async (name: string) => {
|
||||||
try {
|
try {
|
||||||
setError(null)
|
setError(null)
|
||||||
healthService.stopHealthCheck(name)
|
|
||||||
await instancesApi.delete(name)
|
await instancesApi.delete(name)
|
||||||
await fetchInstances()
|
|
||||||
|
// Remove from map directly
|
||||||
|
setInstancesMap(prev => {
|
||||||
|
const newMap = new Map(prev)
|
||||||
|
newMap.delete(name)
|
||||||
|
return newMap
|
||||||
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to delete instance')
|
setError(err instanceof Error ? err.message : 'Failed to delete instance')
|
||||||
}
|
}
|
||||||
}, [fetchInstances])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchInstances()
|
fetchInstances()
|
||||||
|
|||||||
25
ui/src/hooks/useInstanceHealth.ts
Normal file
25
ui/src/hooks/useInstanceHealth.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// ui/src/hooks/useInstanceHealth.ts
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { HealthStatus } from '@/types/instance'
|
||||||
|
import { healthService } from '@/lib/healthService'
|
||||||
|
|
||||||
|
export function useInstanceHealth(instanceName: string, isRunning: boolean): HealthStatus | undefined {
|
||||||
|
const [health, setHealth] = useState<HealthStatus | undefined>()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isRunning) {
|
||||||
|
setHealth(undefined)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe to health updates for this instance
|
||||||
|
const unsubscribe = healthService.subscribe(instanceName, (healthStatus) => {
|
||||||
|
setHealth(healthStatus)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Cleanup subscription on unmount or when running changes
|
||||||
|
return unsubscribe
|
||||||
|
}, [instanceName, isRunning])
|
||||||
|
|
||||||
|
return health
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
// ui/src/lib/healthService.ts
|
|
||||||
import { HealthStatus } from '@/types/instance'
|
import { HealthStatus } from '@/types/instance'
|
||||||
|
|
||||||
|
type HealthCallback = (health: HealthStatus) => void
|
||||||
|
|
||||||
class HealthService {
|
class HealthService {
|
||||||
private intervals: Map<string, NodeJS.Timeout> = new Map()
|
private intervals: Map<string, NodeJS.Timeout> = new Map()
|
||||||
private startupTimeouts: Map<string, NodeJS.Timeout> = new Map()
|
private callbacks: Map<string, Set<HealthCallback>> = new Map()
|
||||||
|
|
||||||
async checkHealth(instanceName: string): Promise<HealthStatus> {
|
async checkHealth(instanceName: string): Promise<HealthStatus> {
|
||||||
try {
|
try {
|
||||||
@@ -37,37 +38,54 @@ class HealthService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startHealthCheck(instanceName: string, onUpdate: (health: HealthStatus) => void): void {
|
subscribe(instanceName: string, callback: HealthCallback): () => void {
|
||||||
// Don't start if already checking
|
if (!this.callbacks.has(instanceName)) {
|
||||||
if (this.isChecking(instanceName)) {
|
this.callbacks.set(instanceName, new Set())
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const startupTimeout = setTimeout(() => {
|
this.callbacks.get(instanceName)!.add(callback)
|
||||||
this.startupTimeouts.delete(instanceName)
|
|
||||||
|
|
||||||
const check = async () => {
|
// Start health checking if this is the first subscriber
|
||||||
const health = await this.checkHealth(instanceName)
|
if (this.callbacks.get(instanceName)!.size === 1) {
|
||||||
onUpdate(health)
|
this.startHealthCheck(instanceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return unsubscribe function
|
||||||
|
return () => {
|
||||||
|
const callbacks = this.callbacks.get(instanceName)
|
||||||
|
if (callbacks) {
|
||||||
|
callbacks.delete(callback)
|
||||||
|
|
||||||
|
// Stop health checking if no more subscribers
|
||||||
|
if (callbacks.size === 0) {
|
||||||
|
this.stopHealthCheck(instanceName)
|
||||||
|
this.callbacks.delete(instanceName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
check()
|
|
||||||
const interval = setInterval(check, 60000)
|
|
||||||
this.intervals.set(instanceName, interval)
|
|
||||||
}, 2000)
|
|
||||||
|
|
||||||
this.startupTimeouts.set(instanceName, startupTimeout)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stopHealthCheck(instanceName: string): void {
|
private startHealthCheck(instanceName: string): void {
|
||||||
// Clear startup timeout if exists
|
if (this.intervals.has(instanceName)) {
|
||||||
const startupTimeout = this.startupTimeouts.get(instanceName)
|
return // Already checking
|
||||||
if (startupTimeout) {
|
|
||||||
clearTimeout(startupTimeout)
|
|
||||||
this.startupTimeouts.delete(instanceName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear interval if exists
|
// Initial check with delay
|
||||||
|
setTimeout(async () => {
|
||||||
|
const health = await this.checkHealth(instanceName)
|
||||||
|
this.notifyCallbacks(instanceName, health)
|
||||||
|
|
||||||
|
// Start periodic checks
|
||||||
|
const interval = setInterval(async () => {
|
||||||
|
const health = await this.checkHealth(instanceName)
|
||||||
|
this.notifyCallbacks(instanceName, health)
|
||||||
|
}, 60000)
|
||||||
|
|
||||||
|
this.intervals.set(instanceName, interval)
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
|
||||||
|
private stopHealthCheck(instanceName: string): void {
|
||||||
const interval = this.intervals.get(instanceName)
|
const interval = this.intervals.get(instanceName)
|
||||||
if (interval) {
|
if (interval) {
|
||||||
clearInterval(interval)
|
clearInterval(interval)
|
||||||
@@ -75,16 +93,23 @@ class HealthService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stopAll(): void {
|
private notifyCallbacks(instanceName: string, health: HealthStatus): void {
|
||||||
this.startupTimeouts.forEach(timeout => clearTimeout(timeout))
|
const callbacks = this.callbacks.get(instanceName)
|
||||||
this.startupTimeouts.clear()
|
if (callbacks) {
|
||||||
this.intervals.forEach(interval => clearInterval(interval))
|
callbacks.forEach(callback => callback(health))
|
||||||
this.intervals.clear()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isChecking(instanceName: string): boolean {
|
stopAll(): void {
|
||||||
return this.intervals.has(instanceName) || this.startupTimeouts.has(instanceName)
|
this.intervals.forEach(interval => clearInterval(interval))
|
||||||
|
this.intervals.clear()
|
||||||
|
this.callbacks.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const healthService = new HealthService()
|
export const healthService = new HealthService()
|
||||||
|
|
||||||
|
// Export the individual checkHealth function as well
|
||||||
|
export async function checkHealth(instanceName: string): Promise<HealthStatus> {
|
||||||
|
return healthService.checkHealth(instanceName)
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import { CreateInstanceOptions } from '@/schemas/instanceOptions'
|
|||||||
export { type CreateInstanceOptions } from '@/schemas/instanceOptions'
|
export { type CreateInstanceOptions } from '@/schemas/instanceOptions'
|
||||||
|
|
||||||
export interface HealthStatus {
|
export interface HealthStatus {
|
||||||
status: 'ok' | 'loading' | 'error'
|
status: 'ok' | 'loading' | 'error' | 'unknown'
|
||||||
message?: string
|
message?: string
|
||||||
lastChecked: Date
|
lastChecked: Date
|
||||||
}
|
}
|
||||||
@@ -12,5 +12,4 @@ export interface Instance {
|
|||||||
name: string;
|
name: string;
|
||||||
running: boolean;
|
running: boolean;
|
||||||
options?: CreateInstanceOptions;
|
options?: CreateInstanceOptions;
|
||||||
health?: HealthStatus;
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user