Add experimental server alerts and webhook notifications
Release rolling / release (push) Successful in 7m30s
Release rolling / release (push) Successful in 7m30s
Server-side alerting for Docker/container/stack health with Discord, Slack, Teams, ntfy, Gotify, Telegram, and generic webhooks. Settings tab, client cache, and backup/restore coverage; marked experimental.
This commit is contained in:
+261
-5
@@ -2682,7 +2682,7 @@ services:
|
||||
<button type="button" class="nav-link" data-settings-tab="jobs" role="tab">Jobs tray</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button type="button" class="nav-link" data-settings-tab="notifications" role="tab">Notifications</button>
|
||||
<button type="button" class="nav-link" data-settings-tab="alerts" role="tab">Alerts & Notifications <span class="badge text-bg-secondary ms-1" style="font-size:0.65em;vertical-align:middle;">Experimental</span></button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button type="button" class="nav-link" data-settings-tab="tunnels" role="tab">Tunnels</button>
|
||||
@@ -2915,10 +2915,11 @@ services:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notifications -->
|
||||
<div class="settings-panel hidden" id="settings-panel-notifications" data-settings-panel="notifications">
|
||||
<!-- Alerts & Notifications (local bell + server webhooks) -->
|
||||
<div class="settings-panel hidden" id="settings-panel-alerts" data-settings-panel="alerts">
|
||||
<div class="settings-section">
|
||||
<h3>Bell & history</h3>
|
||||
<h3>Local bell & history</h3>
|
||||
<p class="small text-muted mb-3">In-app notification center on this client (not webhooks).</p>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="settings-badge-mode">Bell badge</label>
|
||||
@@ -2947,6 +2948,255 @@ services:
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section mt-4">
|
||||
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-2">
|
||||
<div>
|
||||
<h3 class="mb-0">Server alerts & webhooks <span class="badge text-bg-secondary">Experimental</span></h3>
|
||||
<p class="small text-muted mb-0">
|
||||
<strong>Experimental.</strong> Runs on the PearDock <strong>server</strong> — Discord, Slack, Teams, ntfy, Gotify, Telegram, or generic HTTPS.
|
||||
Fires even when this UI is closed. Requires an admin session. Channels, rules, and secrets are backed up in client packages when included.
|
||||
</p>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span id="alerts-engine-status" class="small text-muted">—</span>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" id="alerts-refresh" title="Reload from server">
|
||||
<i class="fas fa-sync-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="alerts-offline-hint" class="alert alert-warning py-2 small hidden">
|
||||
Connect to a server to configure webhook channels and rules.
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mt-1">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label" for="alerts-enabled">Alert engine</label>
|
||||
<select id="alerts-enabled" class="form-select bg-dark text-white">
|
||||
<option value="1">Enabled</option>
|
||||
<option value="0">Disabled</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label" for="alerts-min-severity">Global min severity</label>
|
||||
<select id="alerts-min-severity" class="form-select bg-dark text-white">
|
||||
<option value="info">Info+</option>
|
||||
<option value="warning">Warning+</option>
|
||||
<option value="critical">Critical only</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label" for="alerts-poll-ms">Health poll (sec)</label>
|
||||
<input type="number" id="alerts-poll-ms" class="form-control bg-dark text-white" min="15" max="3600" value="60">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label" for="alerts-rate-limit">Max / minute</label>
|
||||
<input type="number" id="alerts-rate-limit" class="form-control bg-dark text-white" min="1" max="300" value="40">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label" for="alerts-include-hostname">Include hostname</label>
|
||||
<select id="alerts-include-hostname" class="form-select bg-dark text-white">
|
||||
<option value="1">Yes</option>
|
||||
<option value="0">No</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label" for="alerts-quiet-enabled">Quiet hours</label>
|
||||
<select id="alerts-quiet-enabled" class="form-select bg-dark text-white">
|
||||
<option value="0">Off</option>
|
||||
<option value="1">On (critical still fires)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label" for="alerts-quiet-start">Quiet start</label>
|
||||
<input type="text" id="alerts-quiet-start" class="form-control bg-dark text-white" placeholder="22:00" value="22:00">
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label" for="alerts-quiet-end">Quiet end</label>
|
||||
<input type="text" id="alerts-quiet-end" class="form-control bg-dark text-white" placeholder="07:00" value="07:00">
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<button type="button" class="btn btn-primary btn-sm" id="alerts-save-global" data-min-role="admin">
|
||||
<i class="fas fa-save me-1"></i>Save engine settings
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="alerts-status" class="small text-muted mt-2" role="status"></div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section mt-4">
|
||||
<h3>Webhook channels</h3>
|
||||
<p class="small text-muted">Delivery targets. Secrets are stored only on the server (redacted in the UI after save).</p>
|
||||
<div id="alerts-channels-list" class="list-group mb-3"></div>
|
||||
<div class="card bg-dark border-secondary">
|
||||
<div class="card-body">
|
||||
<h4 class="h6 card-title">Add / edit channel</h4>
|
||||
<input type="hidden" id="alert-ch-id" value="">
|
||||
<div class="row g-2">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="alert-ch-name">Name</label>
|
||||
<input type="text" id="alert-ch-name" class="form-control bg-dark text-white" placeholder="Ops Discord">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label" for="alert-ch-type">Type</label>
|
||||
<select id="alert-ch-type" class="form-select bg-dark text-white">
|
||||
<option value="discord">Discord</option>
|
||||
<option value="slack">Slack</option>
|
||||
<option value="teams">Microsoft Teams</option>
|
||||
<option value="generic">Generic webhook</option>
|
||||
<option value="ntfy">ntfy</option>
|
||||
<option value="gotify">Gotify</option>
|
||||
<option value="telegram">Telegram</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label" for="alert-ch-enabled">Enabled</label>
|
||||
<select id="alert-ch-enabled" class="form-select bg-dark text-white">
|
||||
<option value="1">Yes</option>
|
||||
<option value="0">No</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label" for="alert-ch-min-sev">Min severity</label>
|
||||
<select id="alert-ch-min-sev" class="form-select bg-dark text-white">
|
||||
<option value="info">Info+</option>
|
||||
<option value="warning">Warning+</option>
|
||||
<option value="critical">Critical only</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<label class="form-label" for="alert-ch-url">Webhook / server URL</label>
|
||||
<input type="url" id="alert-ch-url" class="form-control bg-dark text-white" placeholder="https://…">
|
||||
<div class="form-text" id="alert-ch-url-help"></div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="alert-ch-username">Display name</label>
|
||||
<input type="text" id="alert-ch-username" class="form-control bg-dark text-white" value="PearDock">
|
||||
</div>
|
||||
<div class="col-md-4 hidden" id="alert-ch-token-row">
|
||||
<label class="form-label" for="alert-ch-token">Token</label>
|
||||
<input type="password" id="alert-ch-token" class="form-control bg-dark text-white" autocomplete="off">
|
||||
</div>
|
||||
<div class="col-md-4 hidden" id="alert-ch-topic-row">
|
||||
<label class="form-label" for="alert-ch-topic">ntfy topic</label>
|
||||
<input type="text" id="alert-ch-topic" class="form-control bg-dark text-white" placeholder="peardock">
|
||||
</div>
|
||||
<div class="col-12 hidden" id="alert-ch-telegram-row">
|
||||
<div class="row g-2">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="alert-ch-bot-token">Telegram bot token</label>
|
||||
<input type="password" id="alert-ch-bot-token" class="form-control bg-dark text-white" autocomplete="off">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="alert-ch-chat-id">Telegram chat ID</label>
|
||||
<input type="text" id="alert-ch-chat-id" class="form-control bg-dark text-white">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 d-flex gap-2">
|
||||
<button type="button" class="btn btn-primary btn-sm" id="alert-ch-save" data-min-role="admin">Save channel</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" id="alert-ch-reset">Clear form</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section mt-4">
|
||||
<h3>Alert rules</h3>
|
||||
<p class="small text-muted">
|
||||
What triggers a notification. Built-in rules cover container die/OOM, healthchecks, Docker daemon down,
|
||||
compose stack degradation, restarts, and optional disk pressure. Fully editable.
|
||||
</p>
|
||||
<div id="alerts-rules-list" class="list-group mb-3"></div>
|
||||
<div class="card bg-dark border-secondary">
|
||||
<div class="card-body">
|
||||
<h4 class="h6 card-title">Add / edit rule</h4>
|
||||
<input type="hidden" id="alert-rule-id" value="">
|
||||
<div class="row g-2">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="alert-rule-name">Name</label>
|
||||
<input type="text" id="alert-rule-name" class="form-control bg-dark text-white" placeholder="Container died">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label" for="alert-rule-kind">Kind</label>
|
||||
<select id="alert-rule-kind" class="form-select bg-dark text-white">
|
||||
<option value="docker_event">Docker event</option>
|
||||
<option value="container_health">Container health</option>
|
||||
<option value="docker_daemon">Docker daemon</option>
|
||||
<option value="stack_health">Stack / compose health</option>
|
||||
<option value="resource">Host resource (disk)</option>
|
||||
<option value="peardock">Manual / PearDock</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label" for="alert-rule-severity">Severity</label>
|
||||
<select id="alert-rule-severity" class="form-select bg-dark text-white">
|
||||
<option value="info">Info</option>
|
||||
<option value="warning">Warning</option>
|
||||
<option value="critical">Critical</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label" for="alert-rule-enabled">Enabled</label>
|
||||
<select id="alert-rule-enabled" class="form-select bg-dark text-white">
|
||||
<option value="1">Yes</option>
|
||||
<option value="0">No</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label" for="alert-rule-types">Event types</label>
|
||||
<input type="text" id="alert-rule-types" class="form-control bg-dark text-white" placeholder="container" value="container">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label" for="alert-rule-actions">Actions</label>
|
||||
<input type="text" id="alert-rule-actions" class="form-control bg-dark text-white" placeholder="die, oom, kill">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label" for="alert-rule-health">Health statuses</label>
|
||||
<input type="text" id="alert-rule-health" class="form-control bg-dark text-white" placeholder="unhealthy">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label" for="alert-rule-disk">Disk % threshold</label>
|
||||
<input type="number" id="alert-rule-disk" class="form-control bg-dark text-white" min="1" max="100" placeholder="90">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label" for="alert-rule-cooldown">Cooldown (sec)</label>
|
||||
<input type="number" id="alert-rule-cooldown" class="form-control bg-dark text-white" min="0" value="300">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label" for="alert-rule-recover">Notify on recover</label>
|
||||
<select id="alert-rule-recover" class="form-select bg-dark text-white">
|
||||
<option value="1">Yes</option>
|
||||
<option value="0">No</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="alert-rule-name-regex">Name regex filter</label>
|
||||
<input type="text" id="alert-rule-name-regex" class="form-control bg-dark text-white" placeholder="^prod-">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="alert-rule-name-include">Name must include</label>
|
||||
<input type="text" id="alert-rule-name-include" class="form-control bg-dark text-white" placeholder="api, worker">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="alert-rule-name-exclude">Name must exclude</label>
|
||||
<input type="text" id="alert-rule-name-exclude" class="form-control bg-dark text-white" placeholder="dev-, test-">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 d-flex gap-2">
|
||||
<button type="button" class="btn btn-primary btn-sm" id="alert-rule-save" data-min-role="admin">Save rule</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" id="alert-rule-reset">Clear form</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section mt-4">
|
||||
<h3>Recent deliveries</h3>
|
||||
<p class="small text-muted">Last alerts fired by this server (success and failures).</p>
|
||||
<div id="alerts-history-list" class="list-group" style="max-height: 22rem; overflow: auto;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tunnels -->
|
||||
@@ -3194,9 +3444,15 @@ services:
|
||||
<label class="form-check-label" for="settings-backup-inc-backupPolicy">Backup retention policy</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 col-sm-6">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="settings-backup-inc-alerts" data-backup-section="alerts" checked>
|
||||
<label class="form-check-label" for="settings-backup-inc-alerts">Server alerts & webhooks</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="form-text text-muted small mt-2 mb-0">
|
||||
Packages include settings (theme, filters, columns, fleet tags, …), peers, templates, notifications,
|
||||
Packages include settings, peers, templates, notifications, alerts/webhooks (channels, rules, secrets),
|
||||
and optionally the client DHT identity seed. Store backup files securely.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user