Enhance alerts with actionable suggestions.

Added `buildAlertSuggestions` to generate tailored recommendations for various alert categories. Integrated these suggestions into the AlertsPage UI and styled them for clarity, improving user guidance on resolving issues.
This commit is contained in:
2026-02-12 13:21:24 +01:00
parent c74461ddfb
commit 2c727c361e
2 changed files with 89 additions and 0 deletions

View File

@@ -24,6 +24,56 @@ function formatTs(ts) {
return new Date(ts).toLocaleString();
}
function buildAlertSuggestions(item) {
const name = (item?.name || "").toLowerCase();
const category = (item?.category || "").toLowerCase();
const source = (item?.source || "").toLowerCase();
const value = Number(item?.value || 0);
const suggestions = [];
if (name.includes("reachability") || name.includes("connectivity") || category === "availability") {
suggestions.push("Verify host, port, firewall rules, and network routing between backend container and DB target.");
suggestions.push("Check PostgreSQL `listen_addresses` and `pg_hba.conf` on the monitored instance.");
}
if (name.includes("freshness") || item?.message?.toLowerCase().includes("no metrics")) {
suggestions.push("Check collector logs and polling interval. Confirm the target credentials are still valid.");
suggestions.push("Run a manual connection test in Targets Management and verify SSL mode.");
}
if (name.includes("cache hit") || category === "performance") {
suggestions.push("Inspect slow queries and add/adjust indexes for frequent WHERE/JOIN columns.");
suggestions.push("Review shared buffers and query patterns that cause high disk reads.");
}
if (name.includes("lock") || category === "contention") {
suggestions.push("Inspect blocking sessions in `pg_stat_activity` and long transactions.");
suggestions.push("Reduce transaction scope/duration and add missing indexes to avoid lock escalation.");
}
if (name.includes("deadlock")) {
suggestions.push("Enforce a consistent table access order in transactions to prevent deadlocks.");
suggestions.push("Retry deadlocked transactions in the application with backoff.");
}
if (name.includes("checkpoint") || category === "io") {
suggestions.push("Review `max_wal_size`, `checkpoint_timeout`, and write burst patterns.");
suggestions.push("Check disk throughput and WAL pressure during peak load.");
}
if (name.includes("rollback")) {
suggestions.push("Investigate application errors causing transaction rollbacks.");
suggestions.push("Validate constraints/input earlier to reduce failed writes.");
}
if (name.includes("query") || category === "query" || source === "custom") {
suggestions.push("Run `EXPLAIN (ANALYZE, BUFFERS)` for the affected query and optimize highest-cost nodes.");
suggestions.push("Prioritize fixes for high total-time queries first, then high mean-time queries.");
}
if (value > 0 && item?.comparison && item?.alert_threshold !== null && item?.alert_threshold !== undefined) {
suggestions.push(`Current value is ${value.toFixed(2)} with threshold rule ${item.comparison} ${Number(item.alert_threshold).toFixed(2)}.`);
}
if (!suggestions.length) {
suggestions.push("Start with target activity, locks, and query insights to isolate the root cause.");
suggestions.push("Compare current values to the last stable period and tune threshold sensitivity if needed.");
}
return suggestions.slice(0, 4);
}
export function AlertsPage() {
const { tokens, refresh, me } = useAuth();
const [targets, setTargets] = useState([]);
@@ -196,6 +246,7 @@ export function AlertsPage() {
<div className="alerts-list">
{status.warnings.map((item) => {
const isOpen = expandedKey === item.alert_key;
const suggestions = buildAlertSuggestions(item);
return (
<article
className={`alert-item warning ${isOpen ? "is-open" : ""}`}
@@ -222,6 +273,12 @@ export function AlertsPage() {
<div><span>Checked At</span><strong>{formatTs(item.checked_at)}</strong></div>
<div><span>Target ID</span><strong>{item.target_id}</strong></div>
{item.sql_text && <div className="alert-sql"><code>{item.sql_text}</code></div>}
<div className="alert-suggestions">
<h4>Recommended actions</h4>
<ul>
{suggestions.map((tip, idx) => <li key={idx}>{tip}</li>)}
</ul>
</div>
</div>
)}
</article>
@@ -239,6 +296,7 @@ export function AlertsPage() {
<div className="alerts-list">
{status.alerts.map((item) => {
const isOpen = expandedKey === item.alert_key;
const suggestions = buildAlertSuggestions(item);
return (
<article
className={`alert-item alert ${isOpen ? "is-open" : ""}`}
@@ -265,6 +323,12 @@ export function AlertsPage() {
<div><span>Checked At</span><strong>{formatTs(item.checked_at)}</strong></div>
<div><span>Target ID</span><strong>{item.target_id}</strong></div>
{item.sql_text && <div className="alert-sql"><code>{item.sql_text}</code></div>}
<div className="alert-suggestions">
<h4>Recommended actions</h4>
<ul>
{suggestions.map((tip, idx) => <li key={idx}>{tip}</li>)}
</ul>
</div>
</div>
)}
</article>

View File

@@ -936,6 +936,31 @@ td {
font-size: 12px;
}
.alert-suggestions {
grid-column: 1 / -1;
margin-top: 2px;
padding: 8px;
border-radius: 8px;
border: 1px solid #3d689d;
background: #0a1f3fb3;
}
.alert-suggestions h4 {
margin: 0 0 6px 0;
font-size: 13px;
}
.alert-suggestions ul {
margin: 0;
padding-left: 16px;
}
.alert-suggestions li {
margin-bottom: 4px;
color: #d4e7ff;
font-size: 12px;
}
.alert-form .field-full {
grid-column: 1 / -1;
}