(function () { "use strict"; const SDK = window.__HERMES_PLUGIN_SDK__; const { React } = SDK; const { useState, useEffect, useCallback } = SDK.hooks; const { Card, CardHeader, CardTitle, CardContent, Button } = SDK.components; async function fetchJSONAllowError(path, opts) { if (SDK && SDK.fetchJSON) { try { return await SDK.fetchJSON(path, opts || {}); } catch (e) { if (e && e.data) return e.data; // SDK may throw Error with message containing JSON const msg = e && e.message || ""; // try to parse detail try { const j = JSON.parse(msg.slice(msg.indexOf("{"))); return j; } catch(_){} throw e; } } let url = path; try { const cur = new URL(window.location.href); const profile = cur.searchParams.get("profile"); if (profile) { const u = new URL(path, window.location.origin); if (!u.searchParams.get("profile")) u.searchParams.set("profile", profile); url = u.pathname + u.search; } } catch (_) {} const res = await fetch(url, opts || {}); const text = await res.text(); try { return JSON.parse(text); } catch (_) { return { status: "error", message: text.slice(0,800), http_status: res.status, detail: text.slice(0,800) }; } } function SSHXPage() { const [link, setLink] = useState(null); const [status, setStatus] = useState("idle"); const [error, setError] = useState(null); const [debug, setDebug] = useState(null); const [restarting, setRestarting] = useState(false); const fetchDebug = useCallback(async function(){ try { const d = await fetchJSONAllowError("/api/plugins/sshx-link/debug"); setDebug(d); } catch(e){ setDebug({error: e.message}); } }, []); const isMissingAPI = function(res){ const s = JSON.stringify(res||{}); return s.includes("No such API endpoint") || (res && res.detail && res.detail.includes("No such API endpoint")); }; const restartDashboard = useCallback(async function(){ setRestarting(true); setError("Đang khởi động lại Gateway/Dashboard (không cần SSH)..."); try { // Try core Hermes restart endpoints (these are always mounted, even when plugin_api 404) let lastErr = ""; const tryPost = async (p) => { try { await fetchJSONAllowError(p, { method:"POST"}); return true; } catch(e){ lastErr = e.message; return false; } }; // SDK path handles auth await tryPost("/api/gateway/restart") || await tryPost("/api/dashboard/restart") || await tryPost("/api/hermes/restart"); if (lastErr && lastErr.includes("No such")) { // fallback: try rescan then wait await fetchJSONAllowError("/api/dashboard/plugins/rescan"); } } catch(e){} setError("Đã gửi lệnh restart. Đợi 6s rồi tự reload..."); setTimeout(async function(){ try { await fetchJSONAllowError("/api/dashboard/plugins/rescan"); } catch(_){} setTimeout(function(){ window.location.reload(); }, 2500); }, 6000); }, []); const start = useCallback(async function () { setStatus("loading"); setError(null); setDebug(null); try { var st = await fetchJSONAllowError("/api/plugins/sshx-link/status"); if (isMissingAPI(st)) { setStatus("needs_restart"); setError('Backend chưa mount: No such API endpoint: /api/plugins/sshx-link/status — Plugin vừa update lên v2.0.2/2.0.3 nhưng Dashboard chưa restart (plugin_api chỉ load lúc start). Cần restart Gateway/Dashboard — có thể làm ngay trên dashboard, không cần SSH.'); fetchDebug(); return; } if (st.status === "installing" || st.status === "starting") { setStatus("installing"); setError(st.message || "Đang xử lý..."); fetchDebug(); return; } if (st.status === "running" && st.link) { setLink(st.link); setStatus("running"); return; } var res = await fetchJSONAllowError("/api/plugins/sshx-link/start"); if (isMissingAPI(res)) { setStatus("needs_restart"); setError('Backend chưa mount: No such API endpoint: /api/plugins/sshx-link/start — Cần restart Dashboard (nút bên dưới, không cần SSH).'); return; } if (res.link) { setLink(res.link); setStatus("running"); } else if (res.status === "starting") { setStatus("installing"); setError(res.message || "sshx đang khởi động, chờ 2s rồi thử lại... " + (res.output_tail || "")); fetchDebug(); } else { setError((res.message || res.detail || JSON.stringify(res)) + (res.output_tail ? "\nTail: " + res.output_tail : "") + (res.debug ? "\nDebug: " + JSON.stringify(res.debug) : "")); setStatus("error"); fetchDebug(); } } catch (err) { const detail = err.data ? (err.data.detail || err.data.message || JSON.stringify(err.data)) : ""; if (detail.includes("No such API endpoint")) { setStatus("needs_restart"); setError("Backend chưa mount (404 No such API endpoint) — Dashboard chưa restart sau khi update plugin. Dùng nút Restart bên dưới, không cần SSH."); } else { setError((err.message || "Failed to start sshx") + (detail ? " | detail: " + detail : "")); setStatus("error"); } fetchDebug(); } }, [fetchDebug]); const stop = useCallback(async function () { try { await fetchJSONAllowError("/api/plugins/sshx-link/stop", { method: "POST" }); setLink(null); setStatus("idle"); setError(null); setDebug(null); } catch (err) { setError(err.message); } }, []); const checkStatus = useCallback(async function () { try { const res = await fetchJSONAllowError("/api/plugins/sshx-link/status"); if (isMissingAPI(res)) { setStatus("needs_restart"); setError("Backend 404 — cần restart Dashboard (không cần SSH)."); return; } if (res.detail === "Unauthorized") return; if (res.status === "running" && res.link) { setLink(res.link); setStatus("running"); setError(null); } else if (res.status === "installing" || res.status === "starting") { setStatus("installing"); if (res.message) setError(res.message); } else if (res.status === "error") { setStatus("error"); setError(res.message + (res.output_tail ? "\nTail: " + res.output_tail : "")); setDebug(res); } else if (res.status === "stopped") { if (status === "running") { setLink(null); setStatus("idle"); } } } catch (_) {} }, [status]); useEffect(function () { checkStatus(); }, [checkStatus]); useEffect(function () { if (status !== "installing" && status !== "loading") return; var timer = setInterval(function () { checkStatus(); }, 2500); return function () { clearInterval(timer); }; }, [status, checkStatus]); if (status === "needs_restart") { return React.createElement(Card, null, React.createElement(CardHeader, null, React.createElement(CardTitle, null, "sshx.io Shared Terminal"), React.createElement("p", { className: "text-xs text-red-500", style: { fontWeight: 700 } }, "● Cần restart Dashboard — Backend chưa mount (404)") ), React.createElement(CardContent, null, React.createElement("pre", { style:{whiteSpace:"pre-wrap", fontSize:"0.85rem", color:"#fbbf24", background:"#1a1a0a", padding:"0.75rem", borderRadius:"6px", marginBottom:"0.75rem"}}, error || "No such API endpoint: /api/plugins/sshx-link/status — plugin vừa update nhưng Dashboard chưa restart."), React.createElement("p", { className:"text-sm text-muted-foreground", style:{marginBottom:"0.75rem"}}, "Không cần SSH. Bấm nút dưới để restart Gateway/Dashboard ngay trên dashboard (dùng /api/gateway/restart). Sau ~8s trang sẽ tự reload."), React.createElement("div", { style:{display:"flex", gap:"0.5rem", flexWrap:"wrap"}}, React.createElement(Button, { onClick: restartDashboard, disabled: restarting }, restarting ? "Đang restart..." : "Restart Dashboard ngay (không cần SSH)"), React.createElement(Button, { onClick: function(){ fetchJSONAllowError("/api/dashboard/plugins/rescan").then(()=>location.reload()); }, variant:"outline", size:"sm"}, "Rescan plugins") ), React.createElement("p", { className:"text-xs text-muted-foreground", style:{marginTop:"0.75rem"}}, "Sau restart, /api/plugins/sshx-link/status sẽ trả 200 và nút Start sẽ tạo link https://sshx.io/s/..."), debug && React.createElement("pre", { style:{marginTop:"0.75rem", fontSize:"0.68rem", background:"#111", padding:"0.5rem", borderRadius:"6px", overflow:"auto", maxHeight:"200px", whiteSpace:"pre-wrap"}}, JSON.stringify(debug, null, 2)) ) ); } if (status === "installing" || status === "loading") { return React.createElement(Card, null, React.createElement(CardHeader, null, React.createElement(CardTitle, null, "sshx.io Shared Terminal"), React.createElement("p", { className: "text-xs text-yellow-500", style: { fontWeight: 600 } }, status==="loading" ? "Đang tạo shell ở sshx.io..." : "Đang cài / khởi động sshx...") ), React.createElement(CardContent, null, React.createElement("p", { className: "text-sm", style: { whiteSpace:"pre-wrap", fontWeight:600, color:"#eab308"}}, error || "Đang tải binary từ s3.amazonaws.com/sshx..."), React.createElement("p", { className: "text-xs text-muted-foreground", style: { marginTop: "0.5rem" } }, "Tự động thử lại mỗi 2.5s."), debug && React.createElement("pre", { style:{marginTop:"0.75rem", fontSize:"0.68rem", background:"#111", padding:"0.5rem", borderRadius:"6px", overflow:"auto", maxHeight:"200px", whiteSpace:"pre-wrap"}}, JSON.stringify(debug, null, 2)), React.createElement("div", { style:{display:"flex", gap:"0.5rem", marginTop:"0.75rem"}}, React.createElement(Button, { onClick: checkStatus, variant:"outline", size:"sm"}, "Refresh"), React.createElement(Button, { onClick: stop, variant:"destructive", size:"sm"}, "Stop") ) ) ); } if (status === "running" && link) { return React.createElement(Card, null, React.createElement(CardHeader, null, React.createElement(CardTitle, null, "sshx.io Shared Terminal"), React.createElement("p", { className: "text-xs text-green-500", style: { fontWeight: 600 } }, "● Running — shell đã tạo ở sshx.io") ), React.createElement(CardContent, null, React.createElement("a", { href: link, target: "_blank", rel: "noopener noreferrer", style: { color: "#60a5fa", fontSize: "1.05rem", wordBreak: "break-all", textDecoration: "underline", display: "block", marginBottom: "0.75rem", fontWeight: 600 } }, link), React.createElement("div", { style: { display: "flex", gap: "0.5rem", marginBottom: "1rem", flexWrap: "wrap" } }, React.createElement(Button, { onClick: function() { navigator.clipboard && navigator.clipboard.writeText(link); }, variant: "outline", size: "sm" }, "Copy Link"), React.createElement(Button, { onClick: function() { window.open(link, "_blank"); }, variant: "outline", size: "sm" }, "Open"), React.createElement(Button, { onClick: stop, variant: "destructive", size: "sm" }, "Stop") ), React.createElement("div", { style: { border: "1px solid #333", borderRadius: "8px", overflow: "hidden", height: "560px", background: "#000" } }, React.createElement("iframe", { src: link, style: { width: "100%", height: "100%", border: "0" }, title: "sshx Terminal", allow: "clipboard-read; clipboard-write" }) ), React.createElement("p", { className: "text-xs text-muted-foreground", style: { marginTop: "0.5rem" } }, "Terminal nhúng ở trên. Nếu iframe bị chặn, bấm Open." ) ) ); } return React.createElement(Card, null, React.createElement(CardHeader, null, React.createElement(CardTitle, null, "sshx.io Shared Terminal"), React.createElement("p", { className: "text-xs text-muted-foreground" }, "Tạo shell ở sshx.io và trả link chia sẻ — auto arch, không cần restart") ), React.createElement(CardContent, null, React.createElement("p", { className: "text-sm text-muted-foreground", style: { marginBottom: "1rem" } }, "Bấm Start để tạo shell cộng tác trên sshx.io. Plugin sẽ tự tải binary sshx (S3), chạy sshx --quiet và parse link https://sshx.io/s/..."), error && React.createElement("pre", { style: { marginBottom: "1rem", whiteSpace: "pre-wrap", fontSize:"0.85rem", color:"#ef4444", background:"#1a0a0a", padding:"0.5rem", borderRadius:"6px", overflow:"auto", maxHeight:"300px"} }, "Error: " + error), debug && React.createElement("pre", { style:{marginBottom:"1rem", fontSize:"0.68rem", background:"#111", padding:"0.5rem", borderRadius:"6px", overflow:"auto", maxHeight:"200px", whiteSpace:"pre-wrap"}}, "Debug: " + JSON.stringify(debug, null, 2)), React.createElement("div", { style:{display:"flex", gap:"0.5rem", flexWrap:"wrap"}}, React.createElement(Button, { onClick: start, disabled: status === "loading" }, status === "loading" ? "Đang tạo shell..." : "Start sshx → Lấy link"), React.createElement(Button, { onClick: fetchDebug, variant:"outline", size:"sm"}, "Debug") ), React.createElement("p", { className: "text-xs text-muted-foreground", style: { marginTop: "0.75rem"}}, "Nếu báo 404 No such API endpoint, bấm Start sẽ tự hiện nút Restart Dashboard — không cần SSH.") ) ); } window.__HERMES_PLUGINS__.register("sshx-link", SSHXPage); })();