// InstancePanel — SSH terminal + port management overlay const { SshTerminal } = window.SEIS_TERMINAL; const { ChatPanel } = window.SEIS_CHAT; function PortsPanel({ instanceId }) { const [rules, setRules] = React.useState(null); const [loading, setLoading] = React.useState(true); const [port, setPort] = React.useState(""); const [protocol, setProtocol] = React.useState("tcp"); const [cidr, setCidr] = React.useState("0.0.0.0/0"); const [desc, setDesc] = React.useState(""); const [adding, setAdding] = React.useState(false); const [err, setErr] = React.useState(""); const load = () => { setLoading(true); fetch(`/api/ec2/instance/${instanceId}/ports`, { credentials: "include" }) .then(r => r.ok ? r.json() : Promise.reject(r.status)) .then(d => { setRules(d); setLoading(false); }) .catch(() => { setErr("Failed to load ports"); setLoading(false); }); }; React.useEffect(() => { load(); }, [instanceId]); const handleAdd = async () => { const p = parseInt(port); if (!p || p < 1 || p > 65535) { setErr("Invalid port"); return; } setAdding(true); setErr(""); try { const r = await fetch(`/api/ec2/instance/${instanceId}/ports`, { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ port: p, protocol, cidr, description: desc }), }); if (!r.ok) { const b = await r.json().catch(() => ({})); setErr(b.detail || "Add failed"); } else { setPort(""); setDesc(""); load(); } } catch { setErr("Network error"); } finally { setAdding(false); } }; const handleRemove = async (rule) => { try { const r = await fetch(`/api/ec2/instance/${instanceId}/ports`, { method: "DELETE", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ port: rule.fromPort, protocol: rule.protocol, cidr: rule.cidr }), }); if (!r.ok) { const b = await r.json().catch(() => ({})); setErr(b.detail || "Remove failed"); } else load(); } catch { setErr("Network error"); } }; const PRESETS = [ { label: "SSH", port: "22", proto: "tcp" }, { label: "HTTP", port: "80", proto: "tcp" }, { label: "HTTPS", port: "443", proto: "tcp" }, { label: "RDP", port: "3389", proto: "tcp" }, { label: "8080", port: "8080", proto: "tcp" }, { label: "8888", port: "8888", proto: "tcp" }, ]; return (
SECURITY GROUP · INBOUND RULES
{loading ? (
LOADING...
) : ( {(rules || []).length === 0 && ( )} {(rules || []).map((r, i) => ( ))}
PORT PROTO SOURCE DESC
NO RULES
{r.fromPort === r.toPort ? r.fromPort : `${r.fromPort}–${r.toPort}`} {r.protocol.toUpperCase()} {r.cidr} {r.description || "—"}
)}
ADD RULE
{PRESETS.map(p => ( ))}
setPort(e.target.value)} style={{ width: 80 }} min="1" max="65535" /> setCidr(e.target.value)} style={{ width: 130 }} /> setDesc(e.target.value)} style={{ width: 160 }} />
{err &&
{err}
}
); } function CredentialsPanel({ instanceId, detail }) { const isWindows = detail?.instanceType && !detail.instanceType.startsWith("t3") && true; // always show for now const [creds, setCreds] = React.useState(null); const [loading, setLoading] = React.useState(false); const [revealed, setRevealed] = React.useState(false); const [err, setErr] = React.useState(""); const load = async () => { setLoading(true); setErr(""); try { const r = await fetch(`/api/ec2/instance/${instanceId}/password`, { credentials: "include" }); const d = await r.json(); if (!r.ok) { setErr(d.detail || "Failed"); } else { setCreds(d); } } catch { setErr("Network error"); } finally { setLoading(false); } }; const copyText = (t) => navigator.clipboard.writeText(t).catch(() => {}); return (
INSTANCE CREDENTIALS
{detail?.publicIp && (
SSH / RDP HOST
{detail.publicIp}
)}
LINUX SSH
{detail?.publicIp ? (
ssh ubuntu@{detail.publicIp}
) : No public IP yet}
WINDOWS ADMINISTRATOR PASSWORD
{!creds && !loading && (
Retrieved via AWS GetPasswordData + your RSA private key. May take 4–15 min after launch for Windows to generate.
)} {loading &&
FETCHING...
} {err &&
{err}
} {creds && !creds.ready && (
NOT READY YET — Windows still initializing. Try again in a few minutes.
)} {creds?.ready && (
USERNAME {creds.username}
PASSWORD {revealed ? creds.password : "●".repeat(Math.min(creds.password.length, 20))}
{detail?.publicIp && (
RDP COMMAND
mstsc /v:{detail.publicIp}
)}
)}
); } function InstancePanel({ instanceId, onClose, hasSshKey, user }) { const [detail, setDetail] = React.useState(null); const [loading, setLoading] = React.useState(true); const [tab, setTab] = React.useState("terminal"); const [aiOpen, setAiOpen] = React.useState(false); const termCmdRef = React.useRef(null); React.useEffect(() => { if (!instanceId) return; fetch(`/api/ec2/instance/${instanceId}`, { credentials: "include" }) .then(r => r.ok ? r.json() : null) .then(d => { setDetail(d); setLoading(false); }) .catch(() => setLoading(false)); }, [instanceId]); React.useEffect(() => { const onKey = e => { if (e.key === "Escape") onClose(); }; document.addEventListener("keydown", onKey); document.body.style.overflow = "hidden"; return () => { document.removeEventListener("keydown", onKey); document.body.style.overflow = ""; }; }, [onClose]); const copyIp = (ip) => { navigator.clipboard.writeText(ip).catch(() => {}); }; return (
{ if (e.target !== e.currentTarget) return; if (aiOpen && !window.confirm("Close instance panel? AI chat will be saved.")) return; onClose(); }}>
e.stopPropagation()}> {/* Header */}
EC2-INST-001 INSTANCE · {instanceId} {detail?.state && ( ● {detail.state.toUpperCase()} )}
{/* Info strip */} {detail && (
{detail.publicIp && ( copyIp(detail.publicIp)} title="Click to copy"> PUBLIC {detail.publicIp} )} {detail.privateIp && ( PRIVATE {detail.privateIp} )} {detail.instanceType} {detail.az} {detail.securityGroups?.[0] && ( SG: {detail.securityGroups[0].name} )}
)} {loading && (
LOADING...
)} {/* Tab bar */}
{[ { key: "terminal", label: "TERMINAL" }, { key: "ports", label: "PORTS" }, { key: "credentials", label: "CREDENTIALS" }, ].map(t => ( ))}
{/* Content */}
{tab === "terminal" && (
{!hasSshKey ? (
⚠ TERMINAL REQUIRES A GENERATED SSH KEY
Generate a key in the SSH KEY section above. Pasted keys are not supported (private key is not stored). Use your local terminal:{" "} ssh ubuntu@{detail?.publicIp || ""}
) : ( )}
{aiOpen && (
setAiOpen(false)} user={user} inline instanceId={instanceId} onRunCommand={(cmd) => termCmdRef.current?.send(cmd)} onYoloRun={(cmd) => termCmdRef.current?.runAndCapture(cmd) || Promise.resolve("")} />
)}
)} {tab === "ports" && (
)} {tab === "credentials" && (
)}
); } window.SEIS_INSTANCE_PANEL = { InstancePanel };