// Shared UI — refined monochrome
const { useState, useEffect, useCallback } = React;
const ToastCtx = React.createContext({ push: () => {} });
function ToastProvider({ children }) {
const [items, setItems] = useState([]);
const push = useCallback((msg) => {
const id = Math.random().toString(36).slice(2);
setItems(s => [...s, { id, msg }]);
setTimeout(() => setItems(s => s.filter(i => i.id !== id)), 2600);
}, []);
return (
{children}
{items.map(t => (
{t.msg}
))}
);
}
const useToast = () => React.useContext(ToastCtx).push;
function Modal({ open, onClose, title, children, footer }) {
if (!open) return null;
return (
e.stopPropagation()}>
{title}
{children}
{footer &&
{footer}
}
);
}
function Drawer({ open, onClose, title, children }) {
if (!open) return null;
return (
<>
>
);
}
function Toggle({ on, onChange, label }) {
return (
onChange(!on)}>
{label &&
{label}
}
);
}
function Seg({ options, value, onChange }) {
return (
{options.map(o => (
))}
);
}
function RegionPill({ code, residential, flag }) {
// derive flag from code prefix if not given
const baseCode = code?.replace(/-RES$/, "");
const flagEmoji = flag || window.PG?.regionFlag?.(baseCode) || null;
return (
{flagEmoji && {flagEmoji}}
{code}
);
}
function StatusDot({ alive, enabled, fail }) {
if (!enabled) return disabled;
if (!alive) return offline;
if (fail > 0) return flaky;
return live;
}
function LatencyBar({ ms }) {
if (!ms || ms <= 0) return —;
const pct = Math.min(100, (ms / 500) * 100);
let tier = "mid";
if (ms < 100) tier = "fast";
else if (ms < 250) tier = "mid";
else if (ms < 500) tier = "slow";
else tier = "bad";
return (
{ms}ms
);
}
// Smooth line + soft area glow
function Sparkline({ data, width = 240, height = 56 }) {
if (!data || !data.length) return null;
const max = Math.max(...data, 1);
const min = Math.min(...data, 0);
const span = max - min || 1;
const dx = width / (data.length - 1 || 1);
const pts = data.map((v, i) => [i * dx, height - ((v - min) / span) * (height - 6) - 3]);
const line = pts.map(([x,y], i) => `${i?'L':'M'}${x.toFixed(1)} ${y.toFixed(1)}`).join(' ');
const fill = `${line} L${width} ${height} L0 ${height} Z`;
const id = "sg" + Math.random().toString(36).slice(2,8);
return (
);
}
// Bars — pure tonal
function BarChart({ series, width = 540, height = 80 }) {
if (!series?.length) return null;
const max = Math.max(...series.map(s => s.total), 1);
const bw = width / series.length;
return (
);
}
function fmtBytes(n) {
if (n < 1024) return `${n} B`;
if (n < 1024*1024) return `${(n/1024).toFixed(1)} KB`;
return `${(n/1024/1024).toFixed(2)} MB`;
}
function fmtAgo(ts) {
const sec = Math.round((Date.now() - ts) / 1000);
if (sec < 60) return `${sec}s`;
if (sec < 3600) return `${Math.round(sec/60)}m`;
return `${Math.round(sec/3600)}h`;
}
function fmtUTC8(ts) {
const d = new Date(Number(ts || Date.now()) + 8 * 60 * 60 * 1000);
const pad = n => String(n).padStart(2, "0");
return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())} UTC+8`;
}
// Status code rendered tonally — no color, only weight
function StatusCode({ code }) {
const ok = code >= 200 && code < 400;
return {code || "ERR"};
}
function RequestDetailContent({ request }) {
if (!request) return null;
const replayRegion = request.region || (request.group || "").replace(/-RES$/, "");
return (
{request.method}
{request.type === "tunnel" && WS tunnel}
{request.template && template}
Time
{fmtUTC8(request.ts)}
URL
{request.url}
Strategy
{request.strategy}
Pool
{request.pool}
Node
{request.node}
TLS
{request.tls || "default"}
Duration
{request.duration_ms} ms
Bytes
{fmtBytes(request.bytes)}
{request.error && <>
Error
{request.error}
>}
Replay payload
{`POST /proxy
Authorization: Bearer
{
"url": "${request.url}",
"method": "${request.method}",
"egress": {
"region": "${replayRegion}",
"strategy": "${request.strategy}",
"residential": ${request.residential},
"tls_fingerprint": "${request.tls}"
}
}`}
);
}
window.UI = { ToastProvider, useToast, Modal, Drawer, Toggle, Seg, RegionPill, StatusDot, LatencyBar, Sparkline, BarChart, fmtBytes, fmtAgo, fmtUTC8, StatusCode, RequestDetailContent };