summaryrefslogtreecommitdiff
path: root/makima/frontend/src/components/directives/EventsTab.tsx
blob: 4dd739ad75b6e8c0ecf4e74b214d5c22ba55e2b6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import { useMemo } from "react";
import type { DirectiveWithProgress } from "../../lib/api";
import { useDirectiveEventSubscription } from "../../hooks/useDirectives";

export function EventsTab({ directive }: { directive: DirectiveWithProgress }) {
  // Subscribe to real-time events via SSE
  const { events: streamEvents, isConnected, error: sseError } = useDirectiveEventSubscription(directive.id);

  // Combine initial events with streamed events (avoiding duplicates)
  const allEvents = useMemo(() => {
    const eventMap = new Map();
    // Add initial events first
    directive.recentEvents.forEach((e) => eventMap.set(e.id, e));
    // Add streamed events (will override any duplicates)
    streamEvents.forEach((e) => eventMap.set(e.id, e));
    // Sort by created_at descending (most recent first)
    return Array.from(eventMap.values()).sort(
      (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
    );
  }, [directive.recentEvents, streamEvents]);

  return (
    <div className="space-y-4">
      {/* Connection status */}
      <div className="flex items-center justify-between text-[10px] font-mono">
        <div className="flex items-center gap-2">
          <span className={isConnected ? "text-green-400" : "text-[#556677]"}>
            {isConnected ? "\u25CF Live" : "\u25CB Connecting..."}
          </span>
          {sseError && <span className="text-red-400">{sseError}</span>}
        </div>
        <span className="text-[#556677]">{allEvents.length} events</span>
      </div>

      {/* Event list */}
      {allEvents.length === 0 ? (
        <div className="text-center py-8">
          <p className="font-mono text-sm text-[#556677]">No events yet</p>
        </div>
      ) : (
        <div className="space-y-2">
          {allEvents.map((event) => {
            const severityColors: Record<string, string> = {
              info: "text-[#75aafc]",
              warning: "text-yellow-400",
              error: "text-red-400",
              critical: "text-red-600",
            };
            const severityColor = severityColors[event.severity] || "text-[#556677]";

            return (
              <div
                key={event.id}
                className="p-3 bg-[rgba(117,170,252,0.02)] border border-[rgba(117,170,252,0.1)]"
              >
                <div className="flex items-center justify-between">
                  <div className="flex items-center gap-2">
                    <span className={`font-mono text-xs ${severityColor}`}>{event.eventType}</span>
                    <span className="font-mono text-[10px] text-[#556677]">{event.actorType}</span>
                  </div>
                  <span className="font-mono text-[10px] text-[#556677]">
                    {new Date(event.createdAt).toLocaleString()}
                  </span>
                </div>
                {event.eventData != null && (
                  <pre className="font-mono text-[10px] text-[#556677] mt-1 overflow-x-auto">
                    {JSON.stringify(event.eventData, null, 2)}
                  </pre>
                )}
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}