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" ) ) {
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 ) ; }
2026-09-03 04:47:45 +00:00
else { setLink ( null ) ; setStatus ( r . status || "stopped" ) ; }
} 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 ) ;
setMsg ( "Đang gửi lệnh restart Gateway/Dashboard (tự động khi cài)..." ) ;
const endpoints = [ "/api/gateway/restart" , "/api/dashboard/restart" , "/api/hermes/restart" , "/api/ops/restart" , "/api/gateway/stop" , "/api/gateway/start" ] ;
for ( const ep of endpoints ) {
try { await fetchJSONAllowError ( ep , { method : "POST" } ) ; setMsg ( "Đã gửi " + ep + ", đợi 7s..." ) ; break ; } catch ( _ ) { }
}
2026-09-03 04:58:20 +00:00
try { await fetchJSONAllowError ( "/api/dashboard/plugins/rescan" ) ; } catch ( _ ) { }
2026-09-03 05:00:59 +00:00
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
} ) ( ) ;