{% extends 'clean_base.axml' %} {% set modal_name = 'Dashboard' %} {% macro dashctrl() %} { window: 'all', metrics: { published: 0, delivered: 0, retrying: 0, pending: 0, failed: 0, total: 0, success_rate: null }, failures: [], subs: [], loading: false, busy: null, gistDetail: null, gistDetailIDE: null, subDetail: null, subGists: [], subLoading: false, showBackfill: false, backfillSub: null, backfillSubName: '', backfillSince: '', backfillCount: null, backfillRunning: false, init(){ this.refresh(); }, refresh(){ this.loadMetrics(); this.loadFailures(); this.loadSubs(); }, loadMetrics(){ fetch(`/v1/metrics/deliveries?window=${this.window}`, { credentials: 'include' }) .then(r => r.json()) .then(data => { this.metrics = data; }) .catch(() => ShowFeedback('error', 'Failed to load metrics')); }, loadFailures(){ this.loading = true; fetch('/v1/gists?status=failed&pagination=50', { credentials: 'include' }) .then(r => r.json()) .then(data => { this.failures = data.data || []; this.loading = false; }) .catch(() => { this.loading = false; }); }, loadSubs(){ fetch('/v1/metrics/subscriptions', { credentials: 'include' }) .then(r => r.json()) .then(data => { this.subs = data.data || []; }) .catch(() => {}); }, setWindow(w){ this.window = w; this.loadMetrics(); }, fmtRate(){ return this.metrics.success_rate === null ? '—' : `${this.metrics.success_rate}%`; }, replayOne(id){ this.busy = id; fetch(`/v1/regists/${id}`, { method: 'POST', credentials: 'include' }) .then(async r => { if(!r.ok) throw await r.json().catch(() => ({})); return r.json(); }) .then(data => { if(data.delivered) ShowFeedback('success', `Gist ${id} replayed — delivered (${data.status_code})`); else ShowFeedback('warning', `Gist ${id} replayed but still failing${data.status_code ? ' ('+data.status_code+')' : ''}`); this.refresh(); }) .catch(err => ShowFeedback('error', err?.error || 'Replay failed')) .finally(() => { this.busy = null; }); }, requeueOne(id){ this.busy = id; fetch(`/v1/requeues?gist=${id}`, { method: 'POST', credentials: 'include' }) .then(async r => { if(!r.ok) throw await r.json().catch(() => ({})); return r.json(); }) .then(data => { ShowFeedback('success', `Gist ${id} requeued — daemon will retry it`); this.refresh(); }) .catch(err => ShowFeedback('error', err?.error || 'Requeue failed')) .finally(() => { this.busy = null; }); }, statusClass(s){ return { delivered: 'success', failed: 'danger', retrying: 'warning', pending: '' }[s] || ''; }, fmtDate(v){ return v ? new Date(String(v).replace(' ', 'T')).toLocaleString() : '—'; }, openGist(g){ this.gistDetail = g; this.$nextTick(() => { const el = document.getElementById('dash-gist-ide'); if(!el) return; this.gistDetailIDE = ace.edit(el); this.gistDetailIDE.session.setMode('ace/mode/json'); const dark = document.documentElement.getAttribute('data-bs-theme') === 'dark'; this.gistDetailIDE.setTheme(dark ? 'ace/theme/one_dark' : 'ace/theme/chrome'); this.gistDetailIDE.setOptions({ readOnly: true, fontSize: 13, showPrintMargin: false, wrap: true, tabSize: 2 }); this.gistDetailIDE.setValue(prettyJson(g.payload || {})); this.gistDetailIDE.clearSelection(); }); }, closeGist(){ this.gistDetail = null; if(this.gistDetailIDE){ this.gistDetailIDE.destroy(); this.gistDetailIDE = null; } }, openSub(s){ this.subDetail = s; this.subGists = []; this.subLoading = true; fetch(`/v1/gists?subscription=${encodeURIComponent(s.subscription)}&pagination=100`, { credentials: 'include' }) .then(r => r.json()) .then(data => { this.subGists = data.data || []; this.subLoading = false; }) .catch(() => { this.subLoading = false; ShowFeedback('error', 'Failed to load subscription deliveries'); }); }, openBackfill(sub){ this.backfillSub = sub.subscription; this.backfillSubName = `${sub.subscriber} · ${sub.action}`; this.backfillSince = ''; this.backfillCount = null; this.showBackfill = true; this.dryBackfill(); }, dryBackfill(){ fetch('/v1/backfills', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ subscription: this.backfillSub, since: this.backfillSince, dry_run: true }) }).then(r => r.json()).then(data => { this.backfillCount = data.count; }) .catch(() => ShowFeedback('error', 'Could not compute backfill count')); }, runBackfill(){ this.backfillRunning = true; fetch('/v1/backfills', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ subscription: this.backfillSub, since: this.backfillSince }) }).then(r => r.json()).then(data => { this.backfillRunning = false; this.showBackfill = false; ShowFeedback('success', `Backfilling ${data.backfilled} historical events to this subscription`); this.refresh(); }).catch(() => { this.backfillRunning = false; ShowFeedback('error', 'Backfill failed'); }); } } {% endmacro %} {% block easel %}
Published
Delivered
Retrying
Failed
Success rate
Needs attention
Action Workspace Publisher → Subscriber Endpoint Attempts Code Last error Last attempt Actions
Subscription health
Subscriber Action Endpoint Delivered Failed Most recent error Backfill
{% endblock %}