feat(ui/alerts): CMD-K sources for alerts + alert rules
Extends operationalSearchData with open alerts (FIRING|ACKNOWLEDGED) and
all rules. Badges convey severity + state. Selecting an alert navigates to
/alerts/inbox/{id}; a rule navigates to /alerts/rules/{id}. Uses the
existing CommandPalette extension point — no new registry.
This commit is contained in:
@@ -31,6 +31,8 @@ import { useAgents } from '../api/queries/agents';
|
|||||||
import { useSearchExecutions, useAttributeKeys } from '../api/queries/executions';
|
import { useSearchExecutions, useAttributeKeys } from '../api/queries/executions';
|
||||||
import { useUsers, useGroups, useRoles } from '../api/queries/admin/rbac';
|
import { useUsers, useGroups, useRoles } from '../api/queries/admin/rbac';
|
||||||
import { useEnvironments } from '../api/queries/admin/environments';
|
import { useEnvironments } from '../api/queries/admin/environments';
|
||||||
|
import { useAlerts } from '../api/queries/alerts';
|
||||||
|
import { useAlertRules } from '../api/queries/alertRules';
|
||||||
import type { UserDetail, GroupDetail, RoleDetail } from '../api/queries/admin/rbac';
|
import type { UserDetail, GroupDetail, RoleDetail } from '../api/queries/admin/rbac';
|
||||||
import { useAuthStore, useIsAdmin, useCanControl } from '../auth/auth-store';
|
import { useAuthStore, useIsAdmin, useCanControl } from '../auth/auth-store';
|
||||||
import { useEnvironmentStore } from '../api/environment-store';
|
import { useEnvironmentStore } from '../api/environment-store';
|
||||||
@@ -161,6 +163,58 @@ function buildAdminSearchData(
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildAlertSearchData(
|
||||||
|
alerts: any[] | undefined,
|
||||||
|
rules: any[] | undefined,
|
||||||
|
): SearchResult[] {
|
||||||
|
const results: SearchResult[] = [];
|
||||||
|
if (alerts) {
|
||||||
|
for (const a of alerts) {
|
||||||
|
results.push({
|
||||||
|
id: `alert:${a.id}`,
|
||||||
|
category: 'alert',
|
||||||
|
title: a.title ?? '(untitled)',
|
||||||
|
badges: [
|
||||||
|
{ label: a.severity, color: severityToSearchColor(a.severity) },
|
||||||
|
{ label: a.state, color: stateToSearchColor(a.state) },
|
||||||
|
],
|
||||||
|
meta: `${a.firedAt ?? ''}${a.silenced ? ' · silenced' : ''}`,
|
||||||
|
path: `/alerts/inbox/${a.id}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rules) {
|
||||||
|
for (const r of rules) {
|
||||||
|
results.push({
|
||||||
|
id: `rule:${r.id}`,
|
||||||
|
category: 'alertRule',
|
||||||
|
title: r.name,
|
||||||
|
badges: [
|
||||||
|
{ label: r.severity, color: severityToSearchColor(r.severity) },
|
||||||
|
{ label: r.conditionKind, color: 'auto' },
|
||||||
|
...(r.enabled ? [] : [{ label: 'DISABLED', color: 'warning' as const }]),
|
||||||
|
],
|
||||||
|
meta: `${r.evaluationIntervalSeconds}s · ${r.targets?.length ?? 0} targets`,
|
||||||
|
path: `/alerts/rules/${r.id}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
function severityToSearchColor(s: string): string {
|
||||||
|
if (s === 'CRITICAL') return 'error';
|
||||||
|
if (s === 'WARNING') return 'warning';
|
||||||
|
return 'auto';
|
||||||
|
}
|
||||||
|
|
||||||
|
function stateToSearchColor(s: string): string {
|
||||||
|
if (s === 'FIRING') return 'error';
|
||||||
|
if (s === 'ACKNOWLEDGED') return 'warning';
|
||||||
|
if (s === 'RESOLVED') return 'success';
|
||||||
|
return 'auto';
|
||||||
|
}
|
||||||
|
|
||||||
function healthToSearchColor(health: string): string {
|
function healthToSearchColor(health: string): string {
|
||||||
switch (health) {
|
switch (health) {
|
||||||
case 'live': return 'success';
|
case 'live': return 'success';
|
||||||
@@ -313,6 +367,10 @@ function LayoutContent() {
|
|||||||
const { data: attributeKeys } = useAttributeKeys();
|
const { data: attributeKeys } = useAttributeKeys();
|
||||||
const { data: envRecords = [] } = useEnvironments();
|
const { data: envRecords = [] } = useEnvironments();
|
||||||
|
|
||||||
|
// Open alerts + rules for CMD-K (env-scoped).
|
||||||
|
const { data: cmdkAlerts } = useAlerts({ state: ['FIRING', 'ACKNOWLEDGED'], limit: 100 });
|
||||||
|
const { data: cmdkRules } = useAlertRules();
|
||||||
|
|
||||||
// Merge environments from both the environments table and agent heartbeats
|
// Merge environments from both the environments table and agent heartbeats
|
||||||
const environments: string[] = useMemo(() => {
|
const environments: string[] = useMemo(() => {
|
||||||
const envSet = new Set<string>();
|
const envSet = new Set<string>();
|
||||||
@@ -569,6 +627,11 @@ function LayoutContent() {
|
|||||||
[adminUsers, adminGroups, adminRoles],
|
[adminUsers, adminGroups, adminRoles],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const alertingSearchData: SearchResult[] = useMemo(
|
||||||
|
() => buildAlertSearchData(cmdkAlerts, cmdkRules),
|
||||||
|
[cmdkAlerts, cmdkRules],
|
||||||
|
);
|
||||||
|
|
||||||
const operationalSearchData: SearchResult[] = useMemo(() => {
|
const operationalSearchData: SearchResult[] = useMemo(() => {
|
||||||
if (isAdminPage) return [];
|
if (isAdminPage) return [];
|
||||||
|
|
||||||
@@ -604,8 +667,8 @@ function LayoutContent() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [...catalogRef.current, ...exchangeItems, ...attributeItems];
|
return [...catalogRef.current, ...exchangeItems, ...attributeItems, ...alertingSearchData];
|
||||||
}, [isAdminPage, catalogRef.current, exchangeResults, debouncedQuery]);
|
}, [isAdminPage, catalogRef.current, exchangeResults, debouncedQuery, alertingSearchData]);
|
||||||
|
|
||||||
const searchData = isAdminPage ? adminSearchData : operationalSearchData;
|
const searchData = isAdminPage ? adminSearchData : operationalSearchData;
|
||||||
|
|
||||||
@@ -653,6 +716,11 @@ function LayoutContent() {
|
|||||||
const ADMIN_TAB_MAP: Record<string, string> = { user: 'users', group: 'groups', role: 'roles' };
|
const ADMIN_TAB_MAP: Record<string, string> = { user: 'users', group: 'groups', role: 'roles' };
|
||||||
|
|
||||||
const handlePaletteSelect = useCallback((result: any) => {
|
const handlePaletteSelect = useCallback((result: any) => {
|
||||||
|
if (result.category === 'alert' || result.category === 'alertRule') {
|
||||||
|
if (result.path) navigate(result.path);
|
||||||
|
setPaletteOpen(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (result.path) {
|
if (result.path) {
|
||||||
if (ADMIN_CATEGORIES.has(result.category)) {
|
if (ADMIN_CATEGORIES.has(result.category)) {
|
||||||
const itemId = result.id.split(':').slice(1).join(':');
|
const itemId = result.id.split(':').slice(1).join(':');
|
||||||
|
|||||||
Reference in New Issue
Block a user