diff --git a/.gitignore b/.gitignore index cdab398b1..f11aade2f 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,6 @@ middleware.ts /blob-report/ /playwright/.cache/ .bin/ + +.envrc +default.nix \ No newline at end of file diff --git a/src/api/query-hooks/useNotificationsQuery.ts b/src/api/query-hooks/useNotificationsQuery.ts index b5b32017b..f75fe713f 100644 --- a/src/api/query-hooks/useNotificationsQuery.ts +++ b/src/api/query-hooks/useNotificationsQuery.ts @@ -32,6 +32,22 @@ export function useNotificationsSummaryQuery( ); } +export function useGetAllNotifications() { + return useQuery( + ["notifications", "all"], + async () => { + const filter: NotificationQueryFilterOptions = { + pageIndex: 0, + pageSize: 1000, + sortBy: "name", + sortOrder: "asc" + }; + const response = await getNotificationsSummary(filter); + return response.data ?? []; + } + ); +} + export function useGetNotificationsByIDQuery( id: string, options?: UseQueryOptions diff --git a/src/api/services/permissions.ts b/src/api/services/permissions.ts index 60beb42d4..75f1ee9e2 100644 --- a/src/api/services/permissions.ts +++ b/src/api/services/permissions.ts @@ -69,6 +69,7 @@ export function fetchPermissions( "team:team_id(id, name, icon)", `person:person_id(${AVATAR_INFO})`, `createdBy:created_by(${AVATAR_INFO})`, + `notification:notification_id(id,name,namespace)`, `connection:connection_id(id,name,type)` ]; diff --git a/src/api/types/notifications.ts b/src/api/types/notifications.ts index 90f596227..bfbf425bd 100644 --- a/src/api/types/notifications.ts +++ b/src/api/types/notifications.ts @@ -6,6 +6,7 @@ import { Team, User } from "./users"; export type NotificationRules = { id: string; + namespace?: string; name: string; title?: string; events: string[]; @@ -68,6 +69,7 @@ export type NotificationSendHistory = { first_observed: string; source_event: string; resource_id: string; + playbook_run_id?: string; person_id?: string | undefined; error?: string | undefined; duration_millis?: number | undefined; diff --git a/src/api/types/permissions.ts b/src/api/types/permissions.ts index a4b32f4f0..cfc84864e 100644 --- a/src/api/types/permissions.ts +++ b/src/api/types/permissions.ts @@ -3,6 +3,7 @@ import { ConfigItem } from "./configs"; import { PlaybookSpec } from "./playbooks"; import { Topology } from "./topology"; import { Team, User } from "./users"; +import { NotificationRules } from "./notifications"; export type PermissionTable = { id: string; @@ -17,6 +18,7 @@ export type PermissionTable = { created_by: string; connection_id?: string; person_id?: string; + notification_id?: string; team_id?: string; updated_by: string; created_at: string; @@ -36,6 +38,7 @@ export type PermissionAPIResponse = PermissionTable & { playbook: Pick; team: Pick; connection: Pick; + notification: Pick; person: User; createdBy: User; }; diff --git a/src/api/types/playbooks.ts b/src/api/types/playbooks.ts index 79718870a..1204d4401 100644 --- a/src/api/types/playbooks.ts +++ b/src/api/types/playbooks.ts @@ -64,6 +64,7 @@ export interface PlaybookRun extends CreatedAt, Avatar, Agent { check_id?: string; config_id?: string; component_id?: string; + notification_send_id?: string; parameters?: Record; /* relationships */ playbooks?: PlaybookSpec; diff --git a/src/components/Forms/Formik/FormikNotificationDropdown.tsx b/src/components/Forms/Formik/FormikNotificationDropdown.tsx new file mode 100644 index 000000000..2f6c20e3e --- /dev/null +++ b/src/components/Forms/Formik/FormikNotificationDropdown.tsx @@ -0,0 +1,42 @@ +import { useMemo } from "react"; +import FormikSelectDropdown from "./FormikSelectDropdown"; +import { useGetAllNotifications } from "@flanksource-ui/api/query-hooks/useNotificationsQuery"; + +type FormikEventsDropdownProps = { + name: string; + label?: string; + required?: boolean; + hint?: string; + className?: string; +}; + +export default function FormikNotificationDropdown({ + name, + label, + required = false, + hint, + className = "flex flex-col space-y-2 py-2" +}: FormikEventsDropdownProps) { + const { data: notificationRules, isLoading } = useGetAllNotifications(); + + const options = useMemo( + () => + notificationRules?.map((rule) => ({ + label: rule.name, + value: rule.id + })), + [notificationRules] + ); + + return ( + + ); +} diff --git a/src/components/Notifications/NotificationSendHistory.tsx b/src/components/Notifications/NotificationSendHistory.tsx index 271136025..7018ed69a 100644 --- a/src/components/Notifications/NotificationSendHistory.tsx +++ b/src/components/Notifications/NotificationSendHistory.tsx @@ -12,7 +12,7 @@ const notificationSendHistoryColumns: MRT_ColumnDef { const dateString = row.original.created_at; const count = row.original.count; @@ -32,7 +32,7 @@ const notificationSendHistoryColumns: MRT_ColumnDef { return (
{sourceEvent}; } + }, + { + header: "Recipient", + size: 200, + Cell: ({ row }) => { + const { playbook_run_id, person_id } = row.original; + + const recipient = playbook_run_id + ? `playbook/${playbook_run_id}` + : person_id + ? `person/${person_id}` + : ""; + + // TODO: use a proper component. + // show connection recipient but the backend doesn't currently store that. + return {recipient}; + } } // { // header: "Body", diff --git a/src/components/Notifications/NotificationsStatusCell.tsx b/src/components/Notifications/NotificationsStatusCell.tsx index f04a18715..ea472ef00 100644 --- a/src/components/Notifications/NotificationsStatusCell.tsx +++ b/src/components/Notifications/NotificationsStatusCell.tsx @@ -1,6 +1,6 @@ import { NotificationSendHistoryApiResponse } from "@flanksource-ui/api/types/notifications"; import { MRTCellProps } from "@flanksource-ui/ui/MRTDataTable/MRTCellProps"; -import { FaBan, FaBellSlash, FaDotCircle } from "react-icons/fa"; +import { FaBellSlash, FaDotCircle } from "react-icons/fa"; export const notificationSendHistoryStatus = { sent: { @@ -15,17 +15,35 @@ export const notificationSendHistoryStatus = { value: "error", id: "error" }, + sending: { + label: "Sending", + Icon: , + value: "sending", + id: "sending" + }, pending: { label: "Pending", Icon: , value: "pending", id: "pending" }, - cancelled: { - label: "Cancelled", - Icon: , - value: "cancelled", - id: "cancelled" + pending_playbook_run: { + label: "Pending Playbook Run", + Icon: , + value: "pending_playbook_run", + id: "pending_playbook_run" + }, + pending_playbook_completion: { + label: "Playbook In Progress", + Icon: , + value: "pending_playbook_completion", + id: "pending_playbook_completion" + }, + "evaluating-waitfor": { + label: "Evaluating WaitFor", + Icon: , + value: "evaluating-waitfor", + id: "evaluating-waitfor" }, "repeat-interval": { label: "Repeated", @@ -38,6 +56,12 @@ export const notificationSendHistoryStatus = { Icon: , value: "silenced", id: "silenced" + }, + skipped: { + label: "Skipped", + Icon: , + value: "skipped", + id: "skipped" } }; diff --git a/src/components/Permissions/ManagePermissions/Forms/PermissionForm.tsx b/src/components/Permissions/ManagePermissions/Forms/PermissionForm.tsx index 899ac1c72..cb37e64b6 100644 --- a/src/components/Permissions/ManagePermissions/Forms/PermissionForm.tsx +++ b/src/components/Permissions/ManagePermissions/Forms/PermissionForm.tsx @@ -120,6 +120,7 @@ export default function PermissionForm({ updated_at: data?.updated_at, updated_by: data?.updated_by, id: data?.id, + notification_id: data?.notification_id, person_id: data?.person_id, team_id: data?.team_id, until: data?.until, diff --git a/src/components/Permissions/ManagePermissions/Forms/PermissionSubjectControls.tsx b/src/components/Permissions/ManagePermissions/Forms/PermissionSubjectControls.tsx index 74405b7df..0a2fa6f13 100644 --- a/src/components/Permissions/ManagePermissions/Forms/PermissionSubjectControls.tsx +++ b/src/components/Permissions/ManagePermissions/Forms/PermissionSubjectControls.tsx @@ -1,5 +1,6 @@ import FormikPeopleDropdown from "@flanksource-ui/components/Forms/Formik/FormikPeopleDropdown"; import FormikTeamsDropdown from "@flanksource-ui/components/Forms/Formik/FormikTeamsDropdown"; +import FormikNotificationDropdown from "@flanksource-ui/components/Forms/Formik/FormikNotificationDropdown"; import { Switch } from "@flanksource-ui/ui/FormControls/Switch"; import { useFormikContext } from "formik"; import { useEffect, useState } from "react"; @@ -9,10 +10,14 @@ export default function PermissionsSubjectControls() { const teamId = values.team_id; const personId = values.person_id; + const notificationId = values.notification_id; - const [switchOption, setSwitchOption] = useState<"Team" | "Person">(() => { + const [switchOption, setSwitchOption] = useState< + "Team" | "Person" | "Notification" + >(() => { if (teamId) return "Team"; if (personId) return "Person"; + if (notificationId) return "Notification"; return "Team"; }); @@ -21,16 +26,18 @@ export default function PermissionsSubjectControls() { setSwitchOption("Team"); } else if (personId) { setSwitchOption("Person"); + } else if (notificationId) { + setSwitchOption("Notification"); } - }, [teamId, personId]); + }, [teamId, personId, notificationId]); return (
- +
- {switchOption === "Team" ? ( + {switchOption === "Team" && ( - ) : ( + )} + {switchOption === "Person" && ( )} + {switchOption === "Notification" && ( + + )}
); diff --git a/src/components/Permissions/PermissionsTable.tsx b/src/components/Permissions/PermissionsTable.tsx index b83eb3d2d..c35309c0f 100644 --- a/src/components/Permissions/PermissionsTable.tsx +++ b/src/components/Permissions/PermissionsTable.tsx @@ -12,6 +12,7 @@ import { TopologyLink } from "../Topology/TopologyLink"; import { permissionObjectList } from "./ManagePermissions/Forms/FormikPermissionSelectResourceFields"; import { permissionsActionsList } from "./PermissionsView"; import { BsBan } from "react-icons/bs"; +import { Link } from "react-router-dom"; const permissionsTableColumns: MRT_ColumnDef[] = [ { @@ -55,6 +56,7 @@ const permissionsTableColumns: MRT_ColumnDef[] = [ Cell: ({ row }) => { const team = row.original.team; const person = row.original.person; + const notification = row.original.notification; if (person) { return ( @@ -74,6 +76,23 @@ const permissionsTableColumns: MRT_ColumnDef[] = [ ); } + if (notification) { + return ( +
+ + + {"notification: " + + (notification.namespace ? notification.namespace + "/" : "") + + notification.name} + + +
+ ); + } + return null; } }, diff --git a/src/components/Permissions/PermissionsView.tsx b/src/components/Permissions/PermissionsView.tsx index c0f32e0d0..27c0f456a 100644 --- a/src/components/Permissions/PermissionsView.tsx +++ b/src/components/Permissions/PermissionsView.tsx @@ -15,14 +15,14 @@ import PermissionForm from "./ManagePermissions/Forms/PermissionForm"; import PermissionsTable from "./PermissionsTable"; export const permissionsActionsList: FormikSelectDropdownOption[] = [ - { value: "ActionRead", label: "read" }, - { value: "ActionUpdate", label: "update" }, - { value: "ActionCreate", label: "create" }, - { value: "ActionDelete", label: "delete" }, - { value: "ActionAll", label: "*" }, - { value: "ActionCRUD", label: "create,read,update,delete" }, - { value: "ActionRun", label: "playbook:run" }, - { value: "ActionApprove", label: "playbook:approve" } + { value: "read", label: "read" }, + { value: "update", label: "update" }, + { value: "create", label: "create" }, + { value: "delete", label: "delete" }, + { value: "*", label: "*" }, + { value: "create,read,update,delete", label: "create,read,update,delete" }, + { value: "playbook:run", label: "playbook:run" }, + { value: "playbook:approve", label: "playbook:approve" } ]; type PermissionsViewProps = { diff --git a/src/components/Playbooks/Runs/Actions/PlaybookRunsActions.tsx b/src/components/Playbooks/Runs/Actions/PlaybookRunsActions.tsx index fec66dd51..f57614539 100644 --- a/src/components/Playbooks/Runs/Actions/PlaybookRunsActions.tsx +++ b/src/components/Playbooks/Runs/Actions/PlaybookRunsActions.tsx @@ -136,6 +136,19 @@ export default function PlaybookRunsActions({ } /> )} + {data.notification_send_id && ( + + {"Notification"} + + } + /> + )}