mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-17 06:24:32 +00:00
Add descriptions and adjust ui
This commit is contained in:
@@ -1412,6 +1412,10 @@
|
|||||||
"alertingActionWebhook": "Webhook",
|
"alertingActionWebhook": "Webhook",
|
||||||
"alertingActionWebhookDescription": "Send an HTTP request to a custom endpoint",
|
"alertingActionWebhookDescription": "Send an HTTP request to a custom endpoint",
|
||||||
"alertingExternalIntegration": "External Integration",
|
"alertingExternalIntegration": "External Integration",
|
||||||
|
"alertingExternalPagerDutyDescription": "Send alerts to PagerDuty for incident management",
|
||||||
|
"alertingExternalOpsgenieDescription": "Route alerts to Opsgenie for on-call management",
|
||||||
|
"alertingExternalServiceNowDescription": "Create ServiceNow incidents from alert events",
|
||||||
|
"alertingExternalIncidentIoDescription": "Trigger Incident.io workflows from alert events",
|
||||||
"alertingActionType": "Action type",
|
"alertingActionType": "Action type",
|
||||||
"alertingNotifyUsers": "Users",
|
"alertingNotifyUsers": "Users",
|
||||||
"alertingNotifyRoles": "Roles",
|
"alertingNotifyRoles": "Roles",
|
||||||
|
|||||||
@@ -52,134 +52,107 @@ import type { Control, UseFormReturn } from "react-hook-form";
|
|||||||
import { useFormContext, useWatch } from "react-hook-form";
|
import { useFormContext, useWatch } from "react-hook-form";
|
||||||
import { useDebounce } from "use-debounce";
|
import { useDebounce } from "use-debounce";
|
||||||
|
|
||||||
const EXTERNAL_INTEGRATIONS = [
|
export function AddActionPanel({
|
||||||
{
|
|
||||||
id: "pagerduty",
|
|
||||||
name: "PagerDuty",
|
|
||||||
logo: "/third-party/pgd.png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "opsgenie",
|
|
||||||
name: "Opsgenie",
|
|
||||||
logo: "/third-party/opsgenie.png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "servicenow",
|
|
||||||
name: "ServiceNow",
|
|
||||||
logo: "/third-party/servicenow.png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "incidentio",
|
|
||||||
name: "Incident.io",
|
|
||||||
logo: "/third-party/incidentio.png"
|
|
||||||
}
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
const EXTERNAL_IDS = EXTERNAL_INTEGRATIONS.map((i) => i.id);
|
|
||||||
|
|
||||||
export function DropdownAddAction({
|
|
||||||
onAdd
|
onAdd
|
||||||
}: {
|
}: {
|
||||||
onAdd: (type: AlertRuleFormAction["type"]) => void;
|
onAdd: (type: AlertRuleFormAction["type"]) => void;
|
||||||
}) {
|
}) {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const [salesFor, setSalesFor] = useState<string | null>(null);
|
|
||||||
|
const EXTERNAL_INTEGRATIONS = [
|
||||||
|
{
|
||||||
|
id: "pagerduty",
|
||||||
|
name: "PagerDuty",
|
||||||
|
logo: "/third-party/pgd.png",
|
||||||
|
description: "Send alerts to PagerDuty for incident management",
|
||||||
|
descriptionKey: t("alertingExternalPagerDutyDescription")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "opsgenie",
|
||||||
|
name: "Opsgenie",
|
||||||
|
logo: "/third-party/opsgenie.png",
|
||||||
|
description: "Route alerts to Opsgenie for on-call management",
|
||||||
|
descriptionKey: t("alertingExternalOpsgenieDescription")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "servicenow",
|
||||||
|
name: "ServiceNow",
|
||||||
|
logo: "/third-party/servicenow.png",
|
||||||
|
description: "Create ServiceNow incidents from alert events",
|
||||||
|
descriptionKey: t("alertingExternalServiceNowDescription")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "incidentio",
|
||||||
|
name: "Incident.io",
|
||||||
|
logo: "/third-party/incidentio.png",
|
||||||
|
description: "Trigger Incident.io workflows from alert events",
|
||||||
|
descriptionKey: t("alertingExternalIncidentIoDescription")
|
||||||
|
}
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const EXTERNAL_IDS = EXTERNAL_INTEGRATIONS.map((i) => i.id);
|
||||||
|
|
||||||
|
const [selected, setSelected] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const isPremiumSelected =
|
||||||
|
selected !== null && EXTERNAL_IDS.includes(selected as any);
|
||||||
|
const isBuiltInSelected = selected !== null && !isPremiumSelected;
|
||||||
|
|
||||||
|
const actionTypeOptions = [
|
||||||
|
{
|
||||||
|
id: "notify",
|
||||||
|
title: t("alertingActionNotify"),
|
||||||
|
description: t("alertingActionNotifyDescription"),
|
||||||
|
icon: <Bell className="h-5 w-5" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "webhook",
|
||||||
|
title: t("alertingActionWebhook"),
|
||||||
|
description: t("alertingActionWebhookDescription"),
|
||||||
|
icon: <Globe className="h-5 w-5" />
|
||||||
|
},
|
||||||
|
...EXTERNAL_INTEGRATIONS.map((integration) => ({
|
||||||
|
id: integration.id,
|
||||||
|
title: integration.name,
|
||||||
|
description: integration.description,
|
||||||
|
icon: (
|
||||||
|
<img
|
||||||
|
src={integration.logo}
|
||||||
|
alt={integration.name}
|
||||||
|
className="h-5 w-5 object-contain"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
if (!isBuiltInSelected) return;
|
||||||
|
onAdd(selected as AlertRuleFormAction["type"]);
|
||||||
|
setSelected(null);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover
|
<div className="space-y-3">
|
||||||
open={open}
|
<StrategySelect
|
||||||
onOpenChange={(o) => {
|
options={actionTypeOptions}
|
||||||
setOpen(o);
|
value={selected}
|
||||||
if (!o) setSalesFor(null);
|
cols={2}
|
||||||
}}
|
onChange={(v) => setSelected(v)}
|
||||||
>
|
/>
|
||||||
<PopoverTrigger asChild>
|
{isPremiumSelected && <ContactSalesBanner />}
|
||||||
<Button type="button" variant="outline" size="sm">
|
{!isPremiumSelected && (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="sm"
|
||||||
|
disabled={!isBuiltInSelected}
|
||||||
|
onClick={handleAdd}
|
||||||
|
>
|
||||||
<Plus className="h-4 w-4 mr-1" />
|
<Plus className="h-4 w-4 mr-1" />
|
||||||
{t("alertingAddAction")}
|
{t("alertingAddAction")}
|
||||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
)}
|
||||||
<PopoverContent
|
</div>
|
||||||
className={salesFor ? "w-80 p-3" : "p-0 w-52"}
|
|
||||||
align="start"
|
|
||||||
>
|
|
||||||
{salesFor ? (
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<img
|
|
||||||
src={
|
|
||||||
EXTERNAL_INTEGRATIONS.find(
|
|
||||||
(i) => i.id === salesFor
|
|
||||||
)?.logo
|
|
||||||
}
|
|
||||||
alt={salesFor}
|
|
||||||
className="h-5 w-5 object-contain"
|
|
||||||
/>
|
|
||||||
<span className="text-sm font-medium">
|
|
||||||
{
|
|
||||||
EXTERNAL_INTEGRATIONS.find(
|
|
||||||
(i) => i.id === salesFor
|
|
||||||
)?.name
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<ContactSalesBanner />
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="w-full"
|
|
||||||
onClick={() => setSalesFor(null)}
|
|
||||||
>
|
|
||||||
← Back
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<Command>
|
|
||||||
<CommandList>
|
|
||||||
<CommandGroup heading="Built-in">
|
|
||||||
<CommandItem
|
|
||||||
onSelect={() => {
|
|
||||||
onAdd("notify");
|
|
||||||
setOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Bell className="h-4 w-4 mr-2 text-muted-foreground" />
|
|
||||||
{t("alertingActionNotify")}
|
|
||||||
</CommandItem>
|
|
||||||
<CommandItem
|
|
||||||
onSelect={() => {
|
|
||||||
onAdd("webhook");
|
|
||||||
setOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Globe className="h-4 w-4 mr-2 text-muted-foreground" />
|
|
||||||
{t("alertingActionWebhook")}
|
|
||||||
</CommandItem>
|
|
||||||
</CommandGroup>
|
|
||||||
<CommandGroup heading="Integrations">
|
|
||||||
{EXTERNAL_INTEGRATIONS.map((integration) => (
|
|
||||||
<CommandItem
|
|
||||||
key={integration.id}
|
|
||||||
onSelect={() =>
|
|
||||||
setSalesFor(integration.id)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={integration.logo}
|
|
||||||
alt={integration.name}
|
|
||||||
className="h-4 w-4 mr-2 object-contain"
|
|
||||||
/>
|
|
||||||
{integration.name}
|
|
||||||
</CommandItem>
|
|
||||||
))}
|
|
||||||
</CommandGroup>
|
|
||||||
</CommandList>
|
|
||||||
</Command>
|
|
||||||
)}
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -470,42 +443,19 @@ export function ActionBlock({
|
|||||||
}) {
|
}) {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
const type = useWatch({ control, name: `actions.${index}.type` });
|
const type = useWatch({ control, name: `actions.${index}.type` });
|
||||||
const [displayType, setDisplayType] = useState<string>(type ?? "notify");
|
|
||||||
|
|
||||||
useEffect(() => {
|
const typeHeader =
|
||||||
if (!EXTERNAL_IDS.includes(displayType as any)) {
|
type === "notify" ? (
|
||||||
setDisplayType(type ?? "notify");
|
<div className="flex items-center gap-2 text-sm font-medium">
|
||||||
}
|
<Bell className="h-4 w-4 text-muted-foreground" />
|
||||||
}, [type]);
|
{t("alertingActionNotify")}
|
||||||
|
</div>
|
||||||
const isPremium = EXTERNAL_IDS.includes(displayType as any);
|
) : (
|
||||||
|
<div className="flex items-center gap-2 text-sm font-medium">
|
||||||
const actionTypeOptions = [
|
<Globe className="h-4 w-4 text-muted-foreground" />
|
||||||
{
|
{t("alertingActionWebhook")}
|
||||||
id: "notify",
|
</div>
|
||||||
title: t("alertingActionNotify"),
|
);
|
||||||
description: t("alertingActionNotifyDescription"),
|
|
||||||
icon: <Bell className="h-5 w-5" />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "webhook",
|
|
||||||
title: t("alertingActionWebhook"),
|
|
||||||
description: t("alertingActionWebhookDescription"),
|
|
||||||
icon: <Globe className="h-5 w-5" />
|
|
||||||
},
|
|
||||||
...EXTERNAL_INTEGRATIONS.map((integration) => ({
|
|
||||||
id: integration.id,
|
|
||||||
title: integration.name,
|
|
||||||
description: t("alertingExternalIntegration"),
|
|
||||||
icon: (
|
|
||||||
<img
|
|
||||||
src={integration.logo}
|
|
||||||
alt={integration.name}
|
|
||||||
className="h-5 w-5 object-contain"
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg border p-4 space-y-3 relative">
|
<div className="rounded-lg border p-4 space-y-3 relative">
|
||||||
@@ -520,44 +470,8 @@ export function ActionBlock({
|
|||||||
<Trash2 className="h-4 w-4 text-destructive" />
|
<Trash2 className="h-4 w-4 text-destructive" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<div className="space-y-2">
|
{typeHeader}
|
||||||
<Label className="text-sm font-medium">
|
{type === "notify" && (
|
||||||
{t("alertingActionType")}
|
|
||||||
</Label>
|
|
||||||
<StrategySelect
|
|
||||||
options={actionTypeOptions}
|
|
||||||
value={displayType}
|
|
||||||
cols={2}
|
|
||||||
onChange={(v) => {
|
|
||||||
setDisplayType(v);
|
|
||||||
if (!EXTERNAL_IDS.includes(v as any)) {
|
|
||||||
const nt = v as AlertRuleFormAction["type"];
|
|
||||||
if (nt === "notify") {
|
|
||||||
onUpdate({
|
|
||||||
type: "notify",
|
|
||||||
userTags: [],
|
|
||||||
roleTags: [],
|
|
||||||
emailTags: []
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
onUpdate({
|
|
||||||
type: "webhook",
|
|
||||||
url: "",
|
|
||||||
method: "POST",
|
|
||||||
headers: [],
|
|
||||||
authType: "none",
|
|
||||||
bearerToken: "",
|
|
||||||
basicCredentials: "",
|
|
||||||
customHeaderName: "",
|
|
||||||
customHeaderValue: ""
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{isPremium && <ContactSalesBanner />}
|
|
||||||
{!isPremium && type === "notify" && (
|
|
||||||
<NotifyActionFields
|
<NotifyActionFields
|
||||||
orgId={orgId}
|
orgId={orgId}
|
||||||
index={index}
|
index={index}
|
||||||
@@ -565,7 +479,7 @@ export function ActionBlock({
|
|||||||
form={form}
|
form={form}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!isPremium && type === "webhook" && (
|
{type === "webhook" && (
|
||||||
<WebhookActionFields
|
<WebhookActionFields
|
||||||
index={index}
|
index={index}
|
||||||
control={control}
|
control={control}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
ActionBlock,
|
ActionBlock,
|
||||||
|
AddActionPanel,
|
||||||
AlertRuleSourceFields,
|
AlertRuleSourceFields,
|
||||||
AlertRuleTriggerFields,
|
AlertRuleTriggerFields
|
||||||
DropdownAddAction
|
|
||||||
} from "@app/components/alert-rule-editor/AlertRuleFields";
|
} from "@app/components/alert-rule-editor/AlertRuleFields";
|
||||||
import { SettingsContainer } from "@app/components/Settings";
|
import { SettingsContainer } from "@app/components/Settings";
|
||||||
import { Button } from "@app/components/ui/button";
|
import { Button } from "@app/components/ui/button";
|
||||||
@@ -693,47 +693,43 @@ export default function AlertRuleGraphEditor({
|
|||||||
)}
|
)}
|
||||||
{isActionsSidebar && (
|
{isActionsSidebar && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
<span className="text-sm font-medium">
|
||||||
<span className="text-sm font-medium">
|
{t("alertingSectionActions")}
|
||||||
{t(
|
</span>
|
||||||
"alertingSectionActions"
|
<AddActionPanel
|
||||||
)}
|
onAdd={(type) => {
|
||||||
</span>
|
const newIndex =
|
||||||
<DropdownAddAction
|
fields.length;
|
||||||
onAdd={(type) => {
|
if (type === "notify") {
|
||||||
const newIndex =
|
append({
|
||||||
fields.length;
|
type: "notify",
|
||||||
if (type === "notify") {
|
userTags: [],
|
||||||
append({
|
roleTags: [],
|
||||||
type: "notify",
|
emailTags: []
|
||||||
userTags: [],
|
});
|
||||||
roleTags: [],
|
} else {
|
||||||
emailTags: []
|
append({
|
||||||
});
|
type: "webhook",
|
||||||
} else {
|
url: "",
|
||||||
append({
|
method: "POST",
|
||||||
type: "webhook",
|
headers: [
|
||||||
url: "",
|
{
|
||||||
method: "POST",
|
key: "",
|
||||||
headers: [
|
value: ""
|
||||||
{
|
}
|
||||||
key: "",
|
],
|
||||||
value: ""
|
authType: "none",
|
||||||
}
|
bearerToken: "",
|
||||||
],
|
basicCredentials: "",
|
||||||
authType: "none",
|
customHeaderName: "",
|
||||||
bearerToken: "",
|
customHeaderValue: ""
|
||||||
basicCredentials: "",
|
});
|
||||||
customHeaderName: "",
|
}
|
||||||
customHeaderValue: ""
|
setSelectedStep(
|
||||||
});
|
`action-${newIndex}`
|
||||||
}
|
);
|
||||||
setSelectedStep(
|
}}
|
||||||
`action-${newIndex}`
|
/>
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{fields.map((f, index) => (
|
{fields.map((f, index) => (
|
||||||
<ActionBlock
|
<ActionBlock
|
||||||
key={f.id}
|
key={f.id}
|
||||||
|
|||||||
Reference in New Issue
Block a user