import { createResource, createSignal, For, Show } from "solid-js"; import { api } from "../lib/api"; import { EmptyState, Skeleton } from "../components/ui"; const FIELDS = ["score", "broker_score", "foreign_net", "volume_mult", "composite"]; const OPS = [">=", "<=", ">", "<", "=="]; type Cond = { field: string; op: string; value: number }; export default function Alerts() { const [alerts, { refetch }] = createResource(() => api.alerts()); const [events, { refetch: refetchEv }] = createResource(() => api.alertEvents("2000-01-01").then((r) => r.events.slice(0, 30))); const [dests, { refetch: refetchDests }] = createResource(() => api.destinations()); const [name, setName] = createSignal("accumulation watcher"); const [field, setField] = createSignal("score"); const [op, setOp] = createSignal(">="); const [value, setValue] = createSignal(60); const [conds, setConds] = createSignal([]); const [channels, setChannels] = createSignal(""); const [busy, setBusy] = createSignal(false); const [dkind, setDkind] = createSignal("telegram"); const [dlabel, setDlabel] = createSignal(""); const [dbot, setDbot] = createSignal(""); const [dchat, setDchat] = createSignal(""); const [dhook, setDhook] = createSignal(""); const [derr, setDerr] = createSignal(""); const rulePreview = () => JSON.stringify(conds().length ? { all: conds() } : { all: true }); function addCond() { setConds((p) => [...p, { field: field(), op: op(), value: value() }]); } async function create() { setBusy(true); try { await api.createAlert({ name: name(), rule: JSON.parse(rulePreview()), channels: channels().split(",").map((c) => c.trim()).filter(Boolean), }); setConds([]); refetch(); } finally { setBusy(false); } } async function del(id: number) { await api.deleteAlert(id); refetch(); } async function addDest() { setDerr(""); try { const body: Record = dkind() === "telegram" ? { kind: "telegram", label: dlabel().trim(), bot_token: dbot().trim(), chat_id: dchat().trim() } : { kind: "discord", label: dlabel().trim(), webhook_url: dhook().trim() }; await api.createDestination(body); setDbot(""); setDchat(""); setDhook(""); setDlabel(""); refetchDests(); } catch (e) { setDerr(String(e)); } } async function toggleDest(id: number, enabled: boolean) { await api.updateDestination(id, { enabled: !enabled }); refetchDests(); } async function delDest(id: number) { await api.deleteDestination(id); refetchDests(); } return (

Alerts

Rules evaluated over snapshots each cycle.

Push destinations (per login)

Secrets stay server-side โ€” the list never shows tokens.

setDlabel(e.currentTarget.value)} style={{ width: "160px" }} /> {dkind() === "telegram" ? ( <> setDbot(e.currentTarget.value)} style={{ width: "200px" }} /> setDchat(e.currentTarget.value)} style={{ width: "120px" }} /> ) : ( setDhook(e.currentTarget.value)} style={{ flex: 1 }} /> )}
{derr() ?

{derr()}

: null}
    {(dests()?.destinations || []).map((d) => (
  • {d.kind} {d.label || `#${d.id}`} {d.enabled ? "on" : "off"} ยท {d.configured ? "configured" : "missing secret"}
  • ))}

New rule

setName(e.currentTarget.value)} style={{ flex: 1 }} />
setValue(Number(e.currentTarget.value))} style={{ width: "100px" }} />
    {(c, i) =>
  • {c.field} {c.op} {c.value}
  • }

Rule JSON: {rulePreview()}

setChannels(e.currentTarget.value)} style={{ flex: 1 }} />

Rules

}>
    {(a) =>
  • {a.name} last: {a.last_fired || "never"}
  • }

Event history

No events yet.

}>
    {(e) =>
  • {String(e.ticker)} โ€” {String(e.message)}
  • }
); }