/** * CreateActionDialog — dialog for creating a new user-defined fail2ban action. * * Asks for an action name and optional lifecycle commands, then calls * ``POST /api/config/actions`` on confirmation. */ import { useCallback, useEffect, useState } from "react"; import { Button, Dialog, DialogActions, DialogBody, DialogContent, DialogSurface, DialogTitle, Field, Input, MessageBar, MessageBarBody, Spinner, Text, Textarea, tokens, } from "@fluentui/react-components"; import type { ActionConfig, ActionCreateRequest } from "../../types/config"; import { ApiError } from "../../api/client"; // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- export interface CreateActionDialogProps { /** Whether the dialog is currently open. */ open: boolean; /** Called when the dialog should close without taking action. */ onClose: () => void; /** * Called when the form is submitted with valid dialog data. * * @param payload - Create request payload. */ onCreateAction: (payload: ActionCreateRequest) => Promise; /** * Called after the action has been successfully created. * * @param action - The newly created ActionConfig returned by the API. */ onCreate: (action: ActionConfig) => void; } // --------------------------------------------------------------------------- // Component // --------------------------------------------------------------------------- /** * Dialog for creating a new action in ``action.d/{name}.local``. * * The name field accepts a plain base name (e.g. ``my-action``); the ``.local`` * extension is added by the backend. * * @param props - Component props. * @returns JSX element. */ export function CreateActionDialog({ open, onClose, onCreateAction, onCreate, }: CreateActionDialogProps): React.JSX.Element { const [name, setName] = useState(""); const [actionban, setActionban] = useState(""); const [actionunban, setActionunban] = useState(""); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); // Reset form when the dialog opens. useEffect(() => { if (open) { setName(""); setActionban(""); setActionunban(""); setError(null); } }, [open]); const handleClose = useCallback((): void => { if (submitting) return; onClose(); }, [submitting, onClose]); const handleConfirm = useCallback((): void => { const trimmedName = name.trim(); if (!trimmedName || submitting) return; const req: ActionCreateRequest = { name: trimmedName, actionban: actionban.trim() || null, actionunban: actionunban.trim() || null, }; setSubmitting(true); setError(null); onCreateAction(req) .then((action) => { onCreate(action); }) .catch((err: unknown) => { setError( err instanceof ApiError ? err.message : "Failed to create action.", ); }) .finally(() => { setSubmitting(false); }); }, [name, actionban, actionunban, submitting, onCreate]); const canConfirm = name.trim() !== "" && !submitting; return ( { if (!data.open) handleClose(); }}> Create Action Creates a new action definition at{" "} action.d/<name>.local. {error !== null && ( {error} )} { setName(d.value); }} placeholder="my-action" disabled={submitting} />