feat(alerts): zehn Warnregeln mit Worker, Liste und Einstellungen (#011)
Regeln als reine Funktionen, Abgleich oeffnet und schliesst Warnungen anhand eines Fingerabdrucks. Worker wertet stuendlich aus, zusaetzlich nach jedem Report. 123 Tests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useTransition } from "react";
|
||||
import { alertsJetztPruefen } from "@/server/actions/alerts";
|
||||
|
||||
/** Stößt die Auswertung sofort an, statt auf den nächsten Durchlauf des Workers zu warten. */
|
||||
export function PruefenKnopf() {
|
||||
const [meldung, setMeldung] = useState<string | null>(null);
|
||||
const [laeuft, starte] = useTransition();
|
||||
|
||||
return (
|
||||
<span className="d-flex align-items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm btn-outline-primary"
|
||||
disabled={laeuft}
|
||||
onClick={() =>
|
||||
starte(async () => {
|
||||
const ergebnis = await alertsJetztPruefen();
|
||||
setMeldung(ergebnis.erfolg ?? ergebnis.fehler ?? null);
|
||||
})
|
||||
}
|
||||
>
|
||||
<i className="bi bi-arrow-clockwise me-1" aria-hidden="true" />
|
||||
{laeuft ? "Prüft …" : "Jetzt prüfen"}
|
||||
</button>
|
||||
{meldung ? <span className="small text-body-secondary">{meldung}</span> : null}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
"use client";
|
||||
|
||||
import { useActionState } from "react";
|
||||
import { warnregelnSpeichern } from "@/server/actions/alerts";
|
||||
import type { AktionsErgebnis } from "@/server/actions/kunden";
|
||||
|
||||
export interface RegelAnzeige {
|
||||
name: string;
|
||||
beschreibung: string;
|
||||
aktiv: boolean;
|
||||
/** Schwellwerte als Text, mehrere Stufen mit Komma getrennt. */
|
||||
schwellwerte: { name: string; beschriftung: string; wert: string }[];
|
||||
}
|
||||
|
||||
const LEER: AktionsErgebnis = {};
|
||||
|
||||
export function WarnregelFormular({ regeln }: { regeln: RegelAnzeige[] }) {
|
||||
const [ergebnis, aktion, laeuft] = useActionState(warnregelnSpeichern, LEER);
|
||||
|
||||
return (
|
||||
<form action={aktion}>
|
||||
{ergebnis.fehler ? <div className="alert alert-danger">{ergebnis.fehler}</div> : null}
|
||||
{ergebnis.erfolg ? <div className="alert alert-success">{ergebnis.erfolg}</div> : null}
|
||||
|
||||
<div className="table-responsive">
|
||||
<table className="table align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" style={{ width: "6rem" }}>
|
||||
Aktiv
|
||||
</th>
|
||||
<th scope="col">Regel</th>
|
||||
<th scope="col" style={{ width: "22rem" }}>
|
||||
Schwellwerte
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{regeln.map((regel) => (
|
||||
<tr key={regel.name}>
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input pl-touch-target"
|
||||
name={`aktiv:${regel.name}`}
|
||||
defaultChecked={regel.aktiv}
|
||||
aria-label={`${regel.beschreibung} aktiv`}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<span className="fw-semibold">{regel.beschreibung}</span>
|
||||
<div className="small text-body-secondary pl-mono">{regel.name}</div>
|
||||
</td>
|
||||
<td>
|
||||
{regel.schwellwerte.length === 0 ? (
|
||||
<span className="small text-body-secondary">keine</span>
|
||||
) : (
|
||||
regel.schwellwerte.map((schwelle) => (
|
||||
<div className="mb-2" key={schwelle.name}>
|
||||
<label className="form-label small mb-1" htmlFor={`${regel.name}-${schwelle.name}`}>
|
||||
{schwelle.beschriftung}
|
||||
</label>
|
||||
<input
|
||||
id={`${regel.name}-${schwelle.name}`}
|
||||
name={`schwelle:${regel.name}:${schwelle.name}`}
|
||||
className="form-control form-control-sm"
|
||||
defaultValue={schwelle.wert}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<button type="submit" className="btn btn-primary pl-touch-target" disabled={laeuft}>
|
||||
{laeuft ? "Speichert …" : "Speichern und neu auswerten"}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -150,7 +150,7 @@ export function Header({
|
||||
{alerts.slice(0, 6).map((alert) => (
|
||||
<Link
|
||||
key={alert.id}
|
||||
href={alert.hostId ? `/hosts/${alert.hostId}` : "/hosts"}
|
||||
href={alert.hostId ? `/hosts/${alert.hostId}` : "/warnungen"}
|
||||
className="dropdown-item d-flex gap-2 py-2"
|
||||
onClick={() => setGlockeOffen(false)}
|
||||
>
|
||||
@@ -162,7 +162,11 @@ export function Header({
|
||||
</Link>
|
||||
))}
|
||||
<div className="dropdown-divider" />
|
||||
<Link href="/hosts" className="dropdown-item text-center" onClick={() => setGlockeOffen(false)}>
|
||||
<Link
|
||||
href="/warnungen"
|
||||
className="dropdown-item text-center"
|
||||
onClick={() => setGlockeOffen(false)}
|
||||
>
|
||||
Alle Warnungen ansehen
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -15,10 +15,12 @@ interface MenuEintrag {
|
||||
export function Sidebar({
|
||||
hostsAnzahl,
|
||||
offeneRechnungspunkte,
|
||||
offeneWarnungen,
|
||||
benutzer,
|
||||
}: {
|
||||
hostsAnzahl: number;
|
||||
offeneRechnungspunkte: number;
|
||||
offeneWarnungen: number;
|
||||
benutzer: string;
|
||||
}) {
|
||||
const pathname = usePathname();
|
||||
@@ -44,10 +46,17 @@ export function Sidebar({
|
||||
: undefined,
|
||||
},
|
||||
{ href: "/jahresbahn", titel: "Jahresbahn", icon: "bi-calendar3-range" },
|
||||
{
|
||||
href: "/warnungen",
|
||||
titel: "Warnungen",
|
||||
icon: "bi-bell",
|
||||
badge: offeneWarnungen > 0 ? { text: String(offeneWarnungen), klasse: "pl-bg-warning" } : undefined,
|
||||
},
|
||||
];
|
||||
|
||||
const einstellungen: MenuEintrag[] = [
|
||||
{ href: "/einstellungen/tokens", titel: "Agent-Tokens", icon: "bi-key" },
|
||||
{ href: "/einstellungen/warnregeln", titel: "Warnregeln", icon: "bi-shield-exclamation" },
|
||||
{ href: "/einstellungen/benachrichtigungen", titel: "Benachrichtigungen", icon: "bi-bell" },
|
||||
{ href: "/einstellungen/system", titel: "System", icon: "bi-sliders" },
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user