import React, { useEffect, useRef, useState } from 'react'; import { X, Download, AlertTriangle } from '../icons'; import api from '../utils/api'; import { useEscapeKey } from '../hooks/useEscapeKey'; import { useFocusTrap } from '../hooks/useFocusTrap'; interface ArtifactDrawerProps { decky: string; storedAs: string; fields: Record; onClose: () => void; } // Bulky nested structures are shipped as one base64-encoded JSON blob in // `meta_json_b64` (see templates/ssh/emit_capture.py). All summary fields // arrive as top-level SD params already present in `fields`. function decodeMeta(fields: Record): Record | null { const b64 = fields.meta_json_b64; if (typeof b64 !== 'string' || !b64) return null; try { const json = atob(b64); return JSON.parse(json); } catch (err) { console.error('artifact: failed to decode meta_json_b64', err); return null; } } const Row: React.FC<{ label: string; value: React.ReactNode }> = ({ label, value }) => (
{label}
{value ?? }
); const ArtifactDrawer: React.FC = ({ decky, storedAs, fields, onClose }) => { const panelRef = useRef(null); useEscapeKey(onClose, true); useFocusTrap(panelRef, true); useEffect(() => { const prev = document.body.style.overflow; document.body.style.overflow = 'hidden'; return () => { document.body.style.overflow = prev; }; }, []); const [downloading, setDownloading] = useState(false); const [error, setError] = useState(null); const meta = decodeMeta(fields); const handleDownload = async () => { setDownloading(true); setError(null); try { const res = await api.get( `/artifacts/${encodeURIComponent(decky)}/${encodeURIComponent(storedAs)}`, { responseType: 'blob' }, ); const blobUrl = URL.createObjectURL(res.data); const a = document.createElement('a'); a.href = blobUrl; a.download = storedAs; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(blobUrl); } catch (err: any) { const status = err?.response?.status; setError( status === 403 ? 'Admin role required to download artifacts.' : status === 404 ? 'Artifact not found on disk (may have been purged).' : status === 400 ? 'Server rejected the request (invalid parameters).' : 'Download failed — see console.' ); console.error('artifact download failed', err); } finally { setDownloading(false); } }; const concurrent = meta?.concurrent_sessions; const ssSnapshot = meta?.ss_snapshot; return (
e.stopPropagation()} style={{ width: 'min(620px, 100%)', height: '100%', backgroundColor: 'var(--bg-color, #0d1117)', borderLeft: '1px solid var(--border-color, #30363d)', padding: '24px', overflowY: 'auto', color: 'var(--text-color)', }} >
CAPTURED ARTIFACT · {decky}
{storedAs}
Attacker-controlled content. Download at your own risk.
{error && (
{error}
)}

ORIGIN

ATTRIBUTION · {fields.attribution ?? 'unknown'}

{Array.isArray(concurrent) && concurrent.length > 0 && (

CONCURRENT SESSIONS ({concurrent.length})

              {JSON.stringify(concurrent, null, 2)}
            
)} {Array.isArray(ssSnapshot) && ssSnapshot.length > 0 && (

SS SNAPSHOT ({ssSnapshot.length})

              {JSON.stringify(ssSnapshot, null, 2)}
            
)}
); }; export default ArtifactDrawer;