diff --git a/src/api/services/playbooks.ts b/src/api/services/playbooks.ts index d52ae0f79..c4ff791e2 100644 --- a/src/api/services/playbooks.ts +++ b/src/api/services/playbooks.ts @@ -219,3 +219,10 @@ export async function getPlaybookRuns({ ); return res; } + +export async function approvePlaybookRun(id: string) { + const res = await PlaybookAPI.post<{ + message: string; + }>(`/run/approve/${id}`); + return res.data; +} diff --git a/src/api/types/playbooks.ts b/src/api/types/playbooks.ts index 157b79884..98c3aa763 100644 --- a/src/api/types/playbooks.ts +++ b/src/api/types/playbooks.ts @@ -12,7 +12,8 @@ export type PlaybookRunStatus = | "completed" | "failed" | "pending" - | "waiting"; + | "waiting" + | "pending_approval"; export type PlaybookRunActionStatus = | "completed" diff --git a/src/components/Playbooks/Runs/Actions/PlaybookRunsActions.tsx b/src/components/Playbooks/Runs/Actions/PlaybookRunsActions.tsx index 75fa25ff5..0aadcf24c 100644 --- a/src/components/Playbooks/Runs/Actions/PlaybookRunsActions.tsx +++ b/src/components/Playbooks/Runs/Actions/PlaybookRunsActions.tsx @@ -20,9 +20,13 @@ import ShowPlaybookRunsParams from "./ShowParamaters/ShowPlaybookRunsParams"; type PlaybookRunActionsProps = { data: PlaybookRunWithActions; + refetch?: () => void; }; -export default function PlaybookRunsActions({ data }: PlaybookRunActionsProps) { +export default function PlaybookRunsActions({ + data, + refetch = () => {} +}: PlaybookRunActionsProps) { const [selectedAction, setSelectedAction] = useState< PlaybookRunAction | undefined >(() => { @@ -69,7 +73,15 @@ export default function PlaybookRunsActions({ data }: PlaybookRunActionsProps) { } + value={ + + } /> void; + open: boolean; + playbookTitle: string; + playbookRunId: string; + refetch?: () => void; +}; + +export default function ApprovePlaybookRunModal({ + onClose, + open, + playbookTitle, + playbookRunId, + refetch = () => {} +}: ApprovePlaybookRunModalProps) { + const { mutate: approve, isLoading } = useMutation({ + mutationFn: (id: string) => { + return approvePlaybookRun(id); + }, + onError: (error: AxiosError) => { + console.error("Failed to approve playbook run", error); + toastError(`Failed to approve playbook run: ${error.message}`); + }, + onSuccess: () => { + onClose(); + refetch(); + } + }); + + return ( + Are you sure you want to approve this playbook run?

} + onConfirm={() => approve(playbookRunId)} + open={open} + onClose={onClose} + isOpen={open} + yesLabel={isLoading ? "Approving..." : "Approve"} + closeLabel="Cancel" + /> + ); +} diff --git a/src/components/Playbooks/Runs/Filter/PlaybookStatusDropdown.tsx b/src/components/Playbooks/Runs/Filter/PlaybookStatusDropdown.tsx index fe2936161..bd4e1ffeb 100644 --- a/src/components/Playbooks/Runs/Filter/PlaybookStatusDropdown.tsx +++ b/src/components/Playbooks/Runs/Filter/PlaybookStatusDropdown.tsx @@ -32,6 +32,11 @@ const options: StateOption[] = [ icon: statusIconMap["pending"], label: "Pending", value: "pending" + }, + { + icon: statusIconMap["pending_approval"], + label: "Pending Approval", + value: "pending_approval" } ].sort((a, b) => a.label.localeCompare(b.label)); diff --git a/src/components/Playbooks/Runs/PlaybookRunsList.tsx b/src/components/Playbooks/Runs/PlaybookRunsList.tsx index 2fdcf2e58..4acfe8129 100644 --- a/src/components/Playbooks/Runs/PlaybookRunsList.tsx +++ b/src/components/Playbooks/Runs/PlaybookRunsList.tsx @@ -65,9 +65,19 @@ const playbookRunsTableColumns: MRT_ColumnDef[] = [ { header: "Status", accessorKey: "status", - Cell: ({ cell }) => { + Cell: ({ cell, row }) => { const status = cell.getValue(); - return ; + const playbookRunId = row.original.id; + const title = + row.original.playbooks?.title ?? row.original.playbooks?.name!; + + return ( + + ); } }, { diff --git a/src/components/Playbooks/Runs/PlaybookRunsStatus.tsx b/src/components/Playbooks/Runs/PlaybookRunsStatus.tsx index 113c9ad37..bcee0c9af 100644 --- a/src/components/Playbooks/Runs/PlaybookRunsStatus.tsx +++ b/src/components/Playbooks/Runs/PlaybookRunsStatus.tsx @@ -1,9 +1,55 @@ +import { Button } from "@flanksource-ui/ui/Buttons/Button"; +import { useState } from "react"; +import { Tooltip } from "react-tooltip"; import { PlaybookRunsStatusProps, PlaybookStatusIcon } from "../../../ui/Icons/PlaybookStatusIcon"; +import ApprovePlaybookRunModal from "./ApprovePlaybookRunModal"; + +export function PlaybookStatusDescription({ + status, + playbookTitle, + showApproveButton = false, + playbookRunId, + refetch = () => {} +}: PlaybookRunsStatusProps & { + playbookTitle: string; + showApproveButton?: boolean; + playbookRunId: string; + refetch?: () => void; +}) { + const [showApprovalModal, setShowApprovalModal] = useState(false); + + if (status === "pending_approval" && showApproveButton) { + return ( + <> + + { + setShowApprovalModal(false); + }} + open={showApprovalModal} + playbookTitle={playbookTitle} + playbookRunId={playbookRunId} + refetch={refetch} + /> + + + ); + } -export function PlaybookStatusDescription({ status }: PlaybookRunsStatusProps) { return (
diff --git a/src/pages/playbooks/PlaybookRunsDetails.tsx b/src/pages/playbooks/PlaybookRunsDetails.tsx index 576561a46..7561718a1 100644 --- a/src/pages/playbooks/PlaybookRunsDetails.tsx +++ b/src/pages/playbooks/PlaybookRunsDetails.tsx @@ -115,7 +115,7 @@ export default function PlaybookRunsDetailsPage() {
{playbookRun ? ( - + ) : ( )} diff --git a/src/ui/AlertDialog/ConfirmationPromptDialog.tsx b/src/ui/AlertDialog/ConfirmationPromptDialog.tsx index 388c5ca86..00efdb2c0 100644 --- a/src/ui/AlertDialog/ConfirmationPromptDialog.tsx +++ b/src/ui/AlertDialog/ConfirmationPromptDialog.tsx @@ -1,13 +1,13 @@ import { Dialog } from "@headlessui/react"; import { XIcon } from "@heroicons/react/outline"; import clsx from "clsx"; -import { ComponentProps } from "react"; +import React, { ComponentProps } from "react"; import { FaCircleNotch } from "react-icons/fa"; type ConfirmationPromptDialogProps = { isOpen: boolean; title: string; - description: string; + description: React.ReactNode; onClose: () => void; onConfirm: () => void; yesLabel?: string; diff --git a/src/ui/Icons/PlaybookStatusIcon.tsx b/src/ui/Icons/PlaybookStatusIcon.tsx index 9d64025c2..35f4b4a5a 100644 --- a/src/ui/Icons/PlaybookStatusIcon.tsx +++ b/src/ui/Icons/PlaybookStatusIcon.tsx @@ -17,31 +17,34 @@ export const statusIconMap: Record< React.ReactElement > = { completed: ( - + ), cancelled: ( - + ), failed: ( - + ), pending: ( + + ), + pending_approval: ( ), waiting: ( - + ), running: ( - + ), scheduled: ( - + ), sleeping: ( - + ), skipped: ( - + ) };