Skip to main content
The Service Status Grid provides an at-a-glance view of all monitored services. It displays health status, service types, and details for each configured service in your infrastructure.

Overview

The status grid polls the backend API to check service health and displays the results in a responsive card layout. Each service card shows:
  • Service name - Configured service identifier
  • Status badge - Running, stopped, or error state
  • Service type - Database, web server, or custom service
  • Details - Additional status information

Real-time Status

Updated via WebSocket events and periodic polling

Service Types

Supports databases, web servers, and custom services

Responsive Grid

Adapts from 1 to 4 columns based on screen size

Visual Indicators

Color-coded status badges and service icons

StatusGrid Component

The component receives service data and renders status cards:
// components/dashboard/StatusGrid.tsx
import { CheckCircle2, XCircle, Globe, Database, Terminal } from "lucide-react";

interface StatusGridProps {
  services: StatusResponse | null;
}

export function StatusGrid({ services }: StatusGridProps) {
  const getStatusColor = (status: string) => {
    switch (status) {
      case 'running': 
        return 'bg-green-500/10 text-green-500 border-green-500/20';
      case 'stopped': 
        return 'bg-red-500/10 text-red-500 border-red-500/20';
      case 'error': 
        return 'bg-red-500/10 text-red-500 border-red-500/20';
      default: 
        return 'bg-gray-500/10 text-gray-400 border-gray-500/20';
    }
  };

  const getStatusIcon = (status: string) => {
    switch (status) {
      case 'running': return <CheckCircle2 className="w-4 h-4" />;
      case 'stopped': return <XCircle className="w-4 h-4" />;
      default: return <Clock className="w-4 h-4" />;
    }
  };

  return (
    <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
      {Object.entries(services).map(([name, service]) => (
        <div key={name} className="p-5 rounded-xl border bg-card shadow-sm">
          <div className="flex items-center justify-between mb-4">
            <h3 className="font-semibold text-sm capitalize">{name}</h3>
            <ServiceTypeIcon type={service.type} />
          </div>

          <div className={`inline-flex items-center gap-1.5 px-2.5 py-0.5 
            rounded-full text-xs font-medium border ${getStatusColor(service.status)}`}>
            {getStatusIcon(service.status)}
            {service.status}
          </div>

          <p className="text-xs text-muted-foreground mt-3 truncate font-mono">
            {service.details}
          </p>
        </div>
      ))}
    </div>
  );
}

Data Flow

The main dashboard page fetches and updates service status:
// app/page.tsx
export default function Home() {
  const { logs, isConnected } = useLogs();
  const [services, setServices] = useState<StatusResponse | null>(null);

  // Initial fetch on mount
  useEffect(() => {
    const fetchStatus = async () => {
      try {
        const data = await api.getStatus();
        setServices(data);
      } catch (e) {
        console.error("Failed to fetch status", e);
      }
    };
    fetchStatus();
  }, []);

  // Update from WebSocket status_update events
  useEffect(() => {
    if (logs.length > 0) {
      const lastLog = logs[logs.length - 1];
      if (lastLog.type === "status_update" && lastLog.details) {
        setServices(lastLog.details as StatusResponse);
      }
    }
  }, [logs]);

  return <StatusGrid services={services} />;
}
The status grid updates in two ways:
  1. Initial HTTP request on page load
  2. Real-time updates via WebSocket status_update events

Backend Status Endpoint

The /status endpoint executes SSH commands to check service health:
# src/api/routes.py
@router.get("/status")
def get_status():
    services_status = {}
    
    ssh = None
    try:
        ssh = SSHClient(
            hostname=config.SSH_HOST,
            port=config.SSH_PORT,
            username=config.SSH_USER,
            password=config.SSH_PASS
        )
    except Exception as e:
        # Return error status for all services if SSH fails
        for name, cfg in config.SERVICES.items():
            services_status[name] = {
                "status": "error",
                "details": f"SSH unavailable: {str(e)}",
                "type": cfg["type"]
            }
        return services_status
    
    try:
        for name, cfg in config.SERVICES.items():
            try:
                code, out, err = ssh.execute_command(cfg["check_command"])
                is_running = cfg["running_indicator"] in out
                services_status[name] = {
                    "status": "running" if is_running else "stopped",
                    "details": out.strip() if not is_running else "Service is active",
                    "type": cfg["type"]
                }
            except Exception as e:
                services_status[name] = {
                    "status": "error",
                    "details": str(e),
                    "type": cfg["type"]
                }
    finally:
        if ssh:
            ssh.close()
        
    return services_status

Service Status Types

Running

Service is active and responding correctly

Stopped

Service is not running or not responding

Error

Unable to check service status (SSH error, etc.)

Service Types and Icons

Each service displays an icon based on its type:
const ServiceTypeIcon = ({ type }: { type: string }) => {
  switch (type) {
    case 'database':
      return <Database className="w-4 h-4 text-purple-500" />;
    case 'web_server':
      return <Globe className="w-4 h-4 text-blue-500" />;
    default:
      return <Terminal className="w-4 h-4 text-orange-500" />;
  }
};

Supported Service Types

TypeIconExample Services
databaseDatabasePostgreSQL, MySQL, MongoDB
web_serverGlobeNginx, Apache, Caddy
customTerminalCustom scripts, cron jobs

Status Response Schema

The API returns this TypeScript interface:
export interface ServiceStatus {
  status: 'running' | 'stopped' | 'error';
  details: string;
  type: string;
}

export type StatusResponse = Record<string, ServiceStatus>;
Example response:
{
  "nginx": {
    "status": "running",
    "details": "Service is active",
    "type": "web_server"
  },
  "postgresql": {
    "status": "stopped",
    "details": "inactive (dead)",
    "type": "database"
  },
  "redis": {
    "status": "error",
    "details": "SSH unavailable: Connection refused",
    "type": "database"
  }
}

Responsive Grid Layout

The grid adapts to different screen sizes:
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
  {/* Service cards */}
</div>
Screen SizeColumnsClass
Mobile (< 768px)1grid-cols-1
Tablet (768px - 1024px)2md:grid-cols-2
Desktop (> 1024px)4lg:grid-cols-4

Loading State

While fetching initial data, the grid shows skeleton cards:
if (!services) {
  return (
    <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 animate-pulse">
      {[1, 2, 3, 4].map((i) => (
        <div key={i} className="h-32 rounded-xl bg-muted/50 border" />
      ))}
    </div>
  );
}
The skeleton UI uses the same grid layout as the actual cards, preventing layout shift when data loads.

Color Coding

Status badges use semantic colors:
const getStatusColor = (status: string) => {
  switch (status) {
    case 'running':
      return 'bg-green-500/10 text-green-500 border-green-500/20';
    case 'stopped':
      return 'bg-red-500/10 text-red-500 border-red-500/20';
    case 'error':
      return 'bg-red-500/10 text-red-500 border-red-500/20';
    default:
      return 'bg-gray-500/10 text-gray-400 border-gray-500/20';
  }
};

Green

Running - Service is healthy

Red

Stopped/Error - Requires attention

Gray

Unknown - Status cannot be determined

WebSocket Status Updates

The dashboard updates automatically when the agent detects changes:
useEffect(() => {
  if (logs.length > 0) {
    const lastLog = logs[logs.length - 1];
    if (lastLog.type === "status_update" && lastLog.details) {
      setTimeout(() => {
        setServices(lastLog.details as StatusResponse);
      }, 0);
    }
  }
}, [logs]);
The setTimeout prevents synchronous state updates during the WebSocket event handler.

Configuring Services

Services are defined in the backend configuration:
# config.yaml or environment variables
SERVICES:
  nginx:
    check_command: "systemctl status nginx"
    running_indicator: "active (running)"
    type: "web_server"
  postgresql:
    check_command: "systemctl status postgresql"
    running_indicator: "active (running)"
    type: "database"
See the Configuration Guide for details on adding services.

Error Scenarios

SSH Connection Failure: If SSH is unavailable, all services show an error status with the message “SSH unavailable: [error details]”.
Command Execution Failure: If a check command fails, that specific service shows an error status with the exception message.

System Metrics

The dashboard also displays overall metrics:
<div className="flex justify-between py-1">
  <span>Monitored Services</span>
  <span className="font-mono">
    {services ? Object.keys(services).length : '-'}
  </span>
</div>

Next Steps

Service Configuration

Learn how to add and configure services

SSH Setup

Configure SSH access for remote monitoring

Monitor Node

Understand how the agent checks service status

WebSocket API

Learn about real-time event distribution

Build docs developers (and LLMs) love