feat: alerts host-filter field with URL query param and notify URL
- Add regex filter input to the Alerts dashboard that filters displayed hosts on every keystroke; invalid regex turns the border red - Initialise the filter from ?filter= in the URL query string - Change _build_url() to produce /alerts?filter=<hostname> so notification links (Pushover, email, Matrix, etc.) land on the alerts page pre-filtered to the alerting host Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+2
-2
@@ -641,7 +641,7 @@ async def start(
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Heartbeat — Login</title>
|
<title>Heartbeat — Login</title>
|
||||||
<style>
|
<style>
|
||||||
body {{ font-family: sans-serif; background: #16191d; display: flex;
|
body {{ font-family: sans-serif; background: #f5f5f5; display: flex;
|
||||||
justify-content: center; align-items: center; height: 100vh; margin: 0; }}
|
justify-content: center; align-items: center; height: 100vh; margin: 0; }}
|
||||||
.box {{ background: #fff; padding: 2em 2.5em; border-radius: 8px;
|
.box {{ background: #fff; padding: 2em 2.5em; border-radius: 8px;
|
||||||
box-shadow: 0 2px 12px rgba(0,0,0,.15); min-width: 300px; }}
|
box-shadow: 0 2px 12px rgba(0,0,0,.15); min-width: 300px; }}
|
||||||
@@ -657,7 +657,7 @@ async def start(
|
|||||||
.divider {{ text-align: center; margin: 1.2em 0 .8em; color: #999;
|
.divider {{ text-align: center; margin: 1.2em 0 .8em; color: #999;
|
||||||
font-size: .85em; border-top: 1px solid #eee; padding-top: .8em; }}
|
font-size: .85em; border-top: 1px solid #eee; padding-top: .8em; }}
|
||||||
.gitea-btn {{ display: flex; align-items: center; justify-content: center;
|
.gitea-btn {{ display: flex; align-items: center; justify-content: center;
|
||||||
gap: .5em; width: 100%; padding: .6em; background: #609926;
|
gap: .5em; width: 100%; padding: .6em; background: #16191d;
|
||||||
color: #fff; border-radius: 4px; font-size: 1em; text-align: center;
|
color: #fff; border-radius: 4px; font-size: 1em; text-align: center;
|
||||||
text-decoration: none; box-sizing: border-box; }}
|
text-decoration: none; box-sizing: border-box; }}
|
||||||
.gitea-btn:hover {{ background: #4e7d1e; }}
|
.gitea-btn:hover {{ background: #4e7d1e; }}
|
||||||
|
|||||||
@@ -401,7 +401,7 @@ def _build_url(host_name: str) -> str:
|
|||||||
base_url = _config.get("base_url", "").rstrip("/")
|
base_url = _config.get("base_url", "").rstrip("/")
|
||||||
if not base_url:
|
if not base_url:
|
||||||
return ""
|
return ""
|
||||||
return f"{base_url}/plugins#{host_name}"
|
return f"{base_url}/alerts?filter={host_name}"
|
||||||
|
|
||||||
|
|
||||||
async def send_notification(host_name: str, notif: Notification) -> dict:
|
async def send_notification(host_name: str, notif: Notification) -> dict:
|
||||||
|
|||||||
@@ -94,6 +94,24 @@
|
|||||||
border-color: #2196f3;
|
border-color: #2196f3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filter-input {
|
||||||
|
padding: 7px 12px;
|
||||||
|
border: 2px solid #ddd;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
outline: none;
|
||||||
|
width: 200px;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-input:focus {
|
||||||
|
border-color: #2196f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-input.invalid {
|
||||||
|
border-color: #f44336;
|
||||||
|
}
|
||||||
|
|
||||||
.alerts-container {
|
.alerts-container {
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -316,6 +334,7 @@
|
|||||||
<button class="filter-button active" onclick="filterAlerts('all')">All</button>
|
<button class="filter-button active" onclick="filterAlerts('all')">All</button>
|
||||||
<button class="filter-button" onclick="filterAlerts('critical')">Critical Only</button>
|
<button class="filter-button" onclick="filterAlerts('critical')">Critical Only</button>
|
||||||
<button class="filter-button" onclick="filterAlerts('warning')">Warning Only</button>
|
<button class="filter-button" onclick="filterAlerts('warning')">Warning Only</button>
|
||||||
|
<input id="host-filter" class="filter-input" type="text" placeholder="host filter (regex)" oninput="onHostFilterInput(this)">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="alerts-container">
|
<div class="alerts-container">
|
||||||
@@ -332,6 +351,7 @@
|
|||||||
<script>
|
<script>
|
||||||
let currentFilter = 'all';
|
let currentFilter = 'all';
|
||||||
let allAlerts = [];
|
let allAlerts = [];
|
||||||
|
let hostFilterRe = null;
|
||||||
|
|
||||||
async function loadAlerts() {
|
async function loadAlerts() {
|
||||||
try {
|
try {
|
||||||
@@ -366,10 +386,13 @@
|
|||||||
// Filter alerts based on current filter
|
// Filter alerts based on current filter
|
||||||
let filteredAlerts = alerts;
|
let filteredAlerts = alerts;
|
||||||
if (currentFilter !== 'all') {
|
if (currentFilter !== 'all') {
|
||||||
filteredAlerts = alerts.filter(alert =>
|
filteredAlerts = filteredAlerts.filter(alert =>
|
||||||
alert.level.toLowerCase() === currentFilter
|
alert.level.toLowerCase() === currentFilter
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (hostFilterRe) {
|
||||||
|
filteredAlerts = filteredAlerts.filter(alert => hostFilterRe.test(alert.hostname));
|
||||||
|
}
|
||||||
|
|
||||||
if (filteredAlerts.length === 0) {
|
if (filteredAlerts.length === 0) {
|
||||||
if (currentFilter === 'all' && alerts.length === 0) {
|
if (currentFilter === 'all' && alerts.length === 0) {
|
||||||
@@ -538,9 +561,36 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onHostFilterInput(input) {
|
||||||
|
const val = input.value.trim();
|
||||||
|
if (!val) {
|
||||||
|
hostFilterRe = null;
|
||||||
|
input.classList.remove('invalid');
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
hostFilterRe = new RegExp(val, 'i');
|
||||||
|
input.classList.remove('invalid');
|
||||||
|
} catch (_) {
|
||||||
|
hostFilterRe = null;
|
||||||
|
input.classList.add('invalid');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
renderAlerts(allAlerts);
|
||||||
|
}
|
||||||
|
|
||||||
// Auto-refresh every 15 seconds
|
// Auto-refresh every 15 seconds
|
||||||
setInterval(loadAlerts, 15000);
|
setInterval(loadAlerts, 15000);
|
||||||
|
|
||||||
|
// Initialise filter from URL query string (?filter=...)
|
||||||
|
(function () {
|
||||||
|
const param = new URLSearchParams(window.location.search).get('filter');
|
||||||
|
if (param) {
|
||||||
|
const input = document.getElementById('host-filter');
|
||||||
|
input.value = param;
|
||||||
|
onHostFilterInput(input);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
// Initial load
|
// Initial load
|
||||||
loadAlerts();
|
loadAlerts();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user