@@ -302,12 +186,11 @@ export function NotificationSettingsModal({ isOpen, onClose }: NotificationSetti
-
+
Global
Discord
Slack
Webhook
- Alert Rules
@@ -375,128 +258,6 @@ export function NotificationSettingsModal({ isOpen, onClose }: NotificationSetti
{renderAgentTab('discord', 'Discord')}
{renderAgentTab('slack', 'Slack')}
{renderAgentTab('webhook', 'Custom Webhook')}
-
-
-
-
-
Existing Rules
- {alerts.length === 0 ? (
-
- No active alert rules across all stacks.
-
- ) : (
- alerts.map(alert => (
-
-
-
-
- [{alert.stack_name}] {metricLabels[alert.metric] || alert.metric} {alert.operator} {alert.threshold}
-
-
- Trigger after {alert.duration_mins}m • Cooldown: {alert.cooldown_mins}m
-
-
-
-
-
- ))
- )}
-
-
-
-
-
-
Add New Rule
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- setNewAlertThreshold(e.target.value)}
- placeholder="e.g. 90"
- />
-
-
-
-
-
-
-
-
-
diff --git a/frontend/src/components/StackAlertSheet.tsx b/frontend/src/components/StackAlertSheet.tsx
index 531897ae..7db64ae9 100644
--- a/frontend/src/components/StackAlertSheet.tsx
+++ b/frontend/src/components/StackAlertSheet.tsx
@@ -9,14 +9,9 @@ import {
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from '@/components/ui/select';
-import { Trash2 } from 'lucide-react';
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
+import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
+import { Trash2, HelpCircle } from 'lucide-react';
import { toast } from 'sonner';
import { apiFetch } from '@/lib/api';
@@ -136,112 +131,179 @@ export function StackAlertSheet({ isOpen, onClose, stackName }: StackAlertSheetP
-
- {/* List Existing Alerts */}
-
-
Existing Rules
- {alerts.length === 0 ? (
-
- No active alert rules for this stack.
-
- ) : (
- alerts.map(alert => (
-
-
-
-
- {metricLabels[alert.metric] || alert.metric} {alert.operator} {alert.threshold}
-
-
- Trigger after {alert.duration_mins}m • Cooldown: {alert.cooldown_mins}m
-
-
-
-
+
+
+ {/* List Existing Alerts */}
+
+
Existing Rules
+ {alerts.length === 0 ? (
+
+ No active alert rules for this stack.
- ))
- )}
-
-
-
-
- {/* Add New Alert Form */}
-
-
Add New Rule
-
-
-
-
+ ) : (
+ alerts.map(alert => (
+
+
+
+
+ {metricLabels[alert.metric] || alert.metric} {alert.operator} {alert.threshold}
+
+
+ Trigger after {alert.duration_mins}m • Cooldown: {alert.cooldown_mins}m
+
+
+
+
+
+ ))
+ )}
-
+
+
+ {/* Add New Alert Form */}
+
+
Add New Rule
+
-
-
-
-
- setThreshold(e.target.value)}
- placeholder="e.g. 90"
- />
-
-
-
-
-
-
setDuration(e.target.value)}
- />
+
+
+
+
+
+
+
+
+
+ The comparison condition to trigger the alert against the threshold.
+
+
+
+
+
+
+
+
+ Greater than
+ Greater or eq
+ Less than
+ Less or eq
+ Equals
+
+
+
+
+
+
+
+
+
+
+
+ The numerical value the metric needs to breach to trigger the conditions.
+
+
+
+
{
+ let val = e.target.value;
+ if (val !== '' && Number(val) < 0) val = '0';
+ setThreshold(val);
+ }}
+ placeholder="e.g. 90"
+ />
+
-
-
- setCooldown(e.target.value)}
- />
-
-
-
+
+
+
+
+
+
+
+
+
+ How long the metric must stay in breach of the threshold before sending an alert.
+
+
+
+
{
+ let val = e.target.value;
+ if (val !== '' && Number(val) < 0) val = '0';
+ setDuration(val);
+ }}
+ />
+
+
+
+
+
+
+
+
+
+ How long to wait before sending another alert if the stack continues to breach.
+
+
+
+
{
+ let val = e.target.value;
+ if (val !== '' && Number(val) < 0) val = '0';
+ setCooldown(val);
+ }}
+ />
+
+
+
+
+
-
+
);