diff --git a/dashboard/dist/index.js b/dashboard/dist/index.js index 7bcc701..2c611ef 100644 --- a/dashboard/dist/index.js +++ b/dashboard/dist/index.js @@ -2,11 +2,11 @@ "use strict"; const SDK = window.__HERMES_PLUGIN_SDK__; const { React } = SDK; - const { Card, CardHeader, CardTitle, CardContent } = SDK.components; + const { Card, CardHeader, CardTitle, CardContent, Button } = SDK.components; - async function fetchJSONAllowError(path) { + async function fetchJSONAllowError(path, opts) { if (SDK && SDK.fetchJSON) { - try { return await SDK.fetchJSON(path); } catch (e) { if (e && e.data) return e.data; throw e; } + try { return await SDK.fetchJSON(path, opts || {}); } catch (e) { if (e && e.data) return e.data; throw e; } } let url = path; try { @@ -18,46 +18,113 @@ url = u.pathname + u.search; } } catch (_) {} - const res = await fetch(url); + 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) }; } + try { return JSON.parse(text); } catch (_) { return { status: "error", message: text.slice(0,800), detail: text.slice(0,800), http_status: res.status }; } } function SSHXPage() { const [link, setLink] = React.useState(null); const [status, setStatus] = React.useState("idle"); + const [msg, setMsg] = React.useState(null); + const [triedAuto, setTriedAuto] = React.useState(false); const check = React.useCallback(async function(){ try { const r = await fetchJSONAllowError("/api/plugins/sshx-link/status"); - if (r.link) { setLink(r.link); setStatus("running"); } + if (r.detail && r.detail.includes("No such API endpoint")) { + setStatus("needs_restart"); + setMsg("Backend chưa mount (404) — Dashboard chưa restart sau update. Cần restart."); + return; + } + if (r.link) { setLink(r.link); setStatus("running"); setMsg(null); } else { setLink(null); setStatus(r.status || "stopped"); } } catch(_){} }, []); + const doStart = React.useCallback(async function(){ + setStatus("starting"); + setMsg("Đang khởi tạo sshx qua nohup (fallback nếu thiếu nohup)..."); + try { + const r = await fetchJSONAllowError("/api/plugins/sshx-link/start"); + if (r.link) { setLink(r.link); setStatus("running"); setMsg(null); } + else if (r.detail && r.detail.includes("No such API")) { setStatus("needs_restart"); setMsg(r.detail); } + else { setMsg(r.message || r.detail || JSON.stringify(r)); setStatus("error"); } + } catch(e){ + const d = e.data ? (e.data.detail || e.data.message) : ""; + if (d.includes("No such API")) { setStatus("needs_restart"); setMsg(d); } + else { setMsg(e.message + (d ? " | "+d : "")); setStatus("error"); } + } + }, []); + React.useEffect(function(){ check(); const t=setInterval(check, 3000); return function(){clearInterval(t);} }, [check]); + // auto-start once if no link after initial check + React.useEffect(function(){ + if (triedAuto) return; + if (status === "stopped") { + setTriedAuto(true); + setTimeout(doStart, 1500); + } + }, [status, triedAuto, doStart]); + + const restartDashboard = React.useCallback(async function(){ + setMsg("Đang gửi lệnh restart Gateway/Dashboard..."); + try { await fetchJSONAllowError("/api/gateway/restart", {method:"POST"}); } catch(_){} + try { await fetchJSONAllowError("/api/dashboard/plugins/rescan"); } catch(_){} + setMsg("Đã gửi restart, đợi 7s rồi reload..."); + setTimeout(function(){ window.location.reload(); }, 7000); + }, []); + + if (status === "needs_restart") { + return React.createElement(Card, null, + React.createElement(CardHeader, null, + React.createElement(CardTitle, null, "sshx.io — Current Link"), + React.createElement("p", { className:"text-xs text-red-500", style:{fontWeight:700}}, "● Cần restart Dashboard (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"}}, msg), + React.createElement(Button, { onClick: restartDashboard }, "Restart Dashboard (không cần SSH)"), + React.createElement("p", { className:"text-xs text-muted-foreground", style:{marginTop:"0.5rem"}}, "Sau restart backend sẽ mount lại, auto-start bằng nohup & sẽ tạo link.") + ) + ); + } + if (link) { return React.createElement(Card, null, React.createElement(CardHeader, null, React.createElement(CardTitle, null, "sshx.io — Current Link"), - React.createElement("p", { className:"text-xs text-green-500", style:{fontWeight:600}}, "● Active") + React.createElement("p", { className:"text-xs text-green-500", style:{fontWeight:600}}, "● Active — auto-start bằng nohup (tránh hermes kill)") ), 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", fontWeight:600 }}, link), - React.createElement("p", { className:"text-xs text-muted-foreground", style:{marginTop:"0.5rem"}}, "Link hiện tại của shell sshx. Chỉ hiển thị — không tự chạy nền (hermes kill shell gây lỗi). Chạy thủ công trên host: nohup sshx --quiet > /tmp/sshx_link.txt 2>&1 &") + React.createElement("p", { className:"text-xs text-muted-foreground", style:{marginTop:"0.5rem"}}, "Link hiện tại, tự chạy nền bằng nohup (có fallback khi thiếu nohup). Tự refresh mỗi 3s.") ) ); } + + if (status === "starting" || status === "installing") { + return React.createElement(Card, null, + React.createElement(CardHeader, null, + React.createElement(CardTitle, null, "sshx.io — Current Link"), + React.createElement("p", { className:"text-xs text-yellow-500", style:{fontWeight:600}}, "● Đang khởi tạo...") + ), + React.createElement(CardContent, null, + React.createElement("p", { style:{whiteSpace:"pre-wrap", fontSize:"0.85rem", color:"#eab308"}}, msg || "Đang tải sshx và chạy nohup..."), + React.createElement("p", { className:"text-xs text-muted-foreground", style:{marginTop:"0.5rem"}}, "Đã dùng nohup & (fallback Python Popen nếu thiếu nohup) để tránh hermes kill shell.") + ) + ); + } + return React.createElement(Card, null, React.createElement(CardHeader, null, React.createElement(CardTitle, null, "sshx.io — Current Link"), React.createElement("p", { className:"text-xs text-muted-foreground"}, "Chưa có shell đang chạy") ), React.createElement(CardContent, null, - React.createElement("p", { className:"text-sm text-muted-foreground"}, "Không có link hiện tại. Plugin ở chế độ passive — không tự chạy sshx nền (hermes kill). Hãy chạy thủ công trên server:"), - React.createElement("pre", { style:{marginTop:"0.5rem", background:"#111", padding:"0.5rem", borderRadius:"6px", fontSize:"0.75rem", overflow:"auto"}}, "nohup sshx --quiet > /tmp/sshx_link.txt 2>&1 &\ncat /tmp/sshx_link.txt"), - React.createElement("p", { className:"text-xs text-muted-foreground", style:{marginTop:"0.5rem"}}, "Dashboard sẽ tự đọc /tmp/sshx_link.txt và hiện link ở đây, đồng thời tự refresh mỗi 3s.") + React.createElement("p", { className:"text-sm text-muted-foreground", style:{marginBottom:"0.5rem"}}, msg || "Đang tự khởi tạo sshx bằng nohup... (có fallback khi thiếu nohup). Nếu sau 10s vẫn chưa có link, dashboard sẽ tự gọi /start."), + React.createElement(Button, { onClick: doStart }, "Khởi tạo ngay (nohup)"), + React.createElement("p", { className:"text-xs text-muted-foreground", style:{marginTop:"0.5rem"}}, "Sau khi chạy, link sẽ lưu ở /tmp/sshx_link.txt và hiện ở đây.") ) ); } diff --git a/dashboard/manifest.json b/dashboard/manifest.json index f0410e9..a1bcdac 100644 --- a/dashboard/manifest.json +++ b/dashboard/manifest.json @@ -1,9 +1,9 @@ { "name": "sshx-link", "label": "sshx.io Terminal", - "description": "Chỉ hiện link hiện tại — auto-start nohup với fallback khi thiếu nohup", + "description": "Chỉ hiện link hiện tại — auto-start nohup (fallback), tự gọi /start nếu chưa có link", "icon": "Terminal", - "version": "2.3.1", + "version": "2.3.2", "tab": { "path": "/sshx", "position": "end" diff --git a/plugin.yaml b/plugin.yaml index 8a7de52..5c3a6ca 100644 --- a/plugin.yaml +++ b/plugin.yaml @@ -1,5 +1,5 @@ name: sshx-link -version: 2.3.1 +version: 2.3.2 description: Tạo shell ở sshx.io và trả link chia sẻ — auto arch detection, không cần restart dashboard author: sisyphus provides_tools: