Add resource

This commit is contained in:
Owen
2026-04-20 17:48:44 -07:00
parent 0a70896080
commit f38069623b
16 changed files with 511 additions and 44 deletions

View File

@@ -41,6 +41,7 @@ type AlertRuleRow = {
updatedAt: number;
siteIds: number[];
healthCheckIds: number[];
resourceIds: number[];
};
function ruleHref(orgId: string, ruleId: number) {
@@ -53,10 +54,14 @@ function sourceSummary(
) {
if (
rule.eventType === "site_online" ||
rule.eventType === "site_offline"
rule.eventType === "site_offline" ||
rule.eventType === "site_toggle"
) {
return t("alertingSummarySites", { count: rule.siteIds.length });
}
if (rule.eventType.startsWith("resource_")) {
return t("alertingSummaryResources", { count: rule.resourceIds.length });
}
return t("alertingSummaryHealthChecks", {
count: rule.healthCheckIds.length
});
@@ -79,6 +84,12 @@ function triggerLabel(
return t("alertingTriggerHcUnhealthy");
case "health_check_toggle":
return t("alertingTriggerHcToggle");
case "resource_healthy":
return t("alertingTriggerResourceHealthy");
case "resource_unhealthy":
return t("alertingTriggerResourceUnhealthy");
case "resource_toggle":
return t("alertingTriggerResourceToggle");
default:
return rule.eventType;
}

View File

@@ -275,6 +275,93 @@ function HealthCheckMultiSelect({
);
}
function ResourceMultiSelect({
orgId,
value,
onChange
}: {
orgId: string;
value: number[];
onChange: (v: number[]) => void;
}) {
const t = useTranslations();
const [open, setOpen] = useState(false);
const [q, setQ] = useState("");
const [debounced] = useDebounce(q, 150);
const { data: resources = [] } = useQuery(
orgQueries.resources({ orgId, query: debounced, perPage: 10 })
);
const shown = useMemo(() => {
return resources;
}, [resources]);
const toggle = (id: number) => {
if (value.includes(id)) {
onChange(value.filter((x) => x !== id));
} else {
onChange([...value, id]);
}
};
const summary =
value.length === 0
? t("alertingSelectResources")
: t("alertingResourcesSelected", { count: value.length });
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
type="button"
variant="outline"
role="combobox"
className="w-full justify-between font-normal"
>
<span className="truncate">{summary}</span>
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent
className="w-[var(--radix-popover-trigger-width)] p-0"
align="start"
>
<Command shouldFilter={false}>
<CommandInput
placeholder={t("alertingSelectResources")}
value={q}
onValueChange={setQ}
/>
<CommandList>
<CommandEmpty>
{t("alertingResourcesEmpty")}
</CommandEmpty>
<CommandGroup>
{shown.map((r) => (
<CommandItem
key={r.resourceId}
value={`${r.resourceId}:${r.name}`}
onSelect={() => toggle(r.resourceId)}
className="cursor-pointer"
>
<Checkbox
checked={value.includes(r.resourceId)}
className="mr-2 pointer-events-none shrink-0"
aria-hidden
tabIndex={-1}
/>
<span className="truncate">{r.name}</span>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}
export function ActionBlock({
orgId,
index,
@@ -895,6 +982,18 @@ export function AlertRuleSourceFields({
shouldValidate: true
});
}
} else if (next === "resource") {
if (
curTrigger !== "resource_healthy" &&
curTrigger !== "resource_unhealthy" &&
curTrigger !== "resource_toggle"
) {
setValue(
"trigger",
"resource_unhealthy",
{ shouldValidate: true }
);
}
} else if (
curTrigger !== "health_check_healthy" &&
curTrigger !== "health_check_unhealthy" &&
@@ -920,6 +1019,9 @@ export function AlertRuleSourceFields({
<SelectItem value="health_check">
{t("alertingSourceHealthCheck")}
</SelectItem>
<SelectItem value="resource">
{t("alertingSourceResource")}
</SelectItem>
</SelectContent>
</Select>
<FormMessage />
@@ -942,6 +1044,22 @@ export function AlertRuleSourceFields({
</FormItem>
)}
/>
) : sourceType === "resource" ? (
<FormField
control={control}
name="resourceIds"
render={({ field }) => (
<FormItem>
<FormLabel>{t("alertingPickResources")}</FormLabel>
<ResourceMultiSelect
orgId={orgId}
value={field.value}
onChange={field.onChange}
/>
<FormMessage />
</FormItem>
)}
/>
) : (
<FormField
control={control}
@@ -1002,6 +1120,18 @@ export function AlertRuleTriggerFields({
{t("alertingTriggerSiteToggle")}
</SelectItem>
</>
) : sourceType === "resource" ? (
<>
<SelectItem value="resource_healthy">
{t("alertingTriggerResourceHealthy")}
</SelectItem>
<SelectItem value="resource_unhealthy">
{t("alertingTriggerResourceUnhealthy")}
</SelectItem>
<SelectItem value="resource_toggle">
{t("alertingTriggerResourceToggle")}
</SelectItem>
</>
) : (
<>
<SelectItem value="health_check_healthy">

View File

@@ -82,6 +82,12 @@ function summarizeSource(v: AlertRuleFormValues, t: AlertRuleT) {
}
return t("alertingSummarySites", { count: v.siteIds.length });
}
if (v.sourceType === "resource") {
if (v.resourceIds.length === 0) {
return t("alertingNodeNotConfigured");
}
return t("alertingSummaryResources", { count: v.resourceIds.length });
}
if (v.healthCheckIds.length === 0) {
return t("alertingNodeNotConfigured");
}
@@ -102,6 +108,12 @@ function summarizeTrigger(v: AlertRuleFormValues, t: AlertRuleT) {
return t("alertingTriggerHcUnhealthy");
case "health_check_toggle":
return t("alertingTriggerHcToggle");
case "resource_healthy":
return t("alertingTriggerResourceHealthy");
case "resource_unhealthy":
return t("alertingTriggerResourceUnhealthy");
case "resource_toggle":
return t("alertingTriggerResourceToggle");
default:
return v.trigger;
}
@@ -338,6 +350,8 @@ export default function AlertRuleGraphEditor({
useWatch({ control: form.control, name: "siteIds" }) ?? [];
const wHealthCheckIds =
useWatch({ control: form.control, name: "healthCheckIds" }) ?? [];
const wResourceIds =
useWatch({ control: form.control, name: "resourceIds" }) ?? [];
const wTrigger =
useWatch({ control: form.control, name: "trigger" }) ??
"site_offline";
@@ -351,6 +365,7 @@ export default function AlertRuleGraphEditor({
sourceType: wSourceType,
siteIds: wSiteIds,
healthCheckIds: wHealthCheckIds,
resourceIds: wResourceIds,
trigger: wTrigger,
actions: wActions
}),
@@ -360,6 +375,7 @@ export default function AlertRuleGraphEditor({
wSourceType,
wSiteIds,
wHealthCheckIds,
wResourceIds,
wTrigger,
wActions
]