import { useEffect, useState, type FormEvent } from 'react'; import { extractApiError } from '@/api/client'; import { useC2Config, useDeleteC2Config, useTestC2Config, useUpdateC2Config } from '@/hooks/useC2'; import { ConfirmDialog } from './ConfirmDialog'; import { FormField, TextInput } from './FormField'; import { useToast } from '@/hooks/useToast'; interface C2ConfigCardProps { engagementId: number; } export function C2ConfigCard({ engagementId }: C2ConfigCardProps): JSX.Element { const { push } = useToast(); const configQuery = useC2Config(engagementId); const updateMutation = useUpdateC2Config(engagementId); const deleteMutation = useDeleteC2Config(engagementId); const testMutation = useTestC2Config(engagementId); const config = configQuery.data; const is503 = configQuery.error ? (configQuery.error as { response?: { status?: number } })?.response?.status === 503 : false; const [url, setUrl] = useState(''); const [token, setToken] = useState(''); const [verifyTls, setVerifyTls] = useState(true); const [replaceToken, setReplaceToken] = useState(false); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [testResult, setTestResult] = useState<{ ok: boolean; message: string } | null>(null); // Sync URL and verifyTls from loaded config (but not token — write-only at API level) useEffect(() => { if (config) { setUrl(config.url); setVerifyTls(config.verify_tls); } }, [config]); const disabled = is503 || configQuery.isLoading; const onSave = async (e: FormEvent) => { e.preventDefault(); if (is503) return; setTestResult(null); const input: { url: string; verify_tls: boolean; api_token?: string } = { url: url.trim(), verify_tls: verifyTls, }; // Send token only if: no existing config, OR user explicitly chose to replace if (!config?.has_token || replaceToken) { if (token.trim()) input.api_token = token.trim(); } try { await updateMutation.mutateAsync(input); push('C2 configuration saved', 'success'); setToken(''); setReplaceToken(false); } catch (err) { push(extractApiError(err, 'Could not save C2 configuration'), 'error'); } }; const onDelete = async () => { setShowDeleteConfirm(false); setTestResult(null); try { await deleteMutation.mutateAsync(); push('C2 configuration removed', 'success'); setUrl(''); setToken(''); setVerifyTls(true); setReplaceToken(false); } catch (err) { push(extractApiError(err, 'Could not remove C2 configuration'), 'error'); } }; const onTest = async () => { setTestResult(null); try { const result = await testMutation.mutateAsync(); setTestResult({ ok: result.ok, message: result.ok ? 'Connected' : (result.error ?? 'Connection failed'), }); } catch (err) { setTestResult({ ok: false, message: extractApiError(err, 'Test failed') }); } }; const submitting = updateMutation.isPending || deleteMutation.isPending; return (

C2 configuration

{is503 && (
C2 features are disabled (server has no encryption key configured).
)} {configQuery.isLoading ? (

Loading…

) : (
setUrl(e.target.value)} disabled={disabled} /> {config?.has_token && !replaceToken ? (
) : ( setToken(e.target.value)} disabled={disabled} /> )}
setVerifyTls(e.target.checked)} disabled={disabled} className="h-4 w-4 accent-primary" />
{testResult !== null && ( {testResult.message} )} {config?.has_token && ( )}
)} {showDeleteConfirm && ( setShowDeleteConfirm(false)} /> )}
); }