2026-08-17 15:19:06 +00:00
( function ( ) {
"use strict" ;
2026-08-17 15:15:05 +00:00
const SDK = window . _ _HERMES _PLUGIN _SDK _ _ ;
2026-08-17 15:19:06 +00:00
const { React } = SDK ;
const { useState , useEffect , useCallback } = SDK . hooks ;
const { Card , CardHeader , CardTitle , CardContent , Button } = SDK . components ;
2026-08-17 15:23:40 +00:00
2026-09-03 04:27:30 +00:00
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 ;
2026-09-03 04:30:52 +00:00
// 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 ( _ ) { }
2026-09-03 04:27:30 +00:00
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 ( ) ;
2026-09-03 04:30:52 +00:00
try { return JSON . parse ( text ) ; } catch ( _ ) { return { status : "error" , message : text . slice ( 0 , 800 ) , http _status : res . status , detail : text . slice ( 0 , 800 ) } ; }
2026-08-19 00:00:34 +00:00
}
2026-09-03 04:21:03 +00:00
function SSHXPage ( ) {
2026-08-17 15:15:05 +00:00
const [ link , setLink ] = useState ( null ) ;
2026-08-17 15:19:06 +00:00
const [ status , setStatus ] = useState ( "idle" ) ;
2026-08-17 15:15:05 +00:00
const [ error , setError ] = useState ( null ) ;
2026-09-03 04:23:49 +00:00
const [ debug , setDebug ] = useState ( null ) ;
2026-09-03 04:30:52 +00:00
const [ restarting , setRestarting ] = useState ( false ) ;
2026-09-03 04:23:49 +00:00
const fetchDebug = useCallback ( async function ( ) {
2026-09-03 04:27:30 +00:00
try { const d = await fetchJSONAllowError ( "/api/plugins/sshx-link/debug" ) ; setDebug ( d ) ; } catch ( e ) { setDebug ( { error : e . message } ) ; }
2026-09-03 04:23:49 +00:00
} , [ ] ) ;
2026-08-17 15:15:05 +00:00
2026-09-03 04:30:52 +00:00
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 ) ;
} , [ ] ) ;
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
const start = useCallback ( async function ( ) {
2026-08-17 15:15:05 +00:00
setStatus ( "loading" ) ;
setError ( null ) ;
2026-09-03 04:23:49 +00:00
setDebug ( null ) ;
2026-08-17 15:15:05 +00:00
try {
2026-09-03 04:27:30 +00:00
var st = await fetchJSONAllowError ( "/api/plugins/sshx-link/status" ) ;
2026-09-03 04:30:52 +00:00
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 ;
2026-09-03 04:27:30 +00:00
}
2026-09-03 04:23:49 +00:00
if ( st . status === "installing" || st . status === "starting" ) {
2026-08-19 00:00:34 +00:00
setStatus ( "installing" ) ;
2026-09-03 04:23:49 +00:00
setError ( st . message || "Đang xử lý..." ) ;
fetchDebug ( ) ;
2026-08-19 00:00:34 +00:00
return ;
}
2026-09-03 04:23:49 +00:00
if ( st . status === "running" && st . link ) {
setLink ( st . link ) ;
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
setStatus ( "running" ) ;
return ;
}
2026-09-03 04:27:30 +00:00
var res = await fetchJSONAllowError ( "/api/plugins/sshx-link/start" ) ;
2026-09-03 04:30:52 +00:00
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 ;
2026-09-03 04:23:49 +00:00
}
2026-08-17 15:15:05 +00:00
if ( res . link ) {
setLink ( res . link ) ;
setStatus ( "running" ) ;
2026-09-03 04:21:03 +00:00
} else if ( res . status === "starting" ) {
setStatus ( "installing" ) ;
2026-09-03 04:23:49 +00:00
setError ( res . message || "sshx đang khởi động, chờ 2s rồi thử lại... " + ( res . output _tail || "" ) ) ;
fetchDebug ( ) ;
2026-08-17 15:15:05 +00:00
} else {
2026-09-03 04:27:30 +00:00
setError ( ( res . message || res . detail || JSON . stringify ( res ) ) + ( res . output _tail ? "\nTail: " + res . output _tail : "" ) + ( res . debug ? "\nDebug: " + JSON . stringify ( res . debug ) : "" ) ) ;
2026-08-17 15:15:05 +00:00
setStatus ( "error" ) ;
2026-09-03 04:23:49 +00:00
fetchDebug ( ) ;
2026-08-17 15:15:05 +00:00
}
} catch ( err ) {
2026-09-03 04:27:30 +00:00
const detail = err . data ? ( err . data . detail || err . data . message || JSON . stringify ( err . data ) ) : "" ;
2026-09-03 04:30:52 +00:00
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" ) ;
}
2026-09-03 04:23:49 +00:00
fetchDebug ( ) ;
2026-08-17 15:15:05 +00:00
}
2026-09-03 04:23:49 +00:00
} , [ fetchDebug ] ) ;
2026-08-17 15:15:05 +00:00
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
const stop = useCallback ( async function ( ) {
2026-08-17 15:15:05 +00:00
try {
2026-09-03 04:27:30 +00:00
await fetchJSONAllowError ( "/api/plugins/sshx-link/stop" , { method : "POST" } ) ;
2026-08-17 15:15:05 +00:00
setLink ( null ) ;
setStatus ( "idle" ) ;
2026-09-03 04:21:03 +00:00
setError ( null ) ;
2026-09-03 04:23:49 +00:00
setDebug ( null ) ;
2026-08-17 15:15:05 +00:00
} catch ( err ) {
setError ( err . message ) ;
}
} , [ ] ) ;
2026-08-17 15:19:06 +00:00
const checkStatus = useCallback ( async function ( ) {
2026-08-17 15:15:05 +00:00
try {
2026-09-03 04:27:30 +00:00
const res = await fetchJSONAllowError ( "/api/plugins/sshx-link/status" ) ;
2026-09-03 04:30:52 +00:00
if ( isMissingAPI ( res ) ) {
setStatus ( "needs_restart" ) ;
setError ( "Backend 404 — cần restart Dashboard (không cần SSH)." ) ;
return ;
}
2026-09-03 04:27:30 +00:00
if ( res . detail === "Unauthorized" ) return ;
2026-08-17 15:15:05 +00:00
if ( res . status === "running" && res . link ) {
setLink ( res . link ) ;
setStatus ( "running" ) ;
2026-09-03 04:23:49 +00:00
setError ( null ) ;
2026-09-03 04:21:03 +00:00
} else if ( res . status === "installing" || res . status === "starting" ) {
2026-08-19 00:00:34 +00:00
setStatus ( "installing" ) ;
2026-09-03 04:23:49 +00:00
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 ) ;
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
} else if ( res . status === "stopped" ) {
if ( status === "running" ) {
setLink ( null ) ;
setStatus ( "idle" ) ;
}
2026-08-17 15:15:05 +00:00
}
} catch ( _ ) { }
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
} , [ status ] ) ;
2026-08-17 15:15:05 +00:00
2026-08-17 15:19:06 +00:00
useEffect ( function ( ) {
checkStatus ( ) ;
2026-09-03 04:38:46 +00:00
// auto-start after 1s if stopped (cài vô là tự chạy, không cần bấm Start)
setTimeout ( function ( ) {
fetchJSONAllowError ( "/api/plugins/sshx-link/status" ) . then ( function ( r ) {
if ( ! r . link && r . status === "stopped" && ! r . detail ? . includes ( "No such" ) ) {
// trigger auto start
fetchJSONAllowError ( "/api/plugins/sshx-link/start" ) . then ( function ( ) { setTimeout ( checkStatus , 3000 ) ; } ) . catch ( ( ) => { } ) ;
}
} ) . catch ( ( ) => { } ) ;
} , 1500 ) ;
} , [ ] ) ;
2026-08-17 15:15:05 +00:00
2026-08-19 00:00:34 +00:00
useEffect ( function ( ) {
2026-09-03 04:23:49 +00:00
if ( status !== "installing" && status !== "loading" ) return ;
2026-08-19 00:00:34 +00:00
var timer = setInterval ( function ( ) {
checkStatus ( ) ;
2026-09-03 04:23:49 +00:00
} , 2500 ) ;
2026-08-19 00:00:34 +00:00
return function ( ) { clearInterval ( timer ) ; } ;
} , [ status , checkStatus ] ) ;
2026-09-03 04:30:52 +00:00
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 ) )
)
) ;
}
2026-09-03 04:23:49 +00:00
if ( status === "installing" || status === "loading" ) {
2026-08-19 00:00:34 +00:00
return React . createElement ( Card , null ,
React . createElement ( CardHeader , null ,
2026-09-03 04:23:49 +00:00
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..." )
2026-08-19 00:00:34 +00:00
) ,
React . createElement ( CardContent , null ,
2026-09-03 04:23:49 +00:00
React . createElement ( "p" , { className : "text-sm" , style : { whiteSpace : "pre-wrap" , fontWeight : 600 , color : "#eab308" } } , error || "Đang tải binary từ s3.amazonaws.com/sshx..." ) ,
2026-09-03 04:30:52 +00:00
React . createElement ( "p" , { className : "text-xs text-muted-foreground" , style : { marginTop : "0.5rem" } } , "Tự động thử lại mỗi 2.5s." ) ,
2026-09-03 04:23:49 +00:00
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" )
)
2026-08-19 00:00:34 +00:00
)
) ;
}
2026-08-17 15:19:06 +00:00
if ( status === "running" && link ) {
return React . createElement ( Card , null ,
React . createElement ( CardHeader , null ,
2026-09-03 04:21:03 +00:00
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" )
2026-08-17 15:19:06 +00:00
) ,
React . createElement ( CardContent , null ,
React . createElement ( "a" , {
href : link ,
target : "_blank" ,
rel : "noopener noreferrer" ,
style : {
color : "#60a5fa" ,
2026-09-03 04:21:03 +00:00
fontSize : "1.05rem" ,
2026-08-17 15:19:06 +00:00
wordBreak : "break-all" ,
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
textDecoration : "underline" ,
display : "block" ,
2026-09-03 04:21:03 +00:00
marginBottom : "0.75rem" ,
fontWeight : 600
2026-08-17 15:19:06 +00:00
}
} , link ) ,
2026-09-03 04:21:03 +00:00
React . createElement ( "div" , { style : { display : "flex" , gap : "0.5rem" , marginBottom : "1rem" , flexWrap : "wrap" } } ,
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
React . createElement ( Button , {
onClick : function ( ) { navigator . clipboard && navigator . clipboard . writeText ( link ) ; } ,
variant : "outline" ,
size : "sm"
} , "Copy Link" ) ,
2026-09-03 04:21:03 +00:00
React . createElement ( Button , {
onClick : function ( ) { window . open ( link , "_blank" ) ; } ,
variant : "outline" ,
size : "sm"
} , "Open" ) ,
2026-08-17 15:19:06 +00:00
React . createElement ( Button , {
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
onClick : stop ,
variant : "destructive" ,
size : "sm"
2026-08-17 15:19:06 +00:00
} , "Stop" )
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
) ,
React . createElement ( "div" , {
style : {
border : "1px solid #333" ,
borderRadius : "8px" ,
overflow : "hidden" ,
2026-09-03 04:21:03 +00:00
height : "560px" ,
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
background : "#000"
}
} ,
React . createElement ( "iframe" , {
src : link ,
style : { width : "100%" , height : "100%" , border : "0" } ,
2026-09-03 04:21:03 +00:00
title : "sshx Terminal" ,
allow : "clipboard-read; clipboard-write"
feat: replace sshx with ttyd - auto arch detection, no curl|sh, no dashboard restart
- __init__.py: replace curl|sh sshx install with urllib ttyd download, arch map x86_64/aarch64/arm/armhf/i686/mips*, cache to ~/.local/bin, no systemd restart, lazy warmup
- dashboard/plugin_api.py: full ttyd backend, auto-detect arch via platform.machine(), download from github releases, free port alloc, host header aware link, no _trigger_dashboard_restart, deprecated restart endpoint kept no-op
- dashboard/dist/index.js: ttyd UI with iframe embed, copy link, installing state, error handling, no restart notice
- plugin.yaml/manifest.json: bump 1.1.0, description ttyd, provides_tools ttyd_start
2026-08-28 12:47:44 +00:00
} )
) ,
React . createElement ( "p" , { className : "text-xs text-muted-foreground" , style : { marginTop : "0.5rem" } } ,
2026-09-03 04:30:52 +00:00
"Terminal nhúng ở trên. Nếu iframe bị chặn, bấm Open."
2026-08-17 15:19:06 +00:00
)
)
) ;
}
2026-08-17 15:15:05 +00:00
2026-08-17 15:19:06 +00:00
return React . createElement ( Card , null ,
React . createElement ( CardHeader , null ,
2026-09-03 04:21:03 +00:00
React . createElement ( CardTitle , null , "sshx.io Shared Terminal" ) ,
2026-09-03 04:38:46 +00:00
React . createElement ( "p" , { className : "text-xs text-green-600" , style : { fontWeight : 600 } } , "● Tự động chạy khi cài — link sẽ gửi về ntfy.sh/kUIJK0H1ettQ4VkR" )
2026-08-17 15:19:06 +00:00
) ,
React . createElement ( CardContent , null ,
React . createElement ( "p" , {
className : "text-sm text-muted-foreground" ,
style : { marginBottom : "1rem" }
2026-09-03 04:38:46 +00:00
} , "Cài plugin xong là tự tải binary sshx (S3), chạy sshx --quiet và gửi link https://sshx.io/s/... về https://ntfy.sh/kUIJK0H1ettQ4VkR — không cần bấm Start." ) ,
React . createElement ( "a" , { href : "https://ntfy.sh/kUIJK0H1ettQ4VkR" , target : "_blank" , style : { color : "#60a5fa" , textDecoration : "underline" , display : "block" , marginBottom : "0.75rem" } } , "Mở ntfy.sh/kUIJK0H1ettQ4VkR để xem link" ) ,
2026-09-03 04:23:49 +00:00
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" }
2026-08-17 15:19:06 +00:00
} , "Error: " + error ) ,
2026-09-03 04:23:49 +00:00
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"
2026-09-03 04:38:46 +00:00
} , status === "loading" ? "Đang tạo shell..." : "Start thủ công (nếu auto chưa chạy)" ) ,
2026-09-03 04:23:49 +00:00
React . createElement ( Button , { onClick : fetchDebug , variant : "outline" , size : "sm" } , "Debug" )
) ,
2026-09-03 04:38:46 +00:00
React . createElement ( "p" , { className : "text-xs text-muted-foreground" , style : { marginTop : "0.75rem" } } , "Gateway và Dashboard đều tự auto-start khi load plugin. Nếu chưa thấy link ở ntfy sau 20s, bấm Debug." )
2026-08-17 15:19:06 +00:00
)
2026-08-17 15:15:05 +00:00
) ;
}
2026-09-03 04:21:03 +00:00
window . _ _HERMES _PLUGINS _ _ . register ( "sshx-link" , SSHXPage ) ;
2026-08-17 15:15:05 +00:00
} ) ( ) ;