diff --git a/frontend/src/pages/service-tabs/ActionsTab.tsx b/frontend/src/pages/service-tabs/ActionsTab.tsx new file mode 100644 index 0000000..a9e7cf4 --- /dev/null +++ b/frontend/src/pages/service-tabs/ActionsTab.tsx @@ -0,0 +1,456 @@ +/** + * ActionsTab — operational content for the ssh_tasks service page. + * + * Lifted from the old top-level `pages/Actions.tsx`. The `instance` prop + * provides the active ssh_tasks service id, which is used as the default run + * service. The page-level header is removed (the service page provides it). + * The task editor dialog, saved-task rail, and run history are preserved. + */ +import type { ReactNode } from "react"; +import { useMemo, useState } from "react"; +import type { SavedTask, SavedTaskInput, ServiceInstance } from "../../types"; +import { + useDeleteTask, + useRunTask, + useSaveTask, + useTaskRuns, + useTasks, +} from "../../hooks/useSettings"; +import { DialogFooter } from "../../components/DialogFooter"; +import { HoverEditButton } from "../../components/HoverEditButton"; +import { SectionCard } from "../../components/SectionCard"; +import { SelectionRailCard } from "../../components/SelectionRailCard"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Separator } from "@/components/ui/separator"; +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Textarea } from "@/components/ui/textarea"; + +type ActionTab = "new" | string; + +function FormField({ + label, + htmlFor, + helperText, + children, +}: { + label: string; + htmlFor?: string; + helperText?: string; + children: ReactNode; +}) { + return ( +
+ + {children} + {helperText ? ( +

{helperText}

+ ) : null} +
+ ); +} + +function emptyTask(): SavedTaskInput { + return { + id: null, + name: "", + task_type: "shell", + content: "", + enabled: true, + default_service_id: "", + notes: "", + }; +} + +function sameTask(a: SavedTaskInput, b: SavedTaskInput) { + return ( + a.id === b.id && + a.name === b.name && + a.task_type === b.task_type && + a.content === b.content && + a.enabled === b.enabled && + a.default_service_id === b.default_service_id && + a.notes === b.notes + ); +} + +function initialFromTask(task: SavedTask): SavedTaskInput { + return { + id: task.id, + name: task.name, + task_type: task.task_type, + content: task.content, + enabled: task.enabled, + default_service_id: task.default_service_id, + notes: task.notes, + }; +} + +function TaskEditor({ + task, + onChange, +}: { + task: SavedTaskInput; + onChange: (task: SavedTaskInput) => void; +}) { + return ( +
+
+

+ {task.id ? "Edit action" : "New action"} +

+ {task.task_type} + {task.enabled ? "enabled" : "disabled"} +
+
+ + onChange({ ...task, name: e.target.value })} + /> + +
+
+ + + +
+
+ + onChange({ ...task, notes: e.target.value })} + /> + + +