feat: Hub monitoring takeover — event system, dead man's switch, notifications (v0.3.0)
Replace external Healthchecks.io with Hub-native monitoring. New events table + /api/v1/event endpoint for structured events from controllers. Staleness checker (60s) detects unresponsive nodes. Backup deadline checker (daily 05:00) catches missed backups. Notification dispatcher sends operator (English) + customer (Hungarian) emails via Resend with per-event cooldowns. Event timeline on customer page, dashboard badges. Config form deprecates Monitoring UUIDs section. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -403,6 +403,61 @@
|
||||
{{end}}
|
||||
</section>
|
||||
|
||||
<!-- Events -->
|
||||
<section class="card">
|
||||
<h2>Events
|
||||
{{if .EventCounts}}
|
||||
{{with mapGet .EventCounts "error"}}<span class="severity-badge severity-error">{{.}} error{{if gt . 1}}s{{end}}</span>{{end}}
|
||||
{{with mapGet .EventCounts "warning"}}<span class="severity-badge severity-warning">{{.}} warning{{if gt . 1}}s{{end}}</span>{{end}}
|
||||
{{end}}
|
||||
<span class="text-muted" style="font-size: 0.7em; font-weight: normal;"> (last 24h)</span>
|
||||
</h2>
|
||||
{{if .Events}}
|
||||
<div style="margin-bottom: 0.5rem;">
|
||||
<button class="btn btn-sm btn-outline event-filter active" data-filter="all">All</button>
|
||||
<button class="btn btn-sm btn-outline event-filter" data-filter="error">Errors</button>
|
||||
<button class="btn btn-sm btn-outline event-filter" data-filter="warning">Warnings</button>
|
||||
<button class="btn btn-sm btn-outline event-filter" data-filter="info">Info</button>
|
||||
</div>
|
||||
<table class="history-table" id="events-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Severity</th>
|
||||
<th>Type</th>
|
||||
<th>Message</th>
|
||||
<th>Source</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Events}}
|
||||
<tr data-severity="{{.Severity}}">
|
||||
<td title="{{.CreatedAt.Format "2006-01-02 15:04:05"}}">{{.CreatedAt.Format "Jan 02 15:04"}}</td>
|
||||
<td><span class="severity-badge severity-{{.Severity}}">{{.Severity}}</span></td>
|
||||
<td><code>{{.EventType}}</code></td>
|
||||
<td>{{.Message}}</td>
|
||||
<td>{{.Source}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
<script>
|
||||
document.querySelectorAll('.event-filter').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
document.querySelectorAll('.event-filter').forEach(b => b.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
const filter = this.dataset.filter;
|
||||
document.querySelectorAll('#events-table tbody tr').forEach(row => {
|
||||
row.style.display = (filter === 'all' || row.dataset.severity === filter) ? '' : 'none';
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{{else}}
|
||||
<p class="text-muted">No events recorded yet.</p>
|
||||
{{end}}
|
||||
</section>
|
||||
|
||||
<!-- Notifications -->
|
||||
<section class="card">
|
||||
<h2>Notifications</h2>
|
||||
@@ -424,6 +479,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Channel</th>
|
||||
<th>Event</th>
|
||||
<th>Status</th>
|
||||
<th>Message</th>
|
||||
@@ -433,6 +489,7 @@
|
||||
{{range .RecentNotifications}}
|
||||
<tr>
|
||||
<td>{{.CreatedAt.Format "Jan 02 15:04"}}</td>
|
||||
<td><span class="status-badge status-badge-{{.Channel}}">{{.Channel}}</span></td>
|
||||
<td>{{.EventType}}</td>
|
||||
<td><span class="status-badge status-badge-{{.Status}}">{{.Status}}</span></td>
|
||||
<td>{{.Message}}</td>
|
||||
|
||||
Reference in New Issue
Block a user