adk-gateway 1.0.0

Multi-channel AI gateway for adk-rust agents — Telegram, Slack, WhatsApp, Discord, Matrix + control panel
import type { HealthComponent, HealthEvent } from '../types';

interface Props {
  component: HealthComponent;
  events: HealthEvent[];
  onClose: () => void;
}

export default function HealthTimeline({ component, events, onClose }: Props) {
  const componentEvents = events.filter((e) => e.component === component.name);
  const statusColor =
    component.status === 'healthy'
      ? 'bg-green-500'
      : component.status === 'degraded'
        ? 'bg-yellow-500'
        : 'bg-red-500';

  return (
    <div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4">
      <div className="bg-white rounded-xl shadow-xl max-w-lg w-full p-6 max-h-[80vh] overflow-y-auto">
        <div className="flex items-center justify-between mb-4">
          <div className="flex items-center gap-3">
            <span className={`inline-block w-3 h-3 rounded-full ${statusColor}`} />
            <h3 className="text-lg font-semibold">{component.name}</h3>
          </div>
          <button
            onClick={onClose}
            className="text-gray-400 hover:text-gray-600 text-xl leading-none"
            aria-label="Close"
          >
            ×
          </button>
        </div>

        {/* Current Status */}
        <div className="bg-gray-50 rounded-lg p-4 mb-4 space-y-2">
          <div className="flex justify-between text-sm">
            <span className="text-gray-600">Status</span>
            <span className="font-medium capitalize">{component.status}</span>
          </div>
          <div className="flex justify-between text-sm">
            <span className="text-gray-600">Last Check</span>
            <span className="font-mono text-xs">{component.last_check}</span>
          </div>
          <div className="flex justify-between text-sm">
            <span className="text-gray-600">Consecutive Failures</span>
            <span className="font-medium">{component.consecutive_failures}</span>
          </div>
          {component.latency_ms !== undefined && (
            <div className="flex justify-between text-sm">
              <span className="text-gray-600">Latency</span>
              <span className="font-mono text-xs">{component.latency_ms}ms</span>
            </div>
          )}
        </div>

        {/* Timeline */}
        <h4 className="text-sm font-semibold text-gray-700 mb-3">Event History</h4>
        {componentEvents.length === 0 ? (
          <div className="text-sm text-gray-400 text-center py-4">No events recorded</div>
        ) : (
          <div className="space-y-3">
            {componentEvents.map((event, i) => (
              <div key={i} className="flex items-start gap-3">
                <div className="flex-shrink-0 mt-1">
                  <span
                    className={`inline-block w-2.5 h-2.5 rounded-full ${
                      event.event === 'alert' ? 'bg-red-500' : 'bg-green-500'
                    }`}
                  />
                </div>
                <div className="flex-1 min-w-0">
                  <div className="flex items-center gap-2">
                    <span className={`text-xs font-semibold uppercase ${
                      event.event === 'alert' ? 'text-red-700' : 'text-green-700'
                    }`}>
                      {event.event}
                    </span>
                    <span className="text-xs text-gray-400 font-mono">{event.timestamp}</span>
                  </div>
                  <p className="text-sm text-gray-600 mt-0.5">{event.message}</p>
                </div>
              </div>
            ))}
          </div>
        )}

        <div className="mt-6 flex justify-end">
          <button
            onClick={onClose}
            className="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200"
          >
            Close
          </button>
        </div>
      </div>
    </div>
  );
}