SPB Git forge
15commits 1branches 0releases
29.7 MBsize
maindefault branch
10 days agolast push
TypeScript 36.3% Python 31.8% Go 18% JavaScript 9.8% Shell 1.9% SQL 1.4% CSS 0.5%
2.8 KB · 67 lines tsx
Raw Blame History
1'use client';23import { useState } from 'react';4import { fmtInt } from '@/lib/format';5import type { AdminRaw } from '@/lib/types';6import { AdminPage, ErrorNote, Field, inputCls, useAdmin } from './shared';78const TABLES = ['measurements', 'traceroutes', 'bgp_events', 'bgp_stats', 'pressure_history', 'signal_features', 'probe_health'];910export function Raw() {11  const [table, setTable] = useState('measurements');12  const [probe, setProbe] = useState('');13  const [target, setTarget] = useState('');14  const [limit, setLimit] = useState(100);15  const { data, err, loading } = useAdmin<AdminRaw>('/raw', { table, probe_id: probe || undefined, target_id: target || undefined, limit });16  return (17    <AdminPage title="Raw explorer" desc="Latest rows of a ClickHouse table, newest first. Read-only, capped at 200 rows.">18      <div className="grid grid-cols-2 gap-3 md:grid-cols-5">19        <Field label="table">20          <select value={table} onChange={(e) => setTable(e.target.value)} className={`${inputCls} num w-full`}>21            {TABLES.map((t) => (22              <option key={t}>{t}</option>23            ))}24          </select>25        </Field>26        <Field label="probe_id">27          <input value={probe} onChange={(e) => setProbe(e.target.value)} className={`${inputCls} num w-full`} />28        </Field>29        <Field label="target_id">30          <input value={target} onChange={(e) => setTarget(e.target.value)} className={`${inputCls} num w-full`} />31        </Field>32        <Field label="limit">33          <input type="number" min={1} max={200} value={limit} onChange={(e) => setLimit(Math.min(200, Math.max(1, Number(e.target.value) || 1)))} className={`${inputCls} num w-full`} />34        </Field>35        <div className="num flex items-end text-[11px] text-ink-3">{loading ? 'loading…' : data ? `${fmtInt(data.rows.length)} rows` : ''}</div>36      </div>37      <ErrorNote err={err} />38      {data && (39        <div className="scroll-x mt-3 max-h-[70vh] overflow-auto rounded-[4px] border border-line">40          <table className="tbl text-[11.5px]">41            <thead className="sticky top-0 bg-bg">42              <tr>43                {data.columns.map((c) => (44                  <th key={c} className="num">45                    {c}46                  </th>47                ))}48              </tr>49            </thead>50            <tbody>51              {data.rows.map((r, i) => (52                <tr key={i}>53                  {r.map((v, j) => (54                    <td key={j} className="num max-w-[280px] truncate" title={String(v)}>55                      {v === null || v === undefined ? <span className="text-ink-3">∅</span> : typeof v === 'boolean' ? (v ? 'true' : 'false') : String(v)}56                    </td>57                  ))}58                </tr>59              ))}60            </tbody>61          </table>62        </div>63      )}64    </AdminPage>65  );66}67