diff --git a/decnet_web/src/components/CanaryTokens.tsx b/decnet_web/src/components/CanaryTokens.tsx index 23c40e4b..fe06b5d2 100644 --- a/decnet_web/src/components/CanaryTokens.tsx +++ b/decnet_web/src/components/CanaryTokens.tsx @@ -60,18 +60,24 @@ const STATE_COLOR = { // ─── CREATE MODAL ────────────────────────────────────────────────────────── +interface DeckyOption { + name: string; + ip?: string; +} + interface CreateModalProps { blobs: BlobRow[]; + deckies: DeckyOption[]; onClose: () => void; onCreated: (token: CanaryTokenRow) => void; } -const CreateModal: React.FC = ({ blobs, onClose, onCreated }) => { +const CreateModal: React.FC = ({ blobs, deckies, onClose, onCreated }) => { const panelRef = useRef(null); useEscapeKey(onClose, true); useFocusTrap(panelRef, true); - const [decky, setDecky] = useState(''); + const [decky, setDecky] = useState(deckies[0]?.name ?? ''); const [kind, setKind] = useState<'http' | 'dns' | 'aws_passive'>('http'); const [path, setPath] = useState('/home/admin/.aws/credentials'); const [source, setSource] = useState<'generator' | 'blob'>('generator'); @@ -82,7 +88,7 @@ const CreateModal: React.FC = ({ blobs, onClose, onCreated }) const handleSubmit = async () => { setError(null); - if (!decky.trim()) return setError('decky_name required.'); + if (!decky.trim()) return setError('Pick a decky.'); if (!path.trim().startsWith('/')) return setError('placement_path must be absolute.'); if (source === 'blob' && !blobUuid) return setError('Pick a blob or switch to Generator.'); setSubmitting(true); @@ -131,14 +137,25 @@ const CreateModal: React.FC = ({ blobs, onClose, onCreated }) - - setDecky(e.target.value)} - placeholder="web1" - autoFocus - style={INPUT_STYLE} - /> + + {deckies.length === 0 ? ( +
+ No deckies running. Deploy a fleet first. +
+ ) : ( + + )}
@@ -372,6 +389,7 @@ const UploadModal: React.FC = ({ onClose, onUploaded }) => { const CanaryTokens: React.FC = () => { const [tokens, setTokens] = useState([]); const [blobs, setBlobs] = useState([]); + const [deckies, setDeckies] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [tab, setTab] = useState<'tokens' | 'blobs'>('tokens'); @@ -386,12 +404,14 @@ const CanaryTokens: React.FC = () => { setLoading(true); setError(null); try { - const [t, b] = await Promise.all([ + const [t, b, d] = await Promise.all([ api.get('/canary/tokens'), api.get('/canary/blobs').catch(() => ({ data: { blobs: [] } })), // viewers can't list blobs + api.get('/deckies').catch(() => ({ data: [] })), ]); setTokens(t.data.tokens || []); setBlobs(b.data.blobs || []); + setDeckies(Array.isArray(d.data) ? d.data : []); } catch (err) { setError(extractError(err, 'Failed to load canary tokens.')); } finally { @@ -618,6 +638,7 @@ const CanaryTokens: React.FC = () => { {showCreate && ( setShowCreate(false)} onCreated={(t) => { setTokens((prev) => [t, ...prev]);