SPB Git forge

spb/market-atlas

Public
12commits 1branches 0releases
1.1 MBsize
maindefault branch
10 days agolast push
TypeScript 96.7% SQL 1.6% CSS 0.8% JavaScript 0.5%
4.2 KB · 77 lines tsx
Raw Blame History
1import type { Metadata } from "next";2import Link from "next/link";3import { RelativeTime } from "@/components/ui/freshness";4import { Page, PageHeader } from "@/components/ui/section";5import { RightsBadge, StatusBadge } from "@/components/ui/status-badge";6import { api } from "@/lib/api";7import { formatDuration } from "@/lib/format";8import type { ConnectorPublic } from "@/lib/types";910export const metadata: Metadata = { title: "Connectors", description: "Operational status of every Market Atlas connector: type, rights, message rate, latency, coverage and reliability.", alternates: { canonical: "/connectors" } };11export const dynamic = "force-dynamic";1213export default async function ConnectorsPage() {14  const rows = await api<ConnectorPublic[]>("/v1/connectors");15  const healthy = rows.filter((r) => r.status === "HEALTHY").length;16  return (17    <Page wide>18      <PageHeader kicker="Operations" title="Connectors" lead={`${rows.length} connectors, ${healthy} healthy. A connector is one technical integration of a source (WebSocket feed, JSON endpoint, official file, RSS, HTML page…). The framework proves it generalizes across ${new Set(rows.map((r) => r.source_type)).size} transport types.`} actions={<Link href="/data-health" className="text-sm text-accent hover:underline">Data health →</Link>} />19      <div className="overflow-x-auto rounded-md border border-rule bg-surface">20        <table className="table-dense">21          <thead>22            <tr>23              <th>Connector</th>24              <th>Status</th>25              <th>Type</th>26              <th>Rights</th>27              <th className="hidden md:table-cell">Real time</th>28              <th className="text-right">Msg / min</th>29              <th className="text-right">Total</th>30              <th className="hidden text-right lg:table-cell">p50 / p95 latency</th>31              <th className="hidden text-right lg:table-cell">Parse</th>32              <th className="text-right">Instruments</th>33              <th className="text-right">Score</th>34              <th>Last message</th>35              <th className="hidden xl:table-cell">Next poll</th>36            </tr>37          </thead>38          <tbody>39            {rows.map((r) => (40              <tr key={r.id}>41                <td className="max-w-[340px] whitespace-normal">42                  <div className="font-medium">{r.name}</div>43                  <div className="mono text-[11px] text-ink-3">44                    {r.id} · v{r.version} ·{" "}45                    <Link href={`/sources#${r.source_id}`} className="hover:underline">46                      {r.source_id}47                    </Link>48                  </div>49                  <div className="mt-0.5 text-xs text-ink-2">{r.description}</div>50                </td>51                <td>52                  <StatusBadge status={r.status} />53                </td>54                <td className="mono text-xs">{r.source_type}</td>55                <td>56                  <RightsBadge status={r.rights_status} />57                </td>58                <td className="hidden text-xs text-ink-2 md:table-cell">{r.realtime_status.replace(/_/g, " ").toLowerCase()}</td>59                <td className="num">{r.messages_1m}</td>60                <td className="num">{r.messages_total.toLocaleString("en-US")}</td>61                <td className="num hidden lg:table-cell">62                  {r.median_latency_ms == null ? "—" : formatDuration(r.median_latency_ms)} / {r.p95_latency_ms == null ? "—" : formatDuration(r.p95_latency_ms)}63                </td>64                <td className="num hidden lg:table-cell">{r.parse_success_rate == null ? "—" : `${Math.round(r.parse_success_rate * 100)}%`}</td>65                <td className="num">{r.instruments_covered}</td>66                <td className="num">{r.reliability_score ?? "—"}</td>67                <td className="text-xs text-ink-3">{r.last_message_at ? <RelativeTime value={r.last_message_at} /> : "—"}</td>68                <td className="hidden text-xs text-ink-3 xl:table-cell">{r.next_poll_at ? <RelativeTime value={r.next_poll_at} /> : r.supports_streaming ? "streaming" : "—"}</td>69              </tr>70            ))}71          </tbody>72        </table>73      </div>74    </Page>75  );76}77