diff --git a/apps/web-dashboard/src/app/services/page.tsx b/apps/web-dashboard/src/app/services/page.tsx new file mode 100644 index 0000000..e6bdba9 --- /dev/null +++ b/apps/web-dashboard/src/app/services/page.tsx @@ -0,0 +1,345 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Server, Play, Square, RefreshCw, Eye, Activity, AlertTriangle } from "lucide-react"; +import { api, Service, ServiceHealth } from "@/lib/api"; +import { ErrorAlert } from "@/components/ui/error-alert"; +import { LoadingState, ActionLoading } from "@/components/ui/loading-state"; +import { useServiceUpdates } from "@/hooks/use-websocket"; +import { WebSocketStatus } from "@/components/ui/websocket-status"; + +export default function ServicesPage() { + const [services, setServices] = useState([]); + const [selectedService, setSelectedService] = useState(null); + const [health, setHealth] = useState(null); + const [podStatus, setPodStatus] = useState(null); + const [logs, setLogs] = useState(""); + const [events, setEvents] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [actionLoading, setActionLoading] = useState(null); + + // WebSocket real-time updates + const serviceUpdate = useServiceUpdates(); + + useEffect(() => { + loadServices(); + }, []); + + // Handle real-time service updates + useEffect(() => { + if (serviceUpdate.data) { + setServices(prevServices => + prevServices.map(service => + service.name === serviceUpdate.data?.service + ? { ...service, ...serviceUpdate.data } + : service + ) + ); + } + }, [serviceUpdate.data]); + + const loadServices = async () => { + try { + setLoading(true); + setError(null); + const servicesResponse = await api.services.list(); + setServices(servicesResponse.services); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : "Failed to load services"; + setError(errorMessage); + console.error("Failed to load services:", err); + } finally { + setLoading(false); + } + }; + + const handleServiceAction = async (serviceName: string, action: string) => { + try { + setActionLoading(action); + setError(null); + switch (action) { + case 'restart': + await api.services.restart(serviceName); + break; + case 'start': + await api.services.start(serviceName); + break; + case 'stop': + await api.services.stop(serviceName); + break; + } + // Refresh services after action + await loadServices(); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : `Failed to ${action} service`; + setError(errorMessage); + console.error(`Failed to ${action} service ${serviceName}:`, err); + } finally { + setActionLoading(null); + } + }; + + const handleScale = async (serviceName: string, replicas: number) => { + try { + setActionLoading('scale'); + setError(null); + await api.services.scale(serviceName, replicas); + await loadServices(); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : "Failed to scale service"; + setError(errorMessage); + console.error(`Failed to scale service ${serviceName} to ${replicas} replicas:`, err); + } finally { + setActionLoading(null); + } + }; + + const loadServiceDetails = async (serviceName: string) => { + try { + setError(null); + const [serviceDetails, healthData, podData, logsData, eventsData] = await Promise.all([ + api.services.get(serviceName), + api.services.health(serviceName), + api.services.podStatus(serviceName), + api.services.logs(serviceName), + api.services.events(serviceName) + ]); + + setSelectedService(serviceDetails); + setHealth(healthData); + setPodStatus(podData); + setLogs(logsData); + setEvents(eventsData.events); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to load service details"); + } + }; + + if (loading && services.length === 0) { + return ( +
+
+
+ +
+

Services

+

Kubernetes service management and monitoring.

+
+
+ +
+ + +
+ {[...Array(6)].map((_, i) => ( +
+
+
+ ))} +
+
+
+ ); + } + + return ( +
+
+
+ +
+

Services

+

Kubernetes service management and monitoring.

+
+
+
+ + +
+
+ + setError(null)} + retryText="Retry Loading" + /> + + +
+ {services.map((service) => ( + loadServiceDetails(service.name)}> + +
+ {service.name} + 0 ? "warning" : "destructive" + }> + {service.readyReplicas}/{service.replicas} + +
+

{service.namespace}

+
+ +
+
+
+

Ready

+

{service.readyReplicas}

+
+
+

Available

+

{service.availableReplicas}

+
+
+
+ + + + + + + + + +
+
+
+
+ ))} +
+
+ + {selectedService && ( +
+ + + + + {selectedService.name} - Details + + + +
+
+
+

Namespace

+

{selectedService.namespace}

+
+
+

Created

+

{new Date(selectedService.creationTimestamp).toLocaleString()}

+
+
+

Replicas

+

{selectedService.replicas}

+
+
+

Ready

+

{selectedService.readyReplicas}

+
+
+ + {health && ( +
+

Health Status

+ + {health.status} + +
+ )} + +
+

Scale Service

+
+ + + + +
+
+
+
+
+ + + + + + Logs & Events + + + +
+
+

Recent Logs

+
+ {logs || "No logs available"} +
+
+ +
+

Recent Events

+
+ {events.length > 0 ? ( + events.slice(0, 5).map((event, index) => ( +
+ {event.type} + {event.message} +
+ )) + ) : ( +

No recent events

+ )} +
+
+
+
+
+
+ )} +
+ ); +}