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%
4.1 KB · 87 lines tsx
Raw Blame History
1'use client';23import { useState } from 'react';4import { adminFetch } from '@/lib/admin-fetch';5import type { AdminTarget } from '@/lib/types';6import { AdminPage, ErrorNote, Field, Panel, Toast, btnPrimary, inputCls, useAdmin } from './shared';78export function Boost() {9  const { data } = useAdmin<{ targets: AdminTarget[] }>('/targets');10  const [q, setQ] = useState('');11  const [sel, setSel] = useState<Set<string>>(new Set());12  const [factor, setFactor] = useState(0.5);13  const [seconds, setSeconds] = useState(900);14  const [msg, setMsg] = useState<string | null>(null);15  const [err, setErr] = useState<string | null>(null);16  const [busy, setBusy] = useState(false);17  const list = (data?.targets ?? []).filter((t) => !q || t.hostname.includes(q) || t.name.toLowerCase().includes(q.toLowerCase()) || t.service_id.includes(q));18  const toggle = (id: string) =>19    setSel((s) => {20      const n = new Set(s);21      if (n.has(id)) n.delete(id);22      else n.add(id);23      return n;24    });25  return (26    <AdminPage title="Sampling boost" desc="Temporarily multiply the check interval of selected targets by a factor < 1 (0.5 = twice as often) for a limited time. Pushed to probes at their next config refresh; never exceeds the ethical traffic budget.">27      <div className="grid grid-cols-[minmax(0,1fr)] gap-3 lg:grid-cols-[minmax(0,1fr)_300px]">28        <Panel title="Targets" right={<input value={q} onChange={(e) => setQ(e.target.value)} placeholder="filter…" className={inputCls} aria-label="Filter" />}>29          <div className="flex gap-2 text-[11px]">30            <button type="button" className="text-ink-2 hover:text-ink" onClick={() => setSel(new Set(list.map((t) => t.target_id)))}>31              select shown ({list.length})32            </button>33            <button type="button" className="text-ink-2 hover:text-ink" onClick={() => setSel(new Set())}>34              clear35            </button>36          </div>37          <ul className="mt-2 max-h-[60vh] overflow-auto divide-y divide-line">38            {list.map((t) => (39              <li key={t.target_id}>40                <label className="flex cursor-pointer items-center gap-3 py-1.5 text-[12.5px]">41                  <input type="checkbox" checked={sel.has(t.target_id)} onChange={() => toggle(t.target_id)} className="accent-[var(--accent)]" />42                  <span className="text-ink">{t.name}</span>43                  <span className="num text-ink-3">{t.hostname}</span>44                  <span className="num ml-auto text-ink-3">tier {t.tier}</span>45                </label>46              </li>47            ))}48          </ul>49        </Panel>50        <Panel title="Boost">51          <Field label="factor (interval multiplier)">52            <input type="number" step="0.1" min={0.1} max={1} value={factor} onChange={(e) => setFactor(Number(e.target.value))} className={`${inputCls} num w-full`} />53          </Field>54          <div className="mt-3">55            <Field label="duration (seconds)">56              <input type="number" step="60" min={60} max={7200} value={seconds} onChange={(e) => setSeconds(Number(e.target.value))} className={`${inputCls} num w-full`} />57            </Field>58          </div>59          <p className="num mt-3 text-[11px] text-ink-3">60            {sel.size} targets · ×{factor} for {Math.round(seconds / 60)} min61          </p>62          <button63            type="button"64            className={`${btnPrimary} mt-3 w-full`}65            disabled={!sel.size || busy || factor <= 0 || factor > 1}66            onClick={() => {67              setBusy(true);68              setErr(null);69              adminFetch('/boost', { method: 'POST', body: { targets: [...sel], factor, seconds } })70                .then(() => setMsg(`Boost pushed for ${sel.size} targets`))71                .catch((e: unknown) => setErr(String(e)))72                .finally(() => {73                  setBusy(false);74                  setTimeout(() => setMsg(null), 4000);75                });76            }}77          >78            Push boost79          </button>80          <Toast msg={msg} />81          <ErrorNote err={err} />82        </Panel>83      </div>84    </AdminPage>85  );86}87