diff --git a/app/caddy/_components/caddy-editor.tsx b/app/caddy/_components/caddy-editor.tsx new file mode 100644 index 0000000..14a4427 --- /dev/null +++ b/app/caddy/_components/caddy-editor.tsx @@ -0,0 +1,214 @@ +'use client' + +import { useCallback, useEffect, useState } from 'react' +import Link from 'next/link' +import { useFlowRun } from '@/hooks/useFlowRun' +import ConfirmDialog from '@/components/ConfirmDialog' +import StreamingTerminal from '@/components/StreamingTerminal' + +type Phase = 'edit' | 'writing' | 'validating' | 'validated' | 'saving' | 'saved' + +type DialogPending = 'validate' | 'save' | null + +type Props = { + initialContent: string + initialError: string | null +} + +const VALIDATE_PREVIEW = + 'cat > /srv/scrum4me/caddy/Caddyfile.new \\\n && mv /srv/scrum4me/caddy/Caddyfile.new /srv/scrum4me/caddy/Caddyfile\ncaddy validate --config /srv/scrum4me/caddy/Caddyfile' + +const SAVE_PREVIEW = 'caddy reload --config /srv/scrum4me/caddy/Caddyfile' + +export default function CaddyEditor({ initialContent, initialError }: Props) { + const [content, setContent] = useState(initialContent) + const [phase, setPhase] = useState('edit') + const [dialogPending, setDialogPending] = useState(null) + + const writeFlow = useFlowRun() + const validateFlow = useFlowRun() + const reloadFlow = useFlowRun() + + // Chain: write done → start validate + useEffect(() => { + if (phase === 'writing' && writeFlow.status === 'done') { + setPhase('validating') + validateFlow.start('caddy_validate') + } else if (phase === 'writing' && (writeFlow.status === 'failed' || writeFlow.status === 'error')) { + setPhase('edit') + } + }, [writeFlow.status, phase, validateFlow.start]) + + // Validate done → validated phase + useEffect(() => { + if (phase === 'validating' && validateFlow.status === 'done') { + setPhase('validated') + } else if (phase === 'validating' && (validateFlow.status === 'failed' || validateFlow.status === 'error')) { + setPhase('edit') + } + }, [validateFlow.status, phase]) + + // Reload done → saved + useEffect(() => { + if (phase === 'saving' && reloadFlow.status === 'done') { + setPhase('saved') + } else if (phase === 'saving' && (reloadFlow.status === 'failed' || reloadFlow.status === 'error')) { + setPhase('validated') + } + }, [reloadFlow.status, phase]) + + const handleValidateConfirm = useCallback(() => { + setDialogPending(null) + setPhase('writing') + writeFlow.reset() + validateFlow.reset() + writeFlow.start('caddy_write_config', [], content) + }, [content, writeFlow.reset, writeFlow.start, validateFlow.reset]) + + const handleSaveConfirm = useCallback(() => { + setDialogPending(null) + setPhase('saving') + reloadFlow.reset() + reloadFlow.start('caddy_reload') + }, [reloadFlow.reset, reloadFlow.start]) + + const handleEditAgain = () => { + writeFlow.reset() + validateFlow.reset() + reloadFlow.reset() + setPhase('edit') + } + + const isActive = phase === 'writing' || phase === 'validating' || phase === 'saving' + + return ( +
+ {initialError && ( +
+ Failed to load current config: {initialError} +
+ )} + + {/* Textarea */} +
+
+

Caddyfile

+ {phase === 'saved' && ( + + + Reloaded successfully + + )} + {phase === 'validated' && ( + + + Config is valid + + )} +
+