feat: implement Phase 5 — Alerting & Monitoring

Backend:
- AlertEvaluationService: evaluates metrics against AlertRules after each heartbeat
  - Supports cpu_usage_percent and memory_usage_percent metric paths
  - Operators: >, >=, <, <=, ==
  - 15-minute dedup window to prevent alert spam
- AlertRulesController: full CRUD for alert rules (GET/POST/PUT/DELETE)
- AlertsController: list with acknowledged filter + POST acknowledge endpoint
- IRmmHubClient: added AlertTriggered push method
- Program.cs: AlertEvaluationService registered as Scoped

Frontend:
- AlertsPage: two-tab layout (active alerts + rules)
  - Alerts tab: severity badges, acknowledge button, all/unack/ack filter
  - Rules tab: condition display, enabled toggle, delete with confirm
  - Create rule modal with MetricPath/Operator/Threshold/Severity selects
- api/types.ts: AlertRule, AlertItem, CreateAlertRuleRequest types
- api/client.ts: alertRulesApi and alertsApi
- useAgentSignalR: handles AlertTriggered → invalidates alerts query
- App.tsx: Alerts nav item with Bell icon

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-03-19 14:00:19 +01:00
parent d17df20f5e
commit eb114f68e2
11 changed files with 815 additions and 3 deletions

View File

@@ -1,9 +1,10 @@
import { useState } from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { LayoutDashboard, Ticket, Menu, X } from 'lucide-react'
import { LayoutDashboard, Ticket, Bell, Menu, X } from 'lucide-react'
import { DashboardPage } from './pages/DashboardPage'
import { AgentDetailPage } from './pages/AgentDetailPage'
import TicketsPage from './pages/TicketsPage'
import AlertsPage from './pages/AlertsPage'
import { cn } from './lib/utils'
const queryClient = new QueryClient({
@@ -15,7 +16,7 @@ const queryClient = new QueryClient({
},
})
type Page = 'dashboard' | 'agent-detail' | 'tickets'
type Page = 'dashboard' | 'agent-detail' | 'tickets' | 'alerts'
interface NavItem {
id: Page
@@ -26,6 +27,7 @@ interface NavItem {
const navItems: NavItem[] = [
{ id: 'dashboard', label: 'Dashboard', icon: <LayoutDashboard size={18} /> },
{ id: 'tickets', label: 'Tickets', icon: <Ticket size={18} /> },
{ id: 'alerts', label: 'Alerts', icon: <Bell size={18} /> },
]
function AppContent() {
@@ -107,6 +109,7 @@ function AppContent() {
<AgentDetailPage agentId={selectedAgentId} onBack={handleBack} />
)}
{page === 'tickets' && <TicketsPage />}
{page === 'alerts' && <AlertsPage />}
</main>
</div>
)