SPB Git forge

spb/fetcha

Public
11commits 1branches 0releases
1.5 MBsize
maindefault branch
16 days agolast push
TypeScript 97.5% SQL 1.4% Python 0.8%
9.1 KB · 187 lines tsx
Raw Blame History
1import { requireAdmin } from "@/lib/session";2import { internalApi, InternalApiError } from "@/lib/api";3import { getProviderConfigs, getProviderHealthHistory, getProviderStats, rangeStart } from "@/lib/queries/admin";4import { formatBytes, formatMs, formatNumber, formatPercent, formatUsd } from "@/lib/format";5import { PageHeader } from "@/components/ui/page-header";6import { Alert } from "@/components/ui/alert";7import { Badge, StatusBadge } from "@/components/ui/badge";8import { Stat, StatGrid } from "@/components/ui/stat";9import { Table, TableBody, TableCell, TableEmpty, TableHead, TableHeader, TableRow } from "@/components/ui/table";10import { DateCell, NetworkBadge, Panel, ProviderBadge, numCell } from "@/components/admin/primitives";11import { CircuitList, ProbeButton, ProviderConfigForm, ResetAllCircuitsButton } from "@/components/admin/provider-panel";1213export const dynamic = "force-dynamic";1415type ProvidersPayload = Awaited<ReturnType<typeof internalApi.providers>>;1617export default async function AdminProvidersPage() {18  await requireAdmin();19  let live: ProvidersPayload | null = null;20  let apiError: string | null = null;21  try {22    live = await internalApi.providers();23  } catch (e) {24    apiError = e instanceof InternalApiError ? `${e.code}: ${e.message}` : (e as Error).message;25  }26  const [configs, stats, history] = await Promise.all([getProviderConfigs(), getProviderStats(rangeStart("24h")), getProviderHealthHistory(50)]);27  const configById = new Map(configs.map((c) => [c.id, c]));28  const statById = new Map(stats.map((s) => [s.provider, s]));2930  // Providers known to the API, plus any config rows the API does not report (e.g. disabled ones).31  const providerIds = Array.from(new Set([...(live?.providers.map((p) => p.id) ?? []), ...configs.map((c) => c.id)]));3233  return (34    <>35      <PageHeader36        eyebrow="Upstream"37        title="Providers"38        description="Configuration, health, circuit breakers and 24-hour unit economics per upstream provider. This page is the only place provider names appear."39        actions={40          <>41            <ResetAllCircuitsButton />42            <ProbeButton />43          </>44        }45      />4647      {apiError ? (48        <Alert variant="warning" title="API service unreachable" className="mb-4">49          Live provider status could not be loaded ({apiError}). Configuration and database statistics are still shown; saving a config will retry the reload.50        </Alert>51      ) : (52        <p className="mb-4 text-[12.5px] text-fg-muted">53          Available networks right now: {live!.available_networks.length ? live!.available_networks.map((n) => <NetworkBadge key={n} network={n} />) : <span>none</span>}54        </p>55      )}5657      <div className="space-y-6">58        {providerIds.length === 0 ? <Alert variant="info">No providers registered.</Alert> : null}59        {providerIds.map((id) => {60          const p = live?.providers.find((x) => x.id === id) ?? null;61          const cfg = configById.get(id) ?? null;62          const st = statById.get(id) ?? null;63          const label = cfg?.label ?? p?.label ?? id;64          const enabled = cfg?.enabled ?? true;65          return (66            <Panel67              key={id}68              title={69                <span className="flex flex-wrap items-center gap-2">70                  <span className="text-[15px]">{label}</span>71                  <ProviderBadge id={id} />72                  {p ? p.configured ? <Badge variant="success" dot>configured</Badge> : <Badge variant="danger" dot>credentials missing</Badge> : <Badge variant="outline">not reported by API</Badge>}73                  {enabled ? <Badge variant="accent">enabled</Badge> : <Badge variant="outline">disabled</Badge>}74                </span>75              }76              description={p ? `Networks: ${p.networks.join(", ") || "none"}` : cfg ? `Networks (config): ${cfg.networks.join(", ")}` : undefined}77              bodyClassName="p-0"78            >79              <div className="border-b border-border">80                <StatGrid cols={6} className="rounded-none border-0 shadow-none">81                  <Stat label="Attempts 24h" value={formatNumber(st?.requests ?? 0)} />82                  <Stat label="Success" value={formatPercent(st?.successRate ?? null)} />83                  <Stat label="Blocked" value={formatPercent(st?.blockedRate ?? null)} />84                  <Stat label="Avg latency" value={formatMs(st?.avgLatency ?? null)} />85                  <Stat label="Bytes · cost" value={`${formatBytes(st?.bytes ?? 0)} · ${formatUsd(st?.cost ?? 0, true)}`} />86                  <Stat label="Cost / success" value={<span className="text-accent">{formatUsd(st?.costPerSuccess ?? null, true)}</span>} hint="key metric" />87                </StatGrid>88              </div>89              <div className="grid gap-6 p-4 lg:grid-cols-2">90                <div>91                  <h3 className="mb-2 text-[12px] font-semibold uppercase tracking-wide text-fg-subtle">Configuration</h3>92                  <ProviderConfigForm93                    id={id}94                    label={label}95                    enabled={enabled}96                    networks={cfg?.networks ?? []}97                    pricePerGbUsd={cfg?.pricePerGbUsd ?? {}}98                    weight={cfg?.weight ?? 1}99                    maxConcurrency={cfg?.maxConcurrency ?? 200}100                    notes={cfg?.notes ?? null}101                    livePrices={p?.prices ?? {}}102                    liveNetworks={p?.networks ?? []}103                    hasConfigRow={Boolean(cfg)}104                  />105                </div>106                <div className="grid gap-5">107                  <div>108                    <h3 className="mb-2 text-[12px] font-semibold uppercase tracking-wide text-fg-subtle">Latest health per network</h3>109                    {p?.health.length ? (110                      <ul className="divide-y divide-border rounded-md border border-border">111                        {p.health.map((h) => (112                          <li key={h.network} className="flex flex-wrap items-center gap-3 px-3 py-2 text-[12.5px]">113                            <NetworkBadge network={h.network} />114                            <StatusBadge status={h.status} />115                            <span className="tabular font-mono text-fg-muted">{formatMs(h.latency_ms)}</span>116                            <span className="text-fg-subtle">117                              <DateCell value={h.checked_at} />118                            </span>119                            {h.detail ? (120                              <span className="w-full truncate font-mono text-[11.5px] text-fg-subtle" title={h.detail}>121                                {h.detail}122                              </span>123                            ) : null}124                          </li>125                        ))}126                      </ul>127                    ) : (128                      <p className="text-[12.5px] text-fg-muted">No health check in the last 24 hours. Run a probe.</p>129                    )}130                  </div>131                  <div>132                    <h3 className="mb-2 text-[12px] font-semibold uppercase tracking-wide text-fg-subtle">Circuit breakers</h3>133                    <CircuitList circuits={p?.circuits ?? []} />134                  </div>135                </div>136              </div>137            </Panel>138          );139        })}140      </div>141142      <Panel title="Health history" description="Last 50 probe results across providers." flush className="mt-6">143        <Table>144          <TableHeader>145            <TableRow>146              <TableHead>Checked</TableHead>147              <TableHead>Provider</TableHead>148              <TableHead>Network</TableHead>149              <TableHead>Status</TableHead>150              <TableHead className="text-right">Latency</TableHead>151              <TableHead className="text-right">Success rate</TableHead>152              <TableHead>Detail</TableHead>153            </TableRow>154          </TableHeader>155          <TableBody>156            {history.length === 0 ? (157              <TableEmpty colSpan={7}>No probes recorded yet.</TableEmpty>158            ) : (159              history.map((h) => (160                <TableRow key={h.id}>161                  <TableCell>162                    <DateCell value={h.checkedAt} relative={false} />163                  </TableCell>164                  <TableCell>165                    <ProviderBadge id={h.provider} />166                  </TableCell>167                  <TableCell>168                    <NetworkBadge network={h.network} />169                  </TableCell>170                  <TableCell>171                    <StatusBadge status={h.status} />172                  </TableCell>173                  <TableCell className={numCell}>{formatMs(h.latencyMs)}</TableCell>174                  <TableCell className={numCell}>{h.successRate !== null ? formatPercent(h.successRate * (h.successRate <= 1 ? 100 : 1)) : "—"}</TableCell>175                  <TableCell className="max-w-[360px] truncate font-mono text-[11.5px] text-fg-muted" title={h.detail ?? undefined}>176                    {h.detail ?? "—"}177                  </TableCell>178                </TableRow>179              ))180            )}181          </TableBody>182        </Table>183      </Panel>184    </>185  );186}187