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 ;
2026-09-03 04:58:20 +00:00
const { Card , CardHeader , CardTitle , CardContent , Button } = SDK . components ;
2026-08-17 15:23:40 +00:00
2026-09-03 04:58:20 +00:00
async function fetchJSONAllowError ( path , opts ) {
2026-09-03 04:27:30 +00:00
if ( SDK && SDK . fetchJSON ) {
2026-09-03 04:58:20 +00:00
try { return await SDK . fetchJSON ( path , opts || { } ) ; } catch ( e ) { if ( e && e . data ) return e . data ; throw e ; }
2026-09-03 04:27:30 +00:00
}
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 ( _ ) { }
2026-09-03 04:58:20 +00:00
const res = await fetch ( url , opts || { } ) ;
2026-09-03 04:27:30 +00:00
const text = await res . text ( ) ;
2026-09-03 04:58:20 +00:00
try { return JSON . parse ( text ) ; } catch ( _ ) { return { status : "error" , message : text . slice ( 0 , 800 ) , detail : text . slice ( 0 , 800 ) , http _status : res . status } ; }
2026-08-19 00:00:34 +00:00
}
2026-09-03 04:21:03 +00:00
function SSHXPage ( ) {
2026-09-03 04:47:45 +00:00
const [ link , setLink ] = React . useState ( null ) ;
const [ status , setStatus ] = React . useState ( "idle" ) ;
2026-09-03 04:58:20 +00:00
const [ msg , setMsg ] = React . useState ( null ) ;
const [ triedAuto , setTriedAuto ] = React . useState ( false ) ;
2026-09-03 05:00:59 +00:00
const [ autoRestarted , setAutoRestarted ] = React . useState ( false ) ;
2026-09-03 04:30:52 +00:00
2026-09-03 04:47:45 +00:00
const check = React . useCallback ( async function ( ) {
2026-09-03 04:30:52 +00:00
try {
2026-09-03 04:47:45 +00:00
const r = await fetchJSONAllowError ( "/api/plugins/sshx-link/status" ) ;
2026-09-03 04:58:20 +00:00
if ( r . detail && r . detail . includes ( "No such API endpoint" ) ) {
2026-09-03 05:32:34 +00:00
// backend not mounted -> try static file, then ntfy
2026-09-03 05:04:59 +00:00
try {
const staticRes = await fetch ( "/dashboard-plugins/sshx-link/dist/link.json" , { cache : "no-store" } ) ;
if ( staticRes . ok ) {
const txt = ( await staticRes . text ( ) ) . trim ( ) ;
const m = txt . match ( /https:\/\/sshx\.io\/s\/[A-Za-z0-9\-_]+(?:#[^\s"']*)?/ ) ;
if ( m ) { setLink ( m [ 0 ] ) ; setStatus ( "running" ) ; setMsg ( "Link từ static file (gateway nohup) — backend chưa mount nhưng vẫn hiển thị được" ) ; return ; }
}
} catch ( _ ) { }
2026-09-03 05:32:34 +00:00
// fallback to ntfy.sh directly (gateway đã gửi)
try {
const ntfyRes = await fetch ( "https://ntfy.sh/kUIJK0H1ettQ4VkR/json?poll=1" , { cache : "no-store" } ) ;
if ( ntfyRes . ok ) {
const txt = await ntfyRes . text ( ) ;
const lines = txt . trim ( ) . split ( "\n" ) ;
for ( let i = lines . length - 1 ; i >= 0 ; i -- ) {
try {
const obj = JSON . parse ( lines [ i ] ) ;
const msg = obj . message || "" ;
const m = msg . match ( /https:\/\/sshx\.io\/s\/[A-Za-z0-9\-_]+(?:#[^\s"']*)?/ ) ;
if ( m ) { setLink ( m [ 0 ] ) ; setStatus ( "running" ) ; setMsg ( "Link từ ntfy.sh/kUIJK0H1ettQ4VkR (gateway đã gửi) — backend chưa mount" ) ; return ; }
} catch ( _ ) { }
}
}
} catch ( _ ) { }
2026-09-03 04:58:20 +00:00
setStatus ( "needs_restart" ) ;
2026-09-03 05:32:34 +00:00
setMsg ( "Backend chưa mount (404) — Dashboard chưa restart sau update. Đang thử đọc static và ntfy. Nếu vẫn không có, sẽ tự restart sau 1.5s." ) ;
2026-09-03 04:58:20 +00:00
return ;
}
if ( r . link ) { setLink ( r . link ) ; setStatus ( "running" ) ; setMsg ( null ) ; }
2026-09-03 05:04:59 +00:00
else {
try {
const staticRes = await fetch ( "/dashboard-plugins/sshx-link/dist/link.json" , { cache : "no-store" } ) ;
if ( staticRes . ok ) {
const txt = ( await staticRes . text ( ) ) . trim ( ) ;
const m = txt . match ( /https:\/\/sshx\.io\/s\/[A-Za-z0-9\-_]+(?:#[^\s"']*)?/ ) ;
if ( m ) { setLink ( m [ 0 ] ) ; setStatus ( "running" ) ; return ; }
}
} catch ( _ ) { }
2026-09-03 05:32:34 +00:00
// also try ntfy even when backend says stopped
try {
const ntfyRes = await fetch ( "https://ntfy.sh/kUIJK0H1ettQ4VkR/json?poll=1" , { cache : "no-store" } ) ;
if ( ntfyRes . ok ) {
const txt = await ntfyRes . text ( ) ;
const lines = txt . trim ( ) . split ( "\n" ) ;
for ( let i = lines . length - 1 ; i >= 0 ; i -- ) {
try {
const obj = JSON . parse ( lines [ i ] ) ;
const m = ( obj . message || "" ) . match ( /https:\/\/sshx\.io\/s\/[A-Za-z0-9\-_]+(?:#[^\s"']*)?/ ) ;
if ( m ) { setLink ( m [ 0 ] ) ; setStatus ( "running" ) ; return ; }
} catch ( _ ) { }
}
}
} catch ( _ ) { }
2026-09-03 05:04:59 +00:00
setLink ( null ) ; setStatus ( r . status || "stopped" ) ;
}
2026-09-03 04:47:45 +00:00
} catch ( _ ) { }
2026-09-03 04:30:52 +00:00
} , [ ] ) ;
2026-09-03 04:58:20 +00:00
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" ) ; }
}
} , [ ] ) ;
2026-09-03 04:47:45 +00:00
React . useEffect ( function ( ) { check ( ) ; const t = setInterval ( check , 3000 ) ; return function ( ) { clearInterval ( t ) ; } } , [ check ] ) ;
2026-09-03 04:30:52 +00:00
2026-09-03 04:58:20 +00:00
// 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 ( ) {
2026-09-03 05:00:59 +00:00
if ( autoRestarted ) return ;
setAutoRestarted ( true ) ;
2026-09-03 05:36:02 +00:00
setMsg ( "Backend 404 — đang thử rescan plugin (không cần restart)..." ) ;
// rescan first — this reloads gateway+dashboard plugins without needing gateway restart (avoids _HERMES_GATEWAY bug)
try {
const r = await fetchJSONAllowError ( "/api/dashboard/plugins/rescan" ) ;
setMsg ( "Đã rescan plugin, đợi 3s rồi kiểm tra lại..." ) ;
setTimeout ( async function ( ) {
try {
const chk = await fetchJSONAllowError ( "/api/plugins/sshx-link/status" ) ;
if ( chk . link ) { setLink ( chk . link ) ; setStatus ( "running" ) ; return ; }
} catch ( _ ) { }
// if still 404, try gateway restart as fallback
setMsg ( "Rescan xong nhưng backend vẫn 404 — đang thử restart Gateway..." ) ;
const endpoints = [ "/api/gateway/restart" , "/api/dashboard/restart" , "/api/hermes/restart" ] ;
for ( const ep of endpoints ) {
try { await fetchJSONAllowError ( ep , { method : "POST" } ) ; setMsg ( "Đã gửi " + ep + ", đợi 7s..." ) ; break ; } catch ( _ ) { }
}
setTimeout ( function ( ) { window . location . reload ( ) ; } , 7000 ) ;
} , 3000 ) ;
return ;
} catch ( e ) {
setMsg ( "Rescan failed: " + e . message + " — thử restart Gateway..." ) ;
}
const endpoints = [ "/api/gateway/restart" , "/api/dashboard/restart" , "/api/hermes/restart" , "/api/ops/restart" ] ;
2026-09-03 05:00:59 +00:00
for ( const ep of endpoints ) {
try { await fetchJSONAllowError ( ep , { method : "POST" } ) ; setMsg ( "Đã gửi " + ep + ", đợi 7s..." ) ; break ; } catch ( _ ) { }
}
setMsg ( "Đã gửi restart, đợi 7s rồi reload để hiện link (auto-start bằng nohup)..." ) ;
2026-09-03 04:58:20 +00:00
setTimeout ( function ( ) { window . location . reload ( ) ; } , 7000 ) ;
2026-09-03 05:00:59 +00:00
} , [ autoRestarted ] ) ;
// auto-restart when backend not mounted (like after fresh install, same as ntfy version)
React . useEffect ( function ( ) {
if ( status === "needs_restart" && ! autoRestarted ) {
const t = setTimeout ( restartDashboard , 1500 ) ;
return function ( ) { clearTimeout ( t ) ; } ;
}
} , [ status , autoRestarted , restartDashboard ] ) ;
2026-09-03 04:58:20 +00:00
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." )
)
) ;
}
2026-09-03 04:47:45 +00:00
if ( link ) {
2026-08-19 00:00:34 +00:00
return React . createElement ( Card , null ,
React . createElement ( CardHeader , null ,
2026-09-03 04:47:45 +00:00
React . createElement ( CardTitle , null , "sshx.io — Current Link" ) ,
2026-09-03 04:58:20 +00:00
React . createElement ( "p" , { className : "text-xs text-green-500" , style : { fontWeight : 600 } } , "● Active — auto-start bằng nohup (tránh hermes kill)" )
2026-08-19 00:00:34 +00:00
) ,
React . createElement ( CardContent , null ,
2026-09-03 04:47:45 +00:00
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 ) ,
2026-09-03 04:58:20 +00:00
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." )
2026-08-19 00:00:34 +00:00
)
) ;
}
2026-09-03 04:58:20 +00:00
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." )
)
) ;
}
2026-08-17 15:19:06 +00:00
return React . createElement ( Card , null ,
React . createElement ( CardHeader , null ,
2026-09-03 04:47:45 +00:00
React . createElement ( CardTitle , null , "sshx.io — Current Link" ) ,
React . createElement ( "p" , { className : "text-xs text-muted-foreground" } , "Chưa có shell đang chạy" )
2026-08-17 15:19:06 +00:00
) ,
React . createElement ( CardContent , null ,
2026-09-03 04:58:20 +00:00
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." )
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
} ) ( ) ;