spb/internetpressure
Public
TypeScript 36.3%
Python 31.8%
Go 18%
JavaScript 9.8%
Shell 1.9%
SQL 1.4%
CSS 0.5%
1'use client';23import { useState } from 'react';4import { PNum } from '@/components/ui/primitives';5import { adminFetch } from '@/lib/admin-fetch';6import { fmtInt, fmtPct } from '@/lib/format';7import type { AdminTarget } from '@/lib/types';8import { AdminPage, ErrorNote, Field, Panel, Toast, btnCls, btnPrimary, inputCls, useAdmin } from './shared';910const EMPTY: Partial<AdminTarget> = { target_id: '', name: '', hostname: '', category: 'cloud', provider: '', service_id: '', country: 'US', region: 'na-east', importance: 3, tier: 2 };11const CATEGORIES = ['dns', 'cdn', 'cloud', 'search', 'messaging', 'social', 'finance', 'government', 'news', 'developer', 'ai', 'streaming', 'commerce', 'infrastructure'];1213export function Targets() {14 const { data, err, reload } = useAdmin<{ targets: AdminTarget[] }>('/targets');15 const [q, setQ] = useState('');16 const [editing, setEditing] = useState<Partial<AdminTarget> | null>(null);17 const [isNew, setIsNew] = useState(false);18 const [msg, setMsg] = useState<string | null>(null);19 const [busy, setBusy] = useState(false);20 const [actErr, setActErr] = useState<string | null>(null);2122 const rows = (data?.targets ?? []).filter((t) => !q || t.hostname.includes(q) || t.name.toLowerCase().includes(q.toLowerCase()) || t.target_id.includes(q));2324 const run = async (fn: () => Promise<unknown>, ok: string) => {25 setBusy(true);26 setActErr(null);27 try {28 await fn();29 setMsg(ok);30 setEditing(null);31 reload();32 } catch (e) {33 setActErr(String(e));34 } finally {35 setBusy(false);36 setTimeout(() => setMsg(null), 3000);37 }38 };3940 return (41 <AdminPage42 title="Targets"43 desc="Registry of measured endpoints. Frequencies come from tiers (scheduler); importance weights the aggregates. Changes apply on the next config refresh (≤ 5 min on probes)."44 right={45 <div className="flex gap-2">46 <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="filter…" className={inputCls} aria-label="Filter" />47 <button48 type="button"49 className={btnPrimary}50 onClick={() => {51 setEditing({ ...EMPTY });52 setIsNew(true);53 }}54 >55 + target56 </button>57 </div>58 }59 >60 <ErrorNote err={err ?? actErr} />61 <Toast msg={msg} />62 {editing && (63 <Panel title={isNew ? 'Create target' : `Edit ${editing.target_id}`} className="mb-3">64 <form65 className="grid grid-cols-2 gap-3 md:grid-cols-5"66 onSubmit={(e) => {67 e.preventDefault();68 const body = { ...editing, importance: Number(editing.importance), tier: Number(editing.tier) };69 void run(() => (isNew ? adminFetch('/targets', { method: 'POST', body }) : adminFetch(`/targets/${encodeURIComponent(editing.target_id!)}`, { method: 'PATCH', body })), isNew ? 'Target created' : 'Target updated');70 }}71 >72 <Field label="target_id">73 <input required disabled={!isNew} value={editing.target_id ?? ''} onChange={(e) => setEditing({ ...editing, target_id: e.target.value })} className={`${inputCls} num w-full`} />74 </Field>75 <Field label="name">76 <input required value={editing.name ?? ''} onChange={(e) => setEditing({ ...editing, name: e.target.value })} className={`${inputCls} w-full`} />77 </Field>78 <Field label="hostname">79 <input required value={editing.hostname ?? ''} onChange={(e) => setEditing({ ...editing, hostname: e.target.value })} className={`${inputCls} num w-full`} />80 </Field>81 <Field label="category">82 <select value={editing.category ?? ''} onChange={(e) => setEditing({ ...editing, category: e.target.value })} className={`${inputCls} w-full`}>83 {CATEGORIES.map((c) => (84 <option key={c}>{c}</option>85 ))}86 </select>87 </Field>88 <Field label="provider">89 <input value={editing.provider ?? ''} onChange={(e) => setEditing({ ...editing, provider: e.target.value })} className={`${inputCls} w-full`} />90 </Field>91 <Field label="service_id">92 <input value={editing.service_id ?? ''} onChange={(e) => setEditing({ ...editing, service_id: e.target.value })} className={`${inputCls} num w-full`} />93 </Field>94 <Field label="country">95 <input value={editing.country ?? ''} maxLength={2} onChange={(e) => setEditing({ ...editing, country: e.target.value.toUpperCase() })} className={`${inputCls} num w-full`} />96 </Field>97 <Field label="region">98 <input value={editing.region ?? ''} onChange={(e) => setEditing({ ...editing, region: e.target.value })} className={`${inputCls} num w-full`} />99 </Field>100 <Field label="importance 1–5">101 <input type="number" min={1} max={5} value={editing.importance ?? 3} onChange={(e) => setEditing({ ...editing, importance: Number(e.target.value) })} className={`${inputCls} num w-full`} />102 </Field>103 <Field label="tier 1–3">104 <input type="number" min={1} max={3} value={editing.tier ?? 2} onChange={(e) => setEditing({ ...editing, tier: Number(e.target.value) })} className={`${inputCls} num w-full`} />105 </Field>106 <div className="col-span-2 flex gap-2 md:col-span-5">107 <button type="submit" className={btnPrimary} disabled={busy}>108 {isNew ? 'Create' : 'Save'}109 </button>110 <button type="button" className={btnCls} onClick={() => setEditing(null)}>111 Cancel112 </button>113 </div>114 </form>115 </Panel>116 )}117 <div className="scroll-x">118 <table className="tbl">119 <thead>120 <tr>121 <th>Target</th>122 <th>Hostname</th>123 <th>Category</th>124 <th>Anchor</th>125 <th className="r">Imp.</th>126 <th className="r">Tier</th>127 <th className="r">Pressure</th>128 <th className="r">OK 1h</th>129 <th>Enabled</th>130 <th></th>131 </tr>132 </thead>133 <tbody>134 {rows.map((t) => (135 <tr key={t.target_id} className={t.enabled === false ? 'opacity-50' : ''}>136 <td>137 <span className="text-ink">{t.name}</span> <span className="num text-[10.5px] text-ink-3">{t.target_id}</span>138 </td>139 <td className="num text-ink-2">{t.hostname}</td>140 <td className="text-ink-2">{t.category}</td>141 <td className="num text-ink-2">142 {t.country} · {t.region}143 </td>144 <td className="num r">{t.importance}</td>145 <td className="num r">{t.tier}</td>146 <td className="r">147 <PNum value={t.pressure} />148 </td>149 <td className="num r">{fmtPct(t.ok_ratio_1h, 1)}</td>150 <td>151 <button type="button" className="text-[11px] uppercase tracking-[0.1em]" style={{ color: t.enabled === false ? 'var(--ink-3)' : 'var(--ok)' }} onClick={() => void run(() => adminFetch(`/targets/${encodeURIComponent(t.target_id)}`, { method: 'PATCH', body: { enabled: t.enabled === false } }), t.enabled === false ? 'Enabled' : 'Disabled')}>152 {t.enabled === false ? 'disabled' : 'enabled'}153 </button>154 </td>155 <td className="r">156 <div className="flex justify-end gap-1">157 <button158 type="button"159 className="text-[11px] text-ink-2 hover:text-ink"160 onClick={() => {161 setEditing({ ...t });162 setIsNew(false);163 }}164 >165 edit166 </button>167 <button168 type="button"169 className="text-[11px] text-bad/80 hover:text-bad"170 onClick={() => {171 if (confirm(`Delete target ${t.target_id}? History is kept; the target stops being measured.`)) void run(() => adminFetch(`/targets/${encodeURIComponent(t.target_id)}`, { method: 'DELETE' }), 'Target deleted');172 }}173 >174 delete175 </button>176 </div>177 </td>178 </tr>179 ))}180 </tbody>181 </table>182 </div>183 <p className="num mt-2 text-[11px] text-ink-3">{fmtInt(rows.length)} targets</p>184 </AdminPage>185 );186}187