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-08-19 00:00:34 +00:00
async function safeFetchJSON ( path , opts ) {
const res = await fetch ( path , opts || { } ) ;
const text = await res . text ( ) ;
try { return JSON . parse ( text ) ; } catch ( _ ) {
2026-09-03 04:23:49 +00:00
throw new Error ( "Server returned non-JSON (status " + res . status + "): " + 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 ) ;
const fetchDebug = useCallback ( async function ( ) {
try { const d = await safeFetchJSON ( "/api/plugins/sshx-link/debug" ) ; setDebug ( d ) ; } catch ( _ ) { }
} , [ ] ) ;
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 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:23:49 +00:00
var st = await safeFetchJSON ( "/api/plugins/sshx-link/status" ) ;
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:23:49 +00:00
if ( st . status === "error" && st . message ) {
// show previous error but try to restart anyway
}
var res = await safeFetchJSON ( "/api/plugins/sshx-link/start" ) ;
// res may be 202 starting
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:23:49 +00:00
setError ( ( res . message || 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:23:49 +00:00
// err may contain JSON body in message
2026-09-03 04:21:03 +00:00
setError ( err . message || "Failed to start sshx" ) ;
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
}
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-08-19 00:00:34 +00:00
await safeFetchJSON ( "/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-08-19 00:00:34 +00:00
const res = await safeFetchJSON ( "/api/plugins/sshx-link/status" ) ;
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 ( ) ;
} , [ checkStatus ] ) ;
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: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..." ) ,
React . createElement ( "p" , { className : "text-xs text-muted-foreground" , style : { marginTop : "0.5rem" } } , "Tự động thử lại mỗi 2.5s. Nếu kẹt quá 20s, kiểm tra outbound tới s3.amazonaws.com và sshx.io trên server 13.140.132.105." ) ,
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:23:49 +00:00
"Terminal nhúng ở trên. Nếu iframe bị chặn, bấm Open. Link dạng https://sshx.io/s/<id>#<key> — chia sẻ cho user là xong."
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" ) ,
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" )
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:23:49 +00:00
} , "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" }
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"
} , 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 lỗi 'No link returned' hoặc timeout, bấm Debug để xem arch/url/binary, kiểm tra server có ra được s3.amazonaws.com và sshx.io không (firewall/security group)." )
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
} ) ( ) ;