diff --git a/betterdesk-agent-client/src-tauri/Cargo.toml b/betterdesk-agent-client/src-tauri/Cargo.toml index 5f8152c8..7451320c 100644 --- a/betterdesk-agent-client/src-tauri/Cargo.toml +++ b/betterdesk-agent-client/src-tauri/Cargo.toml @@ -20,7 +20,7 @@ tauri-plugin-notification = "2" tauri-plugin-single-instance = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" -tokio = { version = "1", features = ["rt-multi-thread", "net", "sync", "time", "macros", "process", "io-util"] } +tokio = { version = "1", features = ["rt-multi-thread", "net", "sync", "time", "macros", "process", "io-util", "signal"] } log = "0.4" env_logger = "0.11" anyhow = "1" diff --git a/betterdesk-agent-client/src-tauri/binaries/betterdesk-agent-x86_64-unknown-linux-gnu b/betterdesk-agent-client/src-tauri/binaries/betterdesk-agent-x86_64-unknown-linux-gnu index ce4ad31e..4b833876 100755 Binary files a/betterdesk-agent-client/src-tauri/binaries/betterdesk-agent-x86_64-unknown-linux-gnu and b/betterdesk-agent-client/src-tauri/binaries/betterdesk-agent-x86_64-unknown-linux-gnu differ diff --git a/betterdesk-agent-client/src-tauri/src/autostart.rs b/betterdesk-agent-client/src-tauri/src/autostart.rs index e6493b6f..9c785be2 100644 --- a/betterdesk-agent-client/src-tauri/src/autostart.rs +++ b/betterdesk-agent-client/src-tauri/src/autostart.rs @@ -22,13 +22,19 @@ pub fn sync_os_autostart(app: &AppHandle, enabled: bool) { let manager = app.autolaunch(); match manager.is_enabled() { - Ok(current) if current == enabled => { + Ok(current) if current == enabled && !enabled => { info!( "[autostart] Already {} — no change", if enabled { "enabled" } else { "disabled" } ); return; } + Ok(current) if current == enabled && enabled => { + info!("[autostart] Refreshing enabled OS registration"); + if let Err(e) = manager.disable() { + warn!("[autostart] Failed to refresh registration: {}", e); + } + } Ok(_) => {} Err(e) => { warn!("[autostart] Failed to query state: {}", e); diff --git a/betterdesk-agent-client/src-tauri/src/commands.rs b/betterdesk-agent-client/src-tauri/src/commands.rs index c3b52e1a..cd139f0a 100644 --- a/betterdesk-agent-client/src-tauri/src/commands.rs +++ b/betterdesk-agent-client/src-tauri/src/commands.rs @@ -1,6 +1,7 @@ -use crate::cdap_client::{CdapClient, CdapStatus}; +use crate::cdap_client::CdapClient; use crate::config::AgentConfig; use crate::registration; +use crate::sidecar::{SidecarConfig, SidecarStatus}; use crate::sysinfo_collect::SystemSnapshot; use log::info; use serde::{Deserialize, Serialize}; @@ -12,8 +13,14 @@ use tauri::Manager; pub struct AgentState { pub config: Mutex, pub chat_history: Mutex>, - /// Native CDAP WebSocket client (replaces Go sidecar). + /// Native CDAP WebSocket client kept for the lightweight telemetry path. + /// The managed Go sidecar below is the active runtime for full remote + /// desktop parity. pub cdap: CdapClient, + /// Managed Go agent sidecar. This is the active runtime for remote desktop + /// parity because it contains desktop streaming, input, monitor, and + /// consent handlers that the native Rust CDAP client does not yet provide. + pub sidecar: crate::sidecar::SidecarManager, } /// Chat message structure. @@ -86,8 +93,9 @@ pub fn is_os_admin() -> bool { /// Called from the overflow menu "Close agent" button or the quit dialog. #[tauri::command] pub fn quit_app(app: tauri::AppHandle) { - // Stop the sidecar gracefully before exit. + // Stop background runtimes gracefully before exit. let state = app.state::(); + state.sidecar.stop(); state.cdap.stop(); app.exit(0); } @@ -665,7 +673,7 @@ pub async fn discover_lan_servers() -> Result, String> // ─────────────────────────── CDAP client control ─────────────────────────── -async fn build_cdap_config(state: &AgentState) -> Result { +async fn build_sidecar_config(state: &AgentState) -> Result { let mut config = { let config = state.config.lock().map_err(|e| e.to_string())?; if !config.is_registered() { @@ -682,57 +690,81 @@ async fn build_cdap_config(state: &AgentState) -> Result) -> CdapStatus { - state.cdap.status() +pub fn get_sidecar_status(state: State<'_, AgentState>) -> SidecarStatus { + state.sidecar.status() } -/// Start or restart the native CDAP client. +/// Start or restart the managed Go CDAP sidecar. #[tauri::command] -pub async fn start_sidecar(_app: tauri::AppHandle, state: State<'_, AgentState>) -> Result { - let cdap_cfg = build_cdap_config(&state).await?; - state.cdap.stop(); - state.cdap.start(&cdap_cfg).map_err(|e| e.to_string())?; - info!("CDAP client started via IPC command"); - Ok(state.cdap.status()) +pub async fn start_sidecar( + app: tauri::AppHandle, + state: State<'_, AgentState>, +) -> Result { + if !crate::privileges::is_os_admin() { + return Err("Administrator privileges are required to control the CDAP agent".to_string()); + } + + let sidecar_cfg = build_sidecar_config(&state).await?; + state.sidecar.stop(); + state + .sidecar + .start(&sidecar_cfg, app) + .map_err(|e| e.to_string())?; + info!("CDAP sidecar started via IPC command"); + Ok(state.sidecar.status()) } /// Stop the CDAP client. #[tauri::command] -pub fn stop_sidecar(state: State<'_, AgentState>) -> CdapStatus { - state.cdap.stop(); - info!("CDAP client stopped via IPC command"); - state.cdap.status() +pub fn stop_sidecar(state: State<'_, AgentState>) -> Result { + if !crate::privileges::is_os_admin() { + return Err("Administrator privileges are required to stop the CDAP agent".to_string()); + } + + state.sidecar.stop(); + info!("CDAP sidecar stopped via IPC command"); + Ok(state.sidecar.status()) } /// Restart the CDAP client (re-reads current config). #[tauri::command] -pub async fn restart_sidecar(app: tauri::AppHandle, state: State<'_, AgentState>) -> Result { +pub async fn restart_sidecar( + app: tauri::AppHandle, + state: State<'_, AgentState>, +) -> Result { start_sidecar(app, state).await } /// Legacy command — redirects to CDAP restart. #[tauri::command] -pub async fn restart_agent_service(app: tauri::AppHandle, state: State<'_, AgentState>) -> Result<(), String> { +pub async fn restart_agent_service( + app: tauri::AppHandle, + state: State<'_, AgentState>, +) -> Result<(), String> { start_sidecar(app, state).await.map(|_| ()) } -/// No-op — consent is now handled natively inside cdap_client.rs. +/// Forward a supervised-session consent response to the Go sidecar. #[tauri::command] pub fn answer_consent( - _state: State<'_, AgentState>, - _session_id: String, - _granted: bool, + state: State<'_, AgentState>, + session_id: String, + granted: bool, ) -> Result<(), String> { + state.sidecar.send_consent(&session_id, granted); Ok(()) } #[tauri::command] pub fn unregister_device(state: State<'_, AgentState>) -> Result<(), String> { + state.sidecar.stop(); + state.cdap.stop(); + let mut config = state.config.lock().map_err(|e| e.to_string())?; let old_id = config.device_id.clone(); diff --git a/betterdesk-agent-client/src-tauri/src/config.rs b/betterdesk-agent-client/src-tauri/src/config.rs index 6a4e1bf5..c0836d51 100644 --- a/betterdesk-agent-client/src-tauri/src/config.rs +++ b/betterdesk-agent-client/src-tauri/src/config.rs @@ -207,10 +207,26 @@ impl AgentConfig { } } - /// Build a `SidecarConfig` — kept for backward compatibility, delegates to CdapConfig. - #[deprecated(note = "Use to_cdap_config() — sidecar is replaced by native CDAP client")] - pub fn to_sidecar_config(&self) -> crate::cdap_client::CdapConfig { - self.to_cdap_config() + /// Build a `SidecarConfig` for the bundled Go agent runtime. + pub fn to_sidecar_config(&self) -> crate::sidecar::SidecarConfig { + let data_dir = directories::ProjectDirs::from("com", "betterdesk", "agent") + .map(|d| d.data_dir().to_path_buf()) + .unwrap_or_else(|| PathBuf::from(".")); + + crate::sidecar::SidecarConfig { + server_address: self.server_address.clone(), + device_id: self.device_id.clone(), + device_name: self.device_name.clone(), + api_key: self.api_key.clone(), + auth_token: self.auth_token.clone(), + allow_terminal: self.allow_terminal, + allow_file_browser: self.allow_file_browser, + allow_clipboard: self.allow_clipboard, + allow_screen_capture: self.allow_screen_capture, + require_consent: self.require_consent, + data_dir, + cdap_port: self.cdap_port, + } } /// Store credentials securely via OS keyring. diff --git a/betterdesk-agent-client/src-tauri/src/lib.rs b/betterdesk-agent-client/src-tauri/src/lib.rs index 39481881..f83e0cc1 100644 --- a/betterdesk-agent-client/src-tauri/src/lib.rs +++ b/betterdesk-agent-client/src-tauri/src/lib.rs @@ -14,6 +14,7 @@ pub mod commands; pub mod config; pub mod privileges; pub mod registration; +pub mod sidecar; pub mod sysinfo_collect; use log::info; @@ -28,9 +29,9 @@ use tauri::Manager; #[allow(dead_code)] struct TrayState(tauri::tray::TrayIcon); -async fn resolve_cdap_config_from_state( +async fn resolve_config_from_state( app: &tauri::AppHandle, -) -> Option { +) -> Option { let mut config = { let state = app.try_state::()?; let guard = state.config.lock().ok()?; @@ -48,7 +49,7 @@ async fn resolve_cdap_config_from_state( } } - Some(config.to_cdap_config()) + Some(config) } /// Spawn a background task that sends `POST /api/heartbeat` every 12 seconds. @@ -169,6 +170,86 @@ fn push_sysinfo_refresh(app: &tauri::AppHandle) { }); } +fn notify_agent_ready(app: &tauri::AppHandle) { + let app_handle = app.clone(); + tauri::async_runtime::spawn(async move { + tokio::time::sleep(std::time::Duration::from_secs(4)).await; + + let (registered, cdap_running, language) = app_handle + .try_state::() + .map(|state| { + let (registered, language) = state + .config + .lock() + .map(|config| (config.is_registered(), config.language.clone())) + .unwrap_or((false, "en".to_string())); + (registered, state.sidecar.is_running(), language) + }) + .unwrap_or((false, false, "en".to_string())); + + if !registered { + return; + } + + let body = match (language.as_str(), cdap_running) { + ("pl", true) => "Agent działa w tle, a CDAP jest połączony.", + ("pl", false) => "Agent działa w tle. CDAP połączy się ponownie automatycznie.", + ("zh" | "zh-TW", true) => "代理正在背景執行,CDAP 已連線。", + ("zh" | "zh-TW", false) => "代理正在背景執行。CDAP 會自動重新連線。", + (_, true) => "Agent is running in the background and CDAP is connected.", + (_, false) => "Agent is running in the background. CDAP will reconnect automatically.", + }; + + use tauri_plugin_notification::NotificationExt; + if let Err(e) = app_handle + .notification() + .builder() + .title("BetterDesk Agent") + .body(body) + .show() + { + log::debug!("Startup notification skipped: {}", e); + } + }); +} + +#[cfg(unix)] +fn install_shutdown_signal_handlers(app: &tauri::AppHandle) { + let app_handle = app.clone(); + tauri::async_runtime::spawn(async move { + use tokio::signal::unix::{signal, SignalKind}; + + let mut sigterm = match signal(SignalKind::terminate()) { + Ok(signal) => signal, + Err(e) => { + log::warn!("Could not install SIGTERM handler: {}", e); + return; + } + }; + let mut sigint = match signal(SignalKind::interrupt()) { + Ok(signal) => signal, + Err(e) => { + log::warn!("Could not install SIGINT handler: {}", e); + return; + } + }; + + tokio::select! { + _ = sigterm.recv() => log::info!("SIGTERM received — exiting for system shutdown/restart"), + _ = sigint.recv() => log::info!("SIGINT received — exiting"), + } + + if let Some(state) = app_handle.try_state::() { + state.sidecar.stop(); + state.cdap.stop(); + } + app_handle.exit(0); + }); +} + +#[cfg(not(unix))] +fn install_shutdown_signal_handlers(_app: &tauri::AppHandle) {} + /// Entry point — called from main.rs. pub fn run() { // WebKitGTK Wayland workaround: prevent Gdk "Error 71 (Protocol error) @@ -209,7 +290,7 @@ pub fn run() { ); let cdap_client = cdap_client::CdapClient::new(); - let cdap_client_clone = cdap_client.clone(); + let sidecar_manager = sidecar::SidecarManager::new(); tauri::Builder::default() .plugin(tauri_plugin_shell::init()) @@ -229,6 +310,7 @@ pub fn run() { config: Mutex::new(settings), chat_history: Mutex::new(Vec::new()), cdap: cdap_client, + sidecar: sidecar_manager, }) .invoke_handler(tauri::generate_handler![ // Status & lifecycle @@ -280,6 +362,8 @@ pub fn run() { let tray = setup_tray(app.handle())?; app.manage(TrayState(tray)); + install_shutdown_signal_handlers(app.handle()); + let is_autostart = std::env::args().any(|a| a == "--autostart"); // On first run (device not registered yet): show the window so the @@ -287,7 +371,9 @@ pub fn run() { // click the tray icon. if !is_registered && !is_autostart { if let Some(window) = app.get_webview_window("main") { + let _ = window.unminimize(); let _ = window.show(); + let _ = window.set_focus(); } } @@ -298,19 +384,22 @@ pub fn run() { } } - // Auto-start native CDAP client if device is registered and setting is on. + // Auto-start the managed Go CDAP sidecar if device is registered and setting is on. if auto_start { let app_handle = app.handle().clone(); - let cdap = cdap_client_clone.clone(); tauri::async_runtime::spawn(async move { - info!("[cdap] Auto-starting native CDAP client..."); - let Some(cdap_cfg) = resolve_cdap_config_from_state(&app_handle).await else { - info!("[cdap] Auto-start skipped: device not registered"); + info!("[sidecar] Auto-starting Go CDAP agent..."); + let Some(mut config) = resolve_config_from_state(&app_handle).await else { + info!("[sidecar] Auto-start skipped: device not registered"); return; }; - if let Err(e) = cdap.start(&cdap_cfg) { - log::warn!("[cdap] Auto-start failed: {}", e); + registration::normalize_server_origin_best_effort(&mut config).await; + let sidecar_cfg = config.to_sidecar_config(); + if let Some(state) = app_handle.try_state::() { + if let Err(e) = state.sidecar.start(&sidecar_cfg, app_handle.clone()) { + log::warn!("[sidecar] Auto-start failed: {}", e); + } } }); } @@ -326,6 +415,8 @@ pub fn run() { push_sysinfo_refresh(app.handle()); } + notify_agent_ready(app.handle()); + // Start the bd-signal WS client — answers operator-initiated // introspection requests (services, processes, files, screenshot, // terminal). Idempotent: silently no-ops until registered. @@ -341,12 +432,9 @@ pub fn run() { }) .on_window_event(|window, event| { if let tauri::WindowEvent::CloseRequested { api, .. } = event { - use tauri::Emitter; - api.prevent_close(); - info!("Close requested from window chrome"); - let _ = window.set_focus(); - let _ = window.app_handle().emit("request-quit", ()); + info!("Close requested from window chrome — hiding to tray"); + let _ = window.hide(); } }) .run(tauri::generate_context!()) @@ -377,7 +465,8 @@ fn setup_tray( let chat = MenuItemBuilder::with_id("chat", "Chat").build(app)?; let check = MenuItemBuilder::with_id("check_conn", "Check connection").build(app)?; - // Sidecar control (visible to all — non-admin gets informational deny). + // CDAP control is admin-only; regular users must not be able to interrupt + // the background connection from the tray menu. let sep1 = PredefinedMenuItem::separator(app)?; let sidecar_toggle = MenuItemBuilder::with_id("sidecar_toggle", "Restart CDAP agent").build(app)?; @@ -386,13 +475,17 @@ fn setup_tray( let settings = MenuItemBuilder::with_id("settings", "Settings").build(app)?; let quit = MenuItemBuilder::with_id("quit", "Quit agent").build(app)?; - let builder = MenuBuilder::new(app) + let mut builder = MenuBuilder::new(app) .item(&show_id) .item(&help) .item(&chat) - .item(&check) - .item(&sep1) - .item(&sidecar_toggle) + .item(&check); + + if is_admin { + builder = builder.item(&sep1).item(&sidecar_toggle); + } + + let builder = builder .item(&sep2) .item(&settings) .item(&quit); @@ -423,20 +516,22 @@ fn setup_tray( "chat" => show_window(app, "/chat"), "check_conn" => show_window(app, "/?action=reconnect"), "sidecar_toggle" => { - // Restart the native CDAP client on demand. + // Restart the managed Go CDAP sidecar on demand. let app_handle = app.clone(); tauri::async_runtime::spawn(async move { - let Some(cdap_cfg) = resolve_cdap_config_from_state(&app_handle).await else { - info!("[tray] CDAP restart: device not registered"); + let Some(mut config) = resolve_config_from_state(&app_handle).await else { + info!("[tray] CDAP sidecar restart: device not registered"); return; }; + registration::normalize_server_origin_best_effort(&mut config).await; + let sidecar_cfg = config.to_sidecar_config(); if let Some(state) = app_handle.try_state::() { - state.cdap.stop(); - if let Err(e) = state.cdap.start(&cdap_cfg) { - log::warn!("[tray] CDAP restart failed: {}", e); + state.sidecar.stop(); + if let Err(e) = state.sidecar.start(&sidecar_cfg, app_handle.clone()) { + log::warn!("[tray] CDAP sidecar restart failed: {}", e); } else { - info!("[tray] CDAP client restarted"); + info!("[tray] CDAP sidecar restarted"); } } }); diff --git a/betterdesk-agent-client/src-tauri/src/sidecar.rs b/betterdesk-agent-client/src-tauri/src/sidecar.rs index b44be313..3c615344 100644 --- a/betterdesk-agent-client/src-tauri/src/sidecar.rs +++ b/betterdesk-agent-client/src-tauri/src/sidecar.rs @@ -70,15 +70,15 @@ pub struct SidecarStatus { /// Fields match `betterdesk-agent/agent/config.go`. #[derive(Debug, serde::Serialize, serde::Deserialize)] struct GoAgentConfig { - server: String, // ws://host:21122/cdap - auth_method: String, // api_key | device_token | user_password + server: String, // ws://host:21122/cdap + auth_method: String, // api_key | device_token | user_password #[serde(skip_serializing_if = "String::is_empty")] api_key: String, #[serde(skip_serializing_if = "String::is_empty")] device_token: String, device_id: String, device_name: String, - device_type: String, // os_agent | desktop | custom + device_type: String, // os_agent | desktop | custom #[serde(skip_serializing_if = "Vec::is_empty")] tags: Vec, @@ -86,6 +86,7 @@ struct GoAgentConfig { file_browser: bool, clipboard: bool, screenshot: bool, + require_consent: bool, heartbeat_sec: u32, reconnect_sec: u32, @@ -108,6 +109,7 @@ pub struct SidecarConfig { pub allow_file_browser: bool, pub allow_clipboard: bool, pub allow_screen_capture: bool, + pub require_consent: bool, pub data_dir: PathBuf, pub cdap_port: u16, } @@ -129,7 +131,15 @@ impl SidecarConfig { } else { host.to_string() }; - let ws_scheme = if parsed.scheme() == "https" { "wss" } else { "ws" }; + // CDAP runs on its own gateway port. Do not inherit the HTTP API + // scheme: a server can expose HTTPS on 21114 while CDAP on 21122 is + // plain WS. Operators can explicitly opt into WSS for CDAP with + // BETTERDESK_CDAP_TLS=1 when the server is started with --tls-cdap. + let ws_scheme = if std::env::var("BETTERDESK_CDAP_TLS").as_deref() == Ok("1") { + "wss" + } else { + "ws" + }; format!("{}://{}:{}/cdap", ws_scheme, host_part, self.cdap_port) } else { format!("ws://{}:{}/cdap", addr, self.cdap_port) @@ -160,6 +170,7 @@ struct Inner { binary_path: Mutex, cdap_url: Mutex, config_path: Mutex, + app_handle: Mutex>, /// When set to true the monitor loop will not restart. stop_requested: AtomicBool, } @@ -176,6 +187,7 @@ impl SidecarManager { binary_path: Mutex::new(PathBuf::new()), cdap_url: Mutex::new(String::new()), config_path: Mutex::new(PathBuf::new()), + app_handle: Mutex::new(None), stop_requested: AtomicBool::new(false), }), } @@ -185,11 +197,12 @@ impl SidecarManager { /// Write Go config and spawn the sidecar. Starts the monitor task. /// Safe to call again after stop — creates a fresh process. - pub fn start(&self, cfg: &SidecarConfig) -> Result<()> { + pub fn start(&self, cfg: &SidecarConfig, app: tauri::AppHandle) -> Result<()> { let inner = &self.inner; // Abort any previous stop state. inner.stop_requested.store(false, Ordering::SeqCst); + *inner.app_handle.lock().unwrap() = Some(app.clone()); // Locate the Go binary. let binary = find_binary(&cfg.data_dir)?; @@ -206,6 +219,7 @@ impl SidecarManager { // Spawn the process. self.spawn_process(&binary, &config_path)?; + self.start_stdout_reader(app); // Start monitor task (async). let manager = self.clone(); @@ -337,6 +351,7 @@ impl SidecarManager { fn terminate_child(&self) { let mut guard = self.inner.child.lock().unwrap(); + self.inner.child_stdin.lock().unwrap().take(); if let Some(mut child) = guard.take() { #[cfg(unix)] { @@ -421,6 +436,8 @@ impl SidecarManager { if let Err(e) = self.spawn_process(binary, config_path) { error!("[sidecar] Restart failed: {}", e); + } else if let Some(app) = self.inner.app_handle.lock().unwrap().clone() { + self.start_stdout_reader(app); } } } @@ -453,21 +470,29 @@ fn find_binary(data_dir: &PathBuf) -> Result { if p.is_file() { return Ok(p); } - warn!("[sidecar] BETTERDESK_AGENT_BIN set but file not found: {}", p.display()); + warn!( + "[sidecar] BETTERDESK_AGENT_BIN set but file not found: {}", + p.display() + ); } - // 2. Same directory as the Tauri executable. + // 2. Same directory as the Tauri executable. Packaged Tauri externalBin + // files may include the target triple, while dev installs often use the + // plain name. if let Ok(exe) = std::env::current_exe() { - let candidate = exe.parent().unwrap_or(&exe).join(bin_name); - if candidate.is_file() { - return Ok(candidate); + let exe_dir = exe.parent().unwrap_or(&exe); + for candidate in binary_candidates(exe_dir, bin_name) { + if candidate.is_file() { + return Ok(candidate); + } } } // 3. App data directory (downloaded/extracted binary). - let candidate = data_dir.join(bin_name); - if candidate.is_file() { - return Ok(candidate); + for candidate in binary_candidates(data_dir, bin_name) { + if candidate.is_file() { + return Ok(candidate); + } } // 4. System PATH. @@ -504,6 +529,35 @@ fn find_binary(data_dir: &PathBuf) -> Result { )) } +fn binary_candidates(dir: &std::path::Path, bin_name: &str) -> Vec { + let mut out = vec![dir.join(bin_name)]; + + let prefix = if cfg!(windows) { + "betterdesk-agent-" + } else { + "betterdesk-agent-" + }; + + if let Ok(entries) = std::fs::read_dir(dir) { + for entry in entries.flatten() { + let path = entry.path(); + let Some(name) = path.file_name().and_then(|n| n.to_str()) else { + continue; + }; + let matches = if cfg!(windows) { + name.starts_with(prefix) && name.ends_with(".exe") + } else { + name.starts_with(prefix) + }; + if matches { + out.push(path); + } + } + } + + out +} + // ── Config writer ────────────────────────────────────────────────────────── /// Write the Go-format JSON config file consumed by betterdesk-agent. @@ -543,6 +597,7 @@ fn write_go_config(path: &PathBuf, cfg: &SidecarConfig) -> Result<()> { file_browser: cfg.allow_file_browser, clipboard: cfg.allow_clipboard, screenshot: cfg.allow_screen_capture, + require_consent: cfg.require_consent, heartbeat_sec: 15, reconnect_sec: 5, max_reconnect: 300, diff --git a/betterdesk-agent-client/src-tauri/tauri.conf.json b/betterdesk-agent-client/src-tauri/tauri.conf.json index 268469d1..1b82e337 100644 --- a/betterdesk-agent-client/src-tauri/tauri.conf.json +++ b/betterdesk-agent-client/src-tauri/tauri.conf.json @@ -18,8 +18,11 @@ "width": 480, "height": 520, "minWidth": 400, - "minHeight": 460, + "minHeight": 520, "resizable": true, + "minimizable": true, + "maximizable": true, + "closable": true, "fullscreen": false, "center": true, "decorations": true, diff --git a/betterdesk-agent-client/src/App.tsx b/betterdesk-agent-client/src/App.tsx index 12037728..caf10a97 100644 --- a/betterdesk-agent-client/src/App.tsx +++ b/betterdesk-agent-client/src/App.tsx @@ -71,9 +71,18 @@ const BottomNav: Component = (props) => { const isActive = (path: string) => path === "/" ? currentPath() === "/" : currentPath().startsWith(path); - // Close overflow menu on outside click + // Close overflow menu on outside click. Solid delegates click handlers at the + // document level, so the native listener must explicitly ignore clicks inside + // this overflow area instead of relying on stopPropagation timing. onMount(() => { - const handler = () => setShowMenu(false); + const handler = (event: MouseEvent) => { + const target = event.target as Element | null; + if (target?.closest(".bottom-nav-overflow")) { + return; + } + + setShowMenu(false); + }; const syncPath = () => setCurrentPath(getCurrentHashPath()); document.addEventListener("click", handler, { capture: true }); @@ -95,7 +104,7 @@ const BottomNav: Component = (props) => { {mainTabs.map((tab) => ( -
+ )} -
- {!sidecar()?.running ? ( - - ) : ( - <> + {t("status.sidecar_managed_hint")}
} + > +
+ {!sidecar()?.running ? ( - - - )} -
+ ) : ( + <> + + + + )} +
+
{sidecarError()}
diff --git a/betterdesk-agent-client/src/locales/en.json b/betterdesk-agent-client/src/locales/en.json index 559a8a65..3c1fb60e 100644 --- a/betterdesk-agent-client/src/locales/en.json +++ b/betterdesk-agent-client/src/locales/en.json @@ -58,7 +58,8 @@ "sidecar_restarts_hint": "Number of automatic restarts", "sidecar_start": "Start", "sidecar_stop": "Stop", - "sidecar_restart": "Restart" + "sidecar_restart": "Restart", + "sidecar_managed_hint": "CDAP runs in the background and can only be controlled by an administrator." }, "setup": { "title": "Server Setup", diff --git a/betterdesk-agent-client/src/locales/pl.json b/betterdesk-agent-client/src/locales/pl.json index 23cf4d73..0731fea5 100644 --- a/betterdesk-agent-client/src/locales/pl.json +++ b/betterdesk-agent-client/src/locales/pl.json @@ -57,6 +57,7 @@ "sidecar_start": "Uruchom", "sidecar_stop": "Zatrzymaj", "sidecar_restart": "Restartuj", + "sidecar_managed_hint": "CDAP działa w tle i może być kontrolowany tylko przez administratora.", "diagnostics_sent": "Diagnostyka wysłana na serwer", "diagnostics_error": "Nie udało się wysłać diagnostyki" }, diff --git a/betterdesk-agent-client/src/locales/zh-TW.json b/betterdesk-agent-client/src/locales/zh-TW.json index 11d5c4bc..b6ab6770 100644 --- a/betterdesk-agent-client/src/locales/zh-TW.json +++ b/betterdesk-agent-client/src/locales/zh-TW.json @@ -41,7 +41,16 @@ "reconnect": "重新連線", "send_diagnostics": "傳送診斷資訊", "diagnostics_sent": "診斷資訊已傳送至伺服器", - "diagnostics_error": "傳送診斷資訊失敗" + "diagnostics_error": "傳送診斷資訊失敗", + "sidecar_title": "CDAP 代理", + "sidecar_running": "執行中", + "sidecar_stopped": "已停止", + "sidecar_not_configured": "未設定", + "sidecar_restarts_hint": "自動重新啟動次數", + "sidecar_start": "啟動", + "sidecar_stop": "停止", + "sidecar_restart": "重新啟動", + "sidecar_managed_hint": "CDAP 在背景執行,只有管理員可以控制。" }, "setup": { "title": "伺服器設定", diff --git a/betterdesk-agent-client/src/styles/global.css b/betterdesk-agent-client/src/styles/global.css index 30c38a4f..508dfb12 100644 --- a/betterdesk-agent-client/src/styles/global.css +++ b/betterdesk-agent-client/src/styles/global.css @@ -35,11 +35,18 @@ box-sizing: border-box; } +html, +body, +#root { + width: 100%; + height: 100%; +} + body { font-family: "Inter", -apple-system, BlinkMacSystemFont, sans-serif; background: var(--bg-primary); color: var(--text-primary); - font-size: 14px; + font-size: clamp(14px, 0.92rem, 16px); line-height: 1.5; overflow: hidden; user-select: none; @@ -110,7 +117,8 @@ a:hover { color: var(--accent-hover); } .app-layout-tray { display: flex; flex-direction: column; - height: 100%; + height: 100dvh; + min-height: 0; } .app-layout-tray .app-main-full { @@ -125,8 +133,15 @@ a:hover { color: var(--accent-hover); } justify-content: space-around; background: var(--bg-secondary); border-top: 1px solid var(--border); - padding: 0.35rem 0; + min-height: clamp(4.25rem, 12vh, 5.25rem); + padding: 0.35rem 0.4rem max(0.45rem, env(safe-area-inset-bottom)); flex-shrink: 0; + overflow-x: auto; + scrollbar-width: none; +} + +.bottom-nav::-webkit-scrollbar { + display: none; } .bottom-nav-item { @@ -134,7 +149,8 @@ a:hover { color: var(--accent-hover); } flex-direction: column; align-items: center; gap: 0.18rem; - padding: 0.45rem 0.75rem; + flex: 1 1 0; + padding: 0.45rem 0.5rem; border: none; background: none; color: var(--text-muted); @@ -142,8 +158,8 @@ a:hover { color: var(--accent-hover); } border-radius: var(--radius); transition: color var(--transition), background var(--transition); font-family: inherit; - min-width: 4rem; - min-height: 3.5rem; + min-width: 0; + min-height: clamp(3.5rem, 9vh, 4.25rem); } .bottom-nav-item:hover { color: var(--text-secondary); @@ -161,9 +177,12 @@ a:hover { color: var(--accent-hover); } } .bottom-nav-label { - font-size: 0.72rem; + font-size: clamp(0.72rem, 2.7vw, 0.82rem); font-weight: 500; - line-height: 1.2; + line-height: 1.15; + max-width: 100%; + overflow-wrap: anywhere; + text-align: center; } .app-main { @@ -176,7 +195,7 @@ a:hover { color: var(--accent-hover); } width: 100%; height: 100%; overflow-y: auto; - padding: 20px; + padding: clamp(14px, 4vw, 20px); } /* ── Admin-required gate (legacy) ── */ @@ -260,19 +279,25 @@ a:hover { color: var(--accent-hover); } /* ── Overflow / "more" menu ── */ .bottom-nav-overflow { position: relative; + flex: 1 1 0; + min-width: 0; +} + +.bottom-nav-overflow > .bottom-nav-item { + width: 100%; } .overflow-menu { - position: absolute; - bottom: calc(100% + 6px); - right: 4px; + position: fixed; + right: max(10px, env(safe-area-inset-right)); + bottom: calc(clamp(4.25rem, 12vh, 5.25rem) + max(10px, env(safe-area-inset-bottom))); background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); - min-width: 180px; - padding: 4px; - z-index: 500; + width: min(220px, calc(100vw - 20px)); + padding: 6px; + z-index: 2000; animation: fadeUp 120ms ease; } @@ -322,6 +347,17 @@ a:hover { color: var(--accent-hover); } margin: 4px 8px; } +.sidecar-managed-hint { + margin-top: 0.75rem; + padding: 0.65rem 0.75rem; + border: 1px solid var(--border); + border-radius: var(--radius); + background: rgba(139, 148, 158, 0.08); + color: var(--text-secondary); + font-size: 0.82rem; + line-height: 1.35; +} + /* ── Sidebar ── */ .sidebar { width: var(--sidebar-width); diff --git a/betterdesk-agent/agent/agent.go b/betterdesk-agent/agent/agent.go index 188401e9..1f0cf3d3 100644 --- a/betterdesk-agent/agent/agent.go +++ b/betterdesk-agent/agent/agent.go @@ -339,7 +339,7 @@ func (a *Agent) dispatch(msg *Message) { // ── Desktop (Screenshot + Streaming) ── case "desktop_start": a.handleDesktopStart(msg) - case "desktop_stop": + case "desktop_stop", "desktop_end": a.handleDesktopStop(msg) case "desktop_input": a.handleDesktopInput(msg) diff --git a/betterdesk-agent/agent/input.go b/betterdesk-agent/agent/input.go index 3d29064b..f1a06d16 100644 --- a/betterdesk-agent/agent/input.go +++ b/betterdesk-agent/agent/input.go @@ -41,5 +41,10 @@ func (a *Agent) handleDesktopInput(msg *Message) { if err := injectInput(&evt); err != nil { log.Printf("[input] Injection failed (%s): %v", evt.Type, err) + _ = a.sendMessage("desktop_input_error", map[string]any{ + "session_id": evt.SessionID, + "type": evt.Type, + "message": err.Error(), + }) } } diff --git a/betterdesk-agent/agent/input_linux.go b/betterdesk-agent/agent/input_linux.go index 11c0ae10..2a7ee2f8 100644 --- a/betterdesk-agent/agent/input_linux.go +++ b/betterdesk-agent/agent/input_linux.go @@ -11,17 +11,29 @@ import ( // injectInput injects a keyboard or mouse event on Linux. // // Strategy: -// 1. X11 or XWayland session (DISPLAY set) → use xdotool -// 2. Pure Wayland without XWayland (DISPLAY not set) → use ydotool -// (requires ydotoold daemon to be running) -// -// Most Wayland compositors (GNOME, KDE, sway) run XWayland, so path 1 -// is taken even on Wayland desktops in most real-world setups. +// 1. Wayland session → prefer ydotool, because xdotool only reaches XWayland windows. +// 2. X11 or XWayland fallback → use xdotool. func injectInput(evt *InputEvent) error { + if isWaylandSession() { + if commandExists("ydotool") { + if err := injectInputWayland(evt); err == nil { + return nil + } else if !hasX11Display() { + return err + } + } + if hasX11Display() { + return injectInputX11(evt) + } + return fmt.Errorf("Wayland input injection requires ydotool and a running ydotoold daemon") + } if hasX11Display() { return injectInputX11(evt) } - return injectInputWayland(evt) + if commandExists("ydotool") { + return injectInputWayland(evt) + } + return fmt.Errorf("no supported input backend found (install xdotool for X11 or ydotool for Wayland)") } // ── X11 / XWayland path (xdotool) ──────────────────────────────────────── @@ -40,21 +52,31 @@ func injectInputX11(evt *InputEvent) error { return xdotool("click", fmt.Sprintf("%d", linuxMouseButton(evt.Button))) case "mouse_down": + if err := xdotool("mousemove", "--sync", + fmt.Sprintf("%d", evt.X), fmt.Sprintf("%d", evt.Y)); err != nil { + return err + } return xdotool("mousedown", fmt.Sprintf("%d", linuxMouseButton(evt.Button))) case "mouse_up": + if err := xdotool("mousemove", "--sync", + fmt.Sprintf("%d", evt.X), fmt.Sprintf("%d", evt.Y)); err != nil { + return err + } return xdotool("mouseup", fmt.Sprintf("%d", linuxMouseButton(evt.Button))) case "mouse_scroll": - if evt.DeltaY < 0 { - return xdotool("click", "--repeat", fmt.Sprintf("%d", abs(evt.DeltaY)), "4") - } else if evt.DeltaY > 0 { - return xdotool("click", "--repeat", fmt.Sprintf("%d", abs(evt.DeltaY)), "5") + stepsY := scrollSteps(evt.DeltaY) + stepsX := scrollSteps(evt.DeltaX) + if stepsY < 0 { + return xdotool("click", "--repeat", fmt.Sprintf("%d", abs(stepsY)), "4") + } else if stepsY > 0 { + return xdotool("click", "--repeat", fmt.Sprintf("%d", abs(stepsY)), "5") } - if evt.DeltaX < 0 { - return xdotool("click", "--repeat", fmt.Sprintf("%d", abs(evt.DeltaX)), "6") - } else if evt.DeltaX > 0 { - return xdotool("click", "--repeat", fmt.Sprintf("%d", abs(evt.DeltaX)), "7") + if stepsX < 0 { + return xdotool("click", "--repeat", fmt.Sprintf("%d", abs(stepsX)), "6") + } else if stepsX > 0 { + return xdotool("click", "--repeat", fmt.Sprintf("%d", abs(stepsX)), "7") } return nil @@ -87,6 +109,11 @@ func xdotool(args ...string) error { return exec.Command(path, args...).Run() } +func commandExists(name string) bool { + _, err := exec.LookPath(name) + return err == nil +} + // ── Pure-Wayland path (ydotool) ────────────────────────────────────────── // injectInputWayland uses ydotool for input injection on pure-Wayland sessions. @@ -113,18 +140,28 @@ func injectInputWayland(evt *InputEvent) error { return ydotoolClick(evt.Button) case "mouse_down": + if err := ydotool("mousemove", + "-x", fmt.Sprintf("%d", evt.X), + "-y", fmt.Sprintf("%d", evt.Y)); err != nil { + return err + } return ydotoolMouseDown(evt.Button) case "mouse_up": + if err := ydotool("mousemove", + "-x", fmt.Sprintf("%d", evt.X), + "-y", fmt.Sprintf("%d", evt.Y)); err != nil { + return err + } return ydotoolMouseUp(evt.Button) case "mouse_scroll": // ydotool scroll: positive DeltaY = scroll down - if evt.DeltaY != 0 { + if steps := scrollSteps(evt.DeltaY); steps != 0 { // ydotool scroll button clicks: 4=wheel-up, 5=wheel-down btn := "5" - repeat := evt.DeltaY - if evt.DeltaY < 0 { + repeat := steps + if steps < 0 { btn = "4" repeat = -repeat } @@ -174,6 +211,26 @@ func ydotool(args ...string) error { return exec.Command(path, args...).Run() } +func scrollSteps(delta int) int { + if delta == 0 { + return 0 + } + steps := abs(delta) + if steps > 12 { + steps = (steps + 79) / 80 + } + if steps < 1 { + steps = 1 + } + if steps > 10 { + steps = 10 + } + if delta < 0 { + return -steps + } + return steps +} + // ydotoolClick sends a full button click (down + up) for a CDAP button number. // CDAP: 1=left, 2=right, 3=middle. ydotool uses the same numbering as xdotool. func ydotoolClick(button int) error { diff --git a/betterdesk-server/api/client_api_handlers.go b/betterdesk-server/api/client_api_handlers.go index cf1c7b67..5c972746 100644 --- a/betterdesk-server/api/client_api_handlers.go +++ b/betterdesk-server/api/client_api_handlers.go @@ -61,7 +61,8 @@ func (s *tfaSessionStore) put(secret string, sess *tfaSession) { } // Limit total sessions to prevent memory exhaustion if len(s.sessions) >= 1000 { - return // defer s.mu.Unlock() handles the unlock + s.mu.Unlock() + return } s.sessions[secret] = sess } diff --git a/betterdesk-server/cdap/desktop.go b/betterdesk-server/cdap/desktop.go index 63480508..14533f31 100644 --- a/betterdesk-server/cdap/desktop.go +++ b/betterdesk-server/cdap/desktop.go @@ -300,11 +300,11 @@ func (g *Gateway) EndDesktopSession(ctx context.Context, sessionID, reason strin } data, _ := json.Marshal(endPayload) msg := &Message{ - Type: "desktop_end", + Type: "desktop_stop", Timestamp: time.Now().UTC().Format(time.RFC3339), Payload: data, } - ds.deviceConn.WriteMessage(ctx, msg) + _ = ds.deviceConn.WriteMessage(ctx, msg) endMsg, _ := json.Marshal(map[string]string{ "type": "end", @@ -326,3 +326,50 @@ func (g *Gateway) EndDesktopSession(ctx context.Context, sessionID, reason strin }) } } + +// HandleDesktopInputError forwards input injection failures from the device to +// the browser session so operators get an actionable error instead of silent no-op input. +func (g *Gateway) HandleDesktopInputError(ctx context.Context, _ *DeviceConn, msg *Message) { + var payload struct { + SessionID string `json:"session_id"` + Type string `json:"type"` + Message string `json:"message"` + } + if err := json.Unmarshal(msg.Payload, &payload); err != nil || payload.SessionID == "" { + return + } + + val, ok := g.desktopSessions.Load(payload.SessionID) + if !ok { + return + } + ds := val.(*DesktopSession) + if ds.closed.Load() { + return + } + + text := payload.Message + if text == "" { + text = "remote input injection failed" + } + out, _ := json.Marshal(map[string]string{ + "type": "error", + "session_id": payload.SessionID, + "error": text, + }) + ds.mu.Lock() + _ = ds.browser.Write(ctx, websocket.MessageText, out) + ds.mu.Unlock() +} + +func (g *Gateway) cleanupDeviceSessions(deviceID, reason string) { + g.desktopSessions.Range(func(key, value any) bool { + ds, ok := value.(*DesktopSession) + if !ok || ds.DeviceID != deviceID { + return true + } + g.EndDesktopSession(context.Background(), ds.ID, reason) + g.desktopSessions.Delete(key) + return true + }) +} diff --git a/betterdesk-server/cdap/gateway.go b/betterdesk-server/cdap/gateway.go index 4016e239..90ab586b 100644 --- a/betterdesk-server/cdap/gateway.go +++ b/betterdesk-server/cdap/gateway.go @@ -353,6 +353,8 @@ func (g *Gateway) messageLoop(ctx context.Context, dc *DeviceConn) { g.handleDesktopFrame(ctx, dc, msg) case "desktop_end": g.handleDesktopEnd(ctx, dc, msg) + case "desktop_input_error": + g.HandleDesktopInputError(ctx, dc, msg) case "video_frame": g.handleVideoFrame(ctx, dc, msg) case "video_end": @@ -476,6 +478,8 @@ func (g *Gateway) removeDevice(dc *DeviceConn) { return true }) + g.cleanupDeviceSessions(dc.ID, "device disconnected") + // Update peer status to OFFLINE if err := g.db.UpdatePeerStatus(dc.ID, "OFFLINE", dc.ClientIP); err != nil { log.Printf("[cdap] %s: failed to set offline: %v", dc.ID, err) diff --git a/betterdesk.ps1 b/betterdesk.ps1 index 7e1ad206..2dd78076 100644 --- a/betterdesk.ps1 +++ b/betterdesk.ps1 @@ -1041,6 +1041,17 @@ function Install-NodeJsConsole { Pop-Location return $false } + + # Best-effort install of node-pty for Server Management terminal (BETA). + # Optional native module — falls back to pipe spawn if build fails. + Print-Step "Installing optional node-pty (Server Management terminal - BETA)..." + $ptyOutput = npm install --no-audit --no-fund --no-save node-pty 2>&1 + if ($LASTEXITCODE -eq 0) { + Print-Success "node-pty installed (real PTY available)" + } else { + Print-Warning "node-pty install failed - Server Management terminal will use pipe fallback" + $ptyOutput | Select-Object -Last 5 | ForEach-Object { Write-Host "[node-pty] $_" } + } # Create data directory for databases $dataDir = Join-Path $script:CONSOLE_PATH "data" diff --git a/betterdesk.sh b/betterdesk.sh index 68c81004..00482169 100644 --- a/betterdesk.sh +++ b/betterdesk.sh @@ -1467,6 +1467,23 @@ install_nodejs_console() { fi rm -f "$npm_log" echo "" + + # Best-effort install of node-pty for Server Management terminal (BETA). + # node-pty is an optional dependency: if the native build fails the + # console falls back to plain pipe spawn (no PTY). + print_step "Installing optional node-pty (Server Management terminal — BETA)..." + if npm install --no-audit --no-fund --no-save node-pty >>"$npm_log" 2>&1; then + print_success "node-pty installed (real PTY available)" + else + print_warn "node-pty install failed — Server Management terminal will use pipe fallback" + fi + rm -f "$npm_log" + + # Server Management Terminal sudo hint (BETA — manual step, NOT automated): + # echo 'betterdesk-console ALL=(ALL) NOPASSWD: /usr/bin/systemctl, /usr/bin/journalctl' \ + # | sudo tee /etc/sudoers.d/betterdesk-console + # The installer never modifies sudoers; admins opt in manually. + echo "" # Create data directory for databases mkdir -p "$CONSOLE_PATH/data" diff --git a/docs/AGENT_CLIENT_FINALIZATION_PLAN_2026-05-06.md b/docs/AGENT_CLIENT_FINALIZATION_PLAN_2026-05-06.md new file mode 100644 index 00000000..d9422e8b --- /dev/null +++ b/docs/AGENT_CLIENT_FINALIZATION_PLAN_2026-05-06.md @@ -0,0 +1,122 @@ +# BetterDesk Agent Client Finalization Plan — 2026-05-06 + +## Current Runtime Decision + +The selected stable architecture is: + +- Tauri remains the endpoint UI, tray, setup wizard, policy surface, notification layer, and consent broker. +- The bundled Go `betterdesk-agent` is the active CDAP and remote-session engine. +- The native Rust `CdapClient` remains available for lightweight telemetry/future migration work, but it is no longer the preferred runtime for remote desktop parity. + +This path is lower risk for the final agent because `betterdesk-agent/agent/desktop*.go`, `input*.go`, monitor handling, Wayland capture support, consent handling, terminal, file browser, clipboard, heartbeat, and reconnect already exist in the Go agent codebase. + +## Fixes Applied In This Pass + +1. Window close no longer opens a sudo quit prompt. Closing the window hides the agent to tray, while explicit quit still goes through the guarded quit flow. +2. Linux SIGTERM/SIGINT handling was added so system shutdown or restart can terminate the agent cleanly without waiting for sudo UI. +3. CDAP start/stop/restart controls are now administrator-only at the IPC layer. +4. The Status page hides CDAP controls from non-admin users and shows a managed background-service hint instead. +5. The tray menu no longer exposes the CDAP restart action to non-admin users. +6. The registered agent posts a system notification after startup, reporting that the background agent is running and whether CDAP is connected/reconnecting. +7. Bottom navigation scaling was adjusted for high-DPI / high-system-scale displays using stable minimum heights, `100dvh`, safe-area padding, and responsive label sizing. +8. Tauri window controls are explicitly marked minimizable, maximizable, closable, and resizable in `tauri.conf.json`. +9. The managed Go sidecar was wired back into `AgentState` as the active CDAP runtime. +10. Sidecar config now passes `require_consent` into the Go agent JSON config. +11. Sidecar stdout consent reader now restarts after sidecar process recovery, preserving supervised-session prompts after crashes/restarts. +12. Sidecar binary discovery now supports Tauri externalBin target-triple filenames such as `betterdesk-agent-x86_64-unknown-linux-gnu`. +13. Unregister and explicit quit now stop both the Go sidecar and the native Rust CDAP client to avoid stale background connections. + +## Final Agent Requirements + +The final BetterDesk Client Agent must support: + +- Supervised access: user consent prompt before a session if policy requires it. +- Unattended access: policy-controlled connection without user consent when explicitly enabled. +- RDClient feature parity: high-resolution streaming, remote input, clipboard sync, file transfer, monitor selection, quality presets, reconnect, and audit events. +- RustDesk desktop client compatibility where protocol boundaries allow it. +- Linux X11 and Wayland, Windows, macOS, and later Android. +- Hardware-accelerated codecs where available, with safe software fallback. +- Background resilience after reboot, including autostart, CDAP reconnect, heartbeat, sysinfo refresh, and operator-visible status. +- User tamper resistance: regular users must not be able to stop CDAP, unregister the device, or disable critical modules. + +## Recommended Implementation Path + +### Phase A — Stabilize One Real Remote Runtime + +Status: started. The Go sidecar is now the selected runtime for CDAP and remote-session work. + +Remaining work: + +- Validate the packaged sidecar path on Windows, Linux, and macOS installers. +- Make sidecar health and reconnect status visible in the Status page. +- Add structured sidecar logs to diagnostics. +- Confirm no duplicate device sessions are created when heartbeat, bd-signal, and sidecar run together. + +### Phase B — Supervised/Unattended Policy Contract + +Add explicit policy fields from server to agent: + +- `allow_remote_desktop` +- `require_consent` +- `allow_unattended` +- `allowed_operators` +- `session_recording_policy` +- `max_resolution` +- `max_fps` +- `codec_policy` + +The agent must enforce policy locally, not only in the web console. + +### Phase C — Remote Desktop Capability Matrix + +Linux: + +- X11: x11grab or native capture backend, xdotool/enigo input. +- Wayland: xdg-desktop-portal + PipeWire capture, compositor-safe input path via ydotool/ydotoold or portal-backed remote desktop where available. +- GPU: VAAPI / NVENC / AMF detection through ffmpeg capability probing. + +Windows: + +- Capture: DXGI Desktop Duplication or Windows Graphics Capture. +- Input: SendInput. +- GPU: Media Foundation / D3D11 / NVENC / AMF where available. + +macOS: + +- Capture: ScreenCaptureKit on modern macOS, AVFoundation fallback. +- Input: CGEvent with Accessibility permission. +- GPU: VideoToolbox H.264/HEVC. + +Android later: + +- Capture: MediaProjection. +- Input: Accessibility service or enterprise device-owner mode. +- Transport: CDAP-compatible mobile channel with Android lifecycle constraints. + +### Phase D — Protocol Completeness + +The agent must handle and test these CDAP message families: + +- `desktop_start`, `desktop_stop`, `desktop_frame`, `desktop_input` +- `codec_offer`, `codec_answer`, `keyframe_request` +- `quality_report`, `quality_update` +- `monitor_list`, `monitor_select` +- `clipboard_get`, `clipboard_set`, `clipboard_update` +- `file_list`, `file_read`, `file_write`, `file_delete` +- `audio_start`, `audio_frame`, `audio_end` +- `consent_request`, `consent_granted`, `consent_denied` + +### Phase E — Tests Before “Final” Label + +Required acceptance tests: + +1. Linux KDE Wayland: reboot, autostart, CDAP reconnect, notification, supervised session, unattended session, input control. +2. Linux X11: same as above plus xdotool path. +3. Windows 10/11: reboot/autostart, UAC-safe background mode, screen capture, input, tray, notifications. +4. macOS: permissions prompts, screen recording, accessibility, launch agent, notification. +5. Web console and RDClient: same device, same status, same capabilities, no duplicate online records. +6. RustDesk desktop client compatibility: login, address book/ID visibility, relay, connection negotiation, encryption, and failure diagnostics. + +## Immediate Next Code Step + +Finish the sidecar runtime hardening: packaged binary verification, sidecar diagnostics, UI health reporting, and end-to-end supervised/unattended session tests against the web console. Until these pass, the agent should not be described as a final RDClient/RustDesk-compatible remote desktop agent. \ No newline at end of file diff --git a/web-nodejs/lang/ar.json b/web-nodejs/lang/ar.json index 446cd3a8..2971fcda 100644 --- a/web-nodejs/lang/ar.json +++ b/web-nodejs/lang/ar.json @@ -80,7 +80,8 @@ "toggle_theme": "Toggle theme", "sdk_studio": "SDK Studio", "login": "Login", - "permissions": "Permissions" + "permissions": "Permissions", + "server_management": "إدارة الخادم" }, "auth": { "login": "Login", @@ -3007,5 +3008,80 @@ "preview_secondary": "Secondary", "reset": "إعادة تعيين", "done": "تم" + }, + "server_mgmt": { + "title": "إدارة الخادم", + "subtitle": "Cockpit-like control plane for the BetterDesk console host (BETA).", + "beta_tooltip": "Beta feature — under active development.", + "notice_title": "Beta feature", + "notice_body": "These tools run on the host where the BetterDesk console process lives. All operations are audited. Feature scope will expand in future releases.", + "tab_overview": "نظرة عامة", + "tab_terminal": "الطرفية", + "tab_files": "الملفات", + "tab_services": "الخدمات", + "cpu": "CPU", + "memory": "الذاكرة", + "load": "Load average", + "disks": "الأقراص", + "history": "History (last 60 samples)", + "host_info": "Host info", + "cores": "cores", + "uptime": "وقت التشغيل", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "اتصال", + "term_disconnect": "قطع الاتصال", + "term_clear": "Clear", + "term_status": "Status", + "term_connecting": "Connecting…", + "term_connected": "Connected", + "term_disconnected": "Disconnected", + "term_error": "Connection error", + "term_warning": "Commands run as the user that owns the console process. Add a sudoers rule to enable elevation.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "Name", + "files_size": "Size", + "files_perms": "Mode", + "files_mtime": "Modified", + "files_rename": "Rename", + "files_rename_prompt": "New name:", + "files_empty": "Empty directory", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "Service", + "svc_state": "State", + "svc_description": "Description", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "Start", + "svc_stop": "Stop", + "svc_restart": "Restart", + "svc_reload": "Reload", + "svc_enable": "Enable", + "svc_disable": "Disable", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/cs.json b/web-nodejs/lang/cs.json index fe90b45f..e1df393d 100644 --- a/web-nodejs/lang/cs.json +++ b/web-nodejs/lang/cs.json @@ -79,7 +79,8 @@ "toggle_theme": "Toggle theme", "sdk_studio": "SDK Studio", "login": "Login", - "permissions": "Permissions" + "permissions": "Permissions", + "server_management": "Správa serveru" }, "auth": { "login": "Login", @@ -3006,5 +3007,80 @@ "preview_secondary": "Secondary", "reset": "Reset", "done": "Done" + }, + "server_mgmt": { + "title": "Správa serveru", + "subtitle": "Cockpit-like control plane for the BetterDesk console host (BETA).", + "beta_tooltip": "Beta feature — under active development.", + "notice_title": "Beta feature", + "notice_body": "These tools run on the host where the BetterDesk console process lives. All operations are audited. Feature scope will expand in future releases.", + "tab_overview": "Přehled", + "tab_terminal": "Terminál", + "tab_files": "Soubory", + "tab_services": "Služby", + "cpu": "CPU", + "memory": "Paměť", + "load": "Load average", + "disks": "Disky", + "history": "History (last 60 samples)", + "host_info": "Host info", + "cores": "cores", + "uptime": "Doba běhu", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "Připojit", + "term_disconnect": "Odpojit", + "term_clear": "Clear", + "term_status": "Status", + "term_connecting": "Connecting…", + "term_connected": "Connected", + "term_disconnected": "Disconnected", + "term_error": "Connection error", + "term_warning": "Commands run as the user that owns the console process. Add a sudoers rule to enable elevation.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "Name", + "files_size": "Size", + "files_perms": "Mode", + "files_mtime": "Modified", + "files_rename": "Rename", + "files_rename_prompt": "New name:", + "files_empty": "Empty directory", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "Služba", + "svc_state": "Stav", + "svc_description": "Description", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "Spustit", + "svc_stop": "Zastavit", + "svc_restart": "Restartovat", + "svc_reload": "Reload", + "svc_enable": "Enable", + "svc_disable": "Disable", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/da.json b/web-nodejs/lang/da.json index f490750a..997af025 100644 --- a/web-nodejs/lang/da.json +++ b/web-nodejs/lang/da.json @@ -79,7 +79,8 @@ "toggle_theme": "Toggle theme", "sdk_studio": "SDK Studio", "login": "Login", - "permissions": "Permissions" + "permissions": "Permissions", + "server_management": "Serveradministration" }, "auth": { "login": "Login", @@ -3006,5 +3007,80 @@ "preview_secondary": "Secondary", "reset": "Reset", "done": "Done" + }, + "server_mgmt": { + "title": "Serveradministration", + "subtitle": "Cockpit-like control plane for the BetterDesk console host (BETA).", + "beta_tooltip": "Beta feature — under active development.", + "notice_title": "Beta feature", + "notice_body": "These tools run on the host where the BetterDesk console process lives. All operations are audited. Feature scope will expand in future releases.", + "tab_overview": "Oversigt", + "tab_terminal": "Terminal", + "tab_files": "Filer", + "tab_services": "Tjenester", + "cpu": "CPU", + "memory": "Hukommelse", + "load": "Load average", + "disks": "Diske", + "history": "History (last 60 samples)", + "host_info": "Host info", + "cores": "cores", + "uptime": "Oppetid", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "Forbind", + "term_disconnect": "Afbryd", + "term_clear": "Clear", + "term_status": "Status", + "term_connecting": "Connecting…", + "term_connected": "Connected", + "term_disconnected": "Disconnected", + "term_error": "Connection error", + "term_warning": "Commands run as the user that owns the console process. Add a sudoers rule to enable elevation.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "Name", + "files_size": "Size", + "files_perms": "Mode", + "files_mtime": "Modified", + "files_rename": "Rename", + "files_rename_prompt": "New name:", + "files_empty": "Empty directory", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "Service", + "svc_state": "State", + "svc_description": "Description", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "Start", + "svc_stop": "Stop", + "svc_restart": "Genstart", + "svc_reload": "Reload", + "svc_enable": "Enable", + "svc_disable": "Disable", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/de.json b/web-nodejs/lang/de.json index b72ba4dc..f539705d 100644 --- a/web-nodejs/lang/de.json +++ b/web-nodejs/lang/de.json @@ -73,7 +73,8 @@ "help": "Help", "expand_sidebar": "Expand sidebar", "login": "Anmelden", - "permissions": "Permissions" + "permissions": "Permissions", + "server_management": "Server-Verwaltung" }, "auth": { "login": "Anmelden", @@ -3000,5 +3001,80 @@ "preview_secondary": "Secondary", "reset": "Zurücksetzen", "done": "Fertig" + }, + "server_mgmt": { + "title": "Server-Verwaltung", + "subtitle": "Cockpit-ähnliche Steuerung für den BetterDesk-Konsolen-Host (BETA).", + "beta_tooltip": "Beta-Funktion — in aktiver Entwicklung.", + "notice_title": "Beta-Funktion", + "notice_body": "Diese Werkzeuge laufen auf dem Host, auf dem der BetterDesk-Konsolenprozess ausgeführt wird. Alle Operationen werden auditiert.", + "tab_overview": "Übersicht", + "tab_terminal": "Terminal", + "tab_files": "Dateien", + "tab_services": "Dienste", + "cpu": "CPU", + "memory": "Speicher", + "load": "Last (Durchschnitt)", + "disks": "Festplatten", + "history": "Verlauf (letzte 60 Werte)", + "host_info": "Host-Info", + "cores": "Kerne", + "uptime": "Laufzeit", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "Verbinden", + "term_disconnect": "Trennen", + "term_clear": "Löschen", + "term_status": "Status", + "term_connecting": "Verbinden…", + "term_connected": "Verbunden", + "term_disconnected": "Getrennt", + "term_error": "Verbindungsfehler", + "term_warning": "Befehle werden als Konsolen-Prozessbenutzer ausgeführt. Sudoers-Regel hinzufügen für Erhöhung.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "Name", + "files_size": "Größe", + "files_perms": "Modus", + "files_mtime": "Geändert", + "files_rename": "Umbenennen", + "files_rename_prompt": "New name:", + "files_empty": "Leeres Verzeichnis", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "Dienst", + "svc_state": "Status", + "svc_description": "Beschreibung", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "Start", + "svc_stop": "Stopp", + "svc_restart": "Neustart", + "svc_reload": "Neu laden", + "svc_enable": "Aktivieren", + "svc_disable": "Deaktivieren", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/en.json b/web-nodejs/lang/en.json index 70dda4b0..cfc9855d 100644 --- a/web-nodejs/lang/en.json +++ b/web-nodejs/lang/en.json @@ -73,7 +73,8 @@ "chat": "Chat", "help": "Help", "expand_sidebar": "Expand sidebar", - "login": "Login" + "login": "Login", + "server_management": "Server Management" }, "auth": { "login": "Login", @@ -3000,5 +3001,80 @@ "preview_secondary": "Secondary", "reset": "Reset", "done": "Done" + }, + "server_mgmt": { + "title": "Server Management", + "subtitle": "Cockpit-like control plane for the BetterDesk console host (BETA).", + "beta_tooltip": "Beta feature — under active development.", + "notice_title": "Beta feature", + "notice_body": "These tools run on the host where the BetterDesk console process lives. All operations are audited. Feature scope will expand in future releases.", + "tab_overview": "Overview", + "tab_terminal": "Terminal", + "tab_files": "Files", + "tab_services": "Services", + "cpu": "CPU", + "memory": "Memory", + "load": "Load average", + "disks": "Disks", + "history": "History (last 60 samples)", + "host_info": "Host info", + "cores": "cores", + "uptime": "Uptime", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "Connect", + "term_disconnect": "Disconnect", + "term_clear": "Clear", + "term_status": "Status", + "term_connecting": "Connecting…", + "term_connected": "Connected", + "term_disconnected": "Disconnected", + "term_error": "Connection error", + "term_warning": "Commands run as the user that owns the console process. Add a sudoers rule to enable elevation.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "Name", + "files_size": "Size", + "files_perms": "Mode", + "files_mtime": "Modified", + "files_rename": "Rename", + "files_rename_prompt": "New name:", + "files_empty": "Empty directory", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "Service", + "svc_state": "State", + "svc_description": "Description", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "Start", + "svc_stop": "Stop", + "svc_restart": "Restart", + "svc_reload": "Reload", + "svc_enable": "Enable", + "svc_disable": "Disable", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/es.json b/web-nodejs/lang/es.json index 10d1e6de..4bbde7d1 100644 --- a/web-nodejs/lang/es.json +++ b/web-nodejs/lang/es.json @@ -73,7 +73,8 @@ "help": "Help", "expand_sidebar": "Expand sidebar", "login": "Iniciar sesión", - "permissions": "Permissions" + "permissions": "Permissions", + "server_management": "Gestión del servidor" }, "auth": { "login": "Iniciar sesión", @@ -3000,5 +3001,80 @@ "preview_secondary": "Secondary", "reset": "Restablecer", "done": "Listo" + }, + "server_mgmt": { + "title": "Gestión del servidor", + "subtitle": "Panel tipo Cockpit para el host de la consola BetterDesk (BETA).", + "beta_tooltip": "Función beta — en desarrollo activo.", + "notice_title": "Función beta", + "notice_body": "Estas herramientas se ejecutan en el host donde corre el proceso de la consola BetterDesk. Todas las operaciones son auditadas.", + "tab_overview": "Resumen", + "tab_terminal": "Terminal", + "tab_files": "Archivos", + "tab_services": "Servicios", + "cpu": "CPU", + "memory": "Memoria", + "load": "Carga promedio", + "disks": "Discos", + "history": "Historial (últimas 60 muestras)", + "host_info": "Info del host", + "cores": "núcleos", + "uptime": "Tiempo activo", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "Conectar", + "term_disconnect": "Desconectar", + "term_clear": "Limpiar", + "term_status": "Status", + "term_connecting": "Conectando…", + "term_connected": "Conectado", + "term_disconnected": "Desconectado", + "term_error": "Connection error", + "term_warning": "Commands run as the user that owns the console process. Add a sudoers rule to enable elevation.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "Nombre", + "files_size": "Tamaño", + "files_perms": "Mode", + "files_mtime": "Modificado", + "files_rename": "Renombrar", + "files_rename_prompt": "New name:", + "files_empty": "Directorio vacío", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "Servicio", + "svc_state": "Estado", + "svc_description": "Descripción", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "Iniciar", + "svc_stop": "Detener", + "svc_restart": "Reiniciar", + "svc_reload": "Recargar", + "svc_enable": "Habilitar", + "svc_disable": "Deshabilitar", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/fi.json b/web-nodejs/lang/fi.json index 7faab6a6..5c5835ad 100644 --- a/web-nodejs/lang/fi.json +++ b/web-nodejs/lang/fi.json @@ -79,7 +79,8 @@ "toggle_theme": "Toggle theme", "sdk_studio": "SDK Studio", "login": "Login", - "permissions": "Permissions" + "permissions": "Permissions", + "server_management": "Palvelimen hallinta" }, "auth": { "login": "Login", @@ -3006,5 +3007,80 @@ "preview_secondary": "Secondary", "reset": "Reset", "done": "Done" + }, + "server_mgmt": { + "title": "Palvelimen hallinta", + "subtitle": "Cockpit-like control plane for the BetterDesk console host (BETA).", + "beta_tooltip": "Beta feature — under active development.", + "notice_title": "Beta feature", + "notice_body": "These tools run on the host where the BetterDesk console process lives. All operations are audited. Feature scope will expand in future releases.", + "tab_overview": "Yleiskatsaus", + "tab_terminal": "Pääte", + "tab_files": "Tiedostot", + "tab_services": "Palvelut", + "cpu": "CPU", + "memory": "Muisti", + "load": "Load average", + "disks": "Levyt", + "history": "History (last 60 samples)", + "host_info": "Host info", + "cores": "cores", + "uptime": "Käyntiaika", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "Yhdistä", + "term_disconnect": "Katkaise", + "term_clear": "Clear", + "term_status": "Status", + "term_connecting": "Connecting…", + "term_connected": "Connected", + "term_disconnected": "Disconnected", + "term_error": "Connection error", + "term_warning": "Commands run as the user that owns the console process. Add a sudoers rule to enable elevation.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "Name", + "files_size": "Size", + "files_perms": "Mode", + "files_mtime": "Modified", + "files_rename": "Rename", + "files_rename_prompt": "New name:", + "files_empty": "Empty directory", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "Service", + "svc_state": "State", + "svc_description": "Description", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "Start", + "svc_stop": "Stop", + "svc_restart": "Restart", + "svc_reload": "Reload", + "svc_enable": "Enable", + "svc_disable": "Disable", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/fr.json b/web-nodejs/lang/fr.json index bd5984c3..46a5e4ee 100644 --- a/web-nodejs/lang/fr.json +++ b/web-nodejs/lang/fr.json @@ -74,7 +74,8 @@ "help": "Aide", "expand_sidebar": "Développer la barre latérale", "login": "Connexion", - "permissions": "Permissions" + "permissions": "Permissions", + "server_management": "Gestion du serveur" }, "auth": { "login": "Connexion", @@ -3001,5 +3002,80 @@ "preview_secondary": "Secondary", "reset": "Réinitialiser", "done": "Terminé" + }, + "server_mgmt": { + "title": "Gestion du serveur", + "subtitle": "Panneau de type Cockpit pour l'hôte de la console BetterDesk (BÊTA).", + "beta_tooltip": "Fonction bêta — en développement actif.", + "notice_title": "Fonction bêta", + "notice_body": "Ces outils s'exécutent sur l'hôte où tourne le processus de la console BetterDesk. Toutes les opérations sont auditées.", + "tab_overview": "Aperçu", + "tab_terminal": "Terminal", + "tab_files": "Fichiers", + "tab_services": "Services", + "cpu": "CPU", + "memory": "Mémoire", + "load": "Charge moyenne", + "disks": "Disques", + "history": "Historique (60 derniers échantillons)", + "host_info": "Infos hôte", + "cores": "cœurs", + "uptime": "Temps d'activité", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "Connecter", + "term_disconnect": "Déconnecter", + "term_clear": "Effacer", + "term_status": "Status", + "term_connecting": "Connexion…", + "term_connected": "Connecté", + "term_disconnected": "Déconnecté", + "term_error": "Connection error", + "term_warning": "Commands run as the user that owns the console process. Add a sudoers rule to enable elevation.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "Nom", + "files_size": "Taille", + "files_perms": "Mode", + "files_mtime": "Modifié", + "files_rename": "Renommer", + "files_rename_prompt": "New name:", + "files_empty": "Répertoire vide", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "Service", + "svc_state": "État", + "svc_description": "Description", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "Démarrer", + "svc_stop": "Arrêter", + "svc_restart": "Redémarrer", + "svc_reload": "Recharger", + "svc_enable": "Activer", + "svc_disable": "Désactiver", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/hi.json b/web-nodejs/lang/hi.json index 8521325d..2659a20f 100644 --- a/web-nodejs/lang/hi.json +++ b/web-nodejs/lang/hi.json @@ -79,7 +79,8 @@ "toggle_theme": "Toggle theme", "sdk_studio": "SDK Studio", "login": "Login", - "permissions": "Permissions" + "permissions": "Permissions", + "server_management": "सर्वर प्रबंधन" }, "auth": { "login": "Login", @@ -3006,5 +3007,80 @@ "preview_secondary": "Secondary", "reset": "रीसेट", "done": "पूर्ण" + }, + "server_mgmt": { + "title": "सर्वर प्रबंधन", + "subtitle": "Cockpit-like control plane for the BetterDesk console host (BETA).", + "beta_tooltip": "Beta feature — under active development.", + "notice_title": "Beta feature", + "notice_body": "These tools run on the host where the BetterDesk console process lives. All operations are audited. Feature scope will expand in future releases.", + "tab_overview": "अवलोकन", + "tab_terminal": "टर्मिनल", + "tab_files": "फ़ाइलें", + "tab_services": "सेवाएं", + "cpu": "CPU", + "memory": "मेमोरी", + "load": "Load average", + "disks": "डिस्क", + "history": "History (last 60 samples)", + "host_info": "Host info", + "cores": "cores", + "uptime": "अपटाइम", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "Connect", + "term_disconnect": "Disconnect", + "term_clear": "Clear", + "term_status": "Status", + "term_connecting": "Connecting…", + "term_connected": "Connected", + "term_disconnected": "Disconnected", + "term_error": "Connection error", + "term_warning": "Commands run as the user that owns the console process. Add a sudoers rule to enable elevation.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "Name", + "files_size": "Size", + "files_perms": "Mode", + "files_mtime": "Modified", + "files_rename": "Rename", + "files_rename_prompt": "New name:", + "files_empty": "Empty directory", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "Service", + "svc_state": "State", + "svc_description": "Description", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "Start", + "svc_stop": "Stop", + "svc_restart": "Restart", + "svc_reload": "Reload", + "svc_enable": "Enable", + "svc_disable": "Disable", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/hu.json b/web-nodejs/lang/hu.json index 0512063f..5196c2ea 100644 --- a/web-nodejs/lang/hu.json +++ b/web-nodejs/lang/hu.json @@ -79,7 +79,8 @@ "toggle_theme": "Toggle theme", "sdk_studio": "SDK Studio", "login": "Login", - "permissions": "Permissions" + "permissions": "Permissions", + "server_management": "Kiszolgáló-kezelés" }, "auth": { "login": "Login", @@ -3006,5 +3007,80 @@ "preview_secondary": "Secondary", "reset": "Reset", "done": "Done" + }, + "server_mgmt": { + "title": "Kiszolgáló-kezelés", + "subtitle": "Cockpit-like control plane for the BetterDesk console host (BETA).", + "beta_tooltip": "Beta feature — under active development.", + "notice_title": "Beta feature", + "notice_body": "These tools run on the host where the BetterDesk console process lives. All operations are audited. Feature scope will expand in future releases.", + "tab_overview": "Áttekintés", + "tab_terminal": "Terminál", + "tab_files": "Fájlok", + "tab_services": "Szolgáltatások", + "cpu": "CPU", + "memory": "Memória", + "load": "Load average", + "disks": "Lemezek", + "history": "History (last 60 samples)", + "host_info": "Host info", + "cores": "cores", + "uptime": "Üzemidő", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "Csatlakozás", + "term_disconnect": "Lecsatlakozás", + "term_clear": "Clear", + "term_status": "Status", + "term_connecting": "Connecting…", + "term_connected": "Connected", + "term_disconnected": "Disconnected", + "term_error": "Connection error", + "term_warning": "Commands run as the user that owns the console process. Add a sudoers rule to enable elevation.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "Name", + "files_size": "Size", + "files_perms": "Mode", + "files_mtime": "Modified", + "files_rename": "Rename", + "files_rename_prompt": "New name:", + "files_empty": "Empty directory", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "Service", + "svc_state": "State", + "svc_description": "Description", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "Start", + "svc_stop": "Stop", + "svc_restart": "Restart", + "svc_reload": "Reload", + "svc_enable": "Enable", + "svc_disable": "Disable", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/id.json b/web-nodejs/lang/id.json index 1e67e054..01ee0a14 100644 --- a/web-nodejs/lang/id.json +++ b/web-nodejs/lang/id.json @@ -79,7 +79,8 @@ "toggle_theme": "Toggle theme", "sdk_studio": "SDK Studio", "login": "Login", - "permissions": "Permissions" + "permissions": "Permissions", + "server_management": "Manajemen Server" }, "auth": { "login": "Login", @@ -3006,5 +3007,80 @@ "preview_secondary": "Secondary", "reset": "Reset", "done": "Done" + }, + "server_mgmt": { + "title": "Manajemen Server", + "subtitle": "Cockpit-like control plane for the BetterDesk console host (BETA).", + "beta_tooltip": "Beta feature — under active development.", + "notice_title": "Beta feature", + "notice_body": "These tools run on the host where the BetterDesk console process lives. All operations are audited. Feature scope will expand in future releases.", + "tab_overview": "Ikhtisar", + "tab_terminal": "Terminal", + "tab_files": "Berkas", + "tab_services": "Layanan", + "cpu": "CPU", + "memory": "Memori", + "load": "Load average", + "disks": "Disk", + "history": "History (last 60 samples)", + "host_info": "Host info", + "cores": "cores", + "uptime": "Waktu aktif", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "Hubungkan", + "term_disconnect": "Putuskan", + "term_clear": "Clear", + "term_status": "Status", + "term_connecting": "Connecting…", + "term_connected": "Connected", + "term_disconnected": "Disconnected", + "term_error": "Connection error", + "term_warning": "Commands run as the user that owns the console process. Add a sudoers rule to enable elevation.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "Name", + "files_size": "Size", + "files_perms": "Mode", + "files_mtime": "Modified", + "files_rename": "Rename", + "files_rename_prompt": "New name:", + "files_empty": "Empty directory", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "Service", + "svc_state": "State", + "svc_description": "Description", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "Start", + "svc_stop": "Stop", + "svc_restart": "Restart", + "svc_reload": "Reload", + "svc_enable": "Enable", + "svc_disable": "Disable", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/it.json b/web-nodejs/lang/it.json index 8c1351f0..b642bbb4 100644 --- a/web-nodejs/lang/it.json +++ b/web-nodejs/lang/it.json @@ -75,7 +75,8 @@ "toggle_theme": "Cambia tema", "sdk_studio": "SDK Studio", "login": "Accedi", - "permissions": "Permissions" + "permissions": "Permissions", + "server_management": "Gestione server" }, "auth": { "login": "Accedi", @@ -3150,5 +3151,80 @@ "preview_secondary": "Secondary", "reset": "Ripristina", "done": "Fine" + }, + "server_mgmt": { + "title": "Gestione server", + "subtitle": "Pannello in stile Cockpit per l'host della console BetterDesk (BETA).", + "beta_tooltip": "Beta feature — under active development.", + "notice_title": "Beta feature", + "notice_body": "These tools run on the host where the BetterDesk console process lives. All operations are audited. Feature scope will expand in future releases.", + "tab_overview": "Panoramica", + "tab_terminal": "Terminale", + "tab_files": "File", + "tab_services": "Servizi", + "cpu": "CPU", + "memory": "Memoria", + "load": "Carico medio", + "disks": "Dischi", + "history": "History (last 60 samples)", + "host_info": "Host info", + "cores": "cores", + "uptime": "Tempo di attività", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "Connetti", + "term_disconnect": "Disconnetti", + "term_clear": "Pulisci", + "term_status": "Status", + "term_connecting": "Connecting…", + "term_connected": "Connesso", + "term_disconnected": "Disconnesso", + "term_error": "Connection error", + "term_warning": "Commands run as the user that owns the console process. Add a sudoers rule to enable elevation.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "Nome", + "files_size": "Dimensione", + "files_perms": "Mode", + "files_mtime": "Modificato", + "files_rename": "Rinomina", + "files_rename_prompt": "New name:", + "files_empty": "Empty directory", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "Servizio", + "svc_state": "Stato", + "svc_description": "Description", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "Avvia", + "svc_stop": "Ferma", + "svc_restart": "Riavvia", + "svc_reload": "Reload", + "svc_enable": "Abilita", + "svc_disable": "Disabilita", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/ja.json b/web-nodejs/lang/ja.json index bedfeab8..2a087770 100644 --- a/web-nodejs/lang/ja.json +++ b/web-nodejs/lang/ja.json @@ -73,7 +73,8 @@ "toggle_theme": "Toggle theme", "sdk_studio": "SDK Studio", "login": "Login", - "permissions": "Permissions" + "permissions": "Permissions", + "server_management": "サーバー管理" }, "auth": { "login": "Login", @@ -3000,5 +3001,80 @@ "preview_secondary": "Secondary", "reset": "リセット", "done": "完了" + }, + "server_mgmt": { + "title": "サーバー管理", + "subtitle": "BetterDesk コンソールホスト用の Cockpit 風コントロールパネル(ベータ)。", + "beta_tooltip": "Beta feature — under active development.", + "notice_title": "Beta feature", + "notice_body": "These tools run on the host where the BetterDesk console process lives. All operations are audited. Feature scope will expand in future releases.", + "tab_overview": "概要", + "tab_terminal": "ターミナル", + "tab_files": "ファイル", + "tab_services": "サービス", + "cpu": "CPU", + "memory": "メモリ", + "load": "Load average", + "disks": "ディスク", + "history": "History (last 60 samples)", + "host_info": "Host info", + "cores": "cores", + "uptime": "稼働時間", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "接続", + "term_disconnect": "切断", + "term_clear": "Clear", + "term_status": "Status", + "term_connecting": "Connecting…", + "term_connected": "Connected", + "term_disconnected": "Disconnected", + "term_error": "Connection error", + "term_warning": "Commands run as the user that owns the console process. Add a sudoers rule to enable elevation.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "名前", + "files_size": "サイズ", + "files_perms": "Mode", + "files_mtime": "Modified", + "files_rename": "Rename", + "files_rename_prompt": "New name:", + "files_empty": "Empty directory", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "サービス", + "svc_state": "状態", + "svc_description": "Description", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "開始", + "svc_stop": "停止", + "svc_restart": "再起動", + "svc_reload": "Reload", + "svc_enable": "Enable", + "svc_disable": "Disable", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/ko.json b/web-nodejs/lang/ko.json index b550c512..2e68ff6a 100644 --- a/web-nodejs/lang/ko.json +++ b/web-nodejs/lang/ko.json @@ -73,7 +73,8 @@ "toggle_theme": "Toggle theme", "sdk_studio": "SDK Studio", "login": "Login", - "permissions": "Permissions" + "permissions": "Permissions", + "server_management": "서버 관리" }, "auth": { "login": "Login", @@ -3000,5 +3001,80 @@ "preview_secondary": "Secondary", "reset": "초기화", "done": "완료" + }, + "server_mgmt": { + "title": "서버 관리", + "subtitle": "BetterDesk 콘솔 호스트용 Cockpit 스타일 제어판(베타).", + "beta_tooltip": "Beta feature — under active development.", + "notice_title": "Beta feature", + "notice_body": "These tools run on the host where the BetterDesk console process lives. All operations are audited. Feature scope will expand in future releases.", + "tab_overview": "개요", + "tab_terminal": "터미널", + "tab_files": "파일", + "tab_services": "서비스", + "cpu": "CPU", + "memory": "메모리", + "load": "Load average", + "disks": "디스크", + "history": "History (last 60 samples)", + "host_info": "Host info", + "cores": "cores", + "uptime": "가동 시간", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "연결", + "term_disconnect": "연결 해제", + "term_clear": "Clear", + "term_status": "Status", + "term_connecting": "Connecting…", + "term_connected": "Connected", + "term_disconnected": "Disconnected", + "term_error": "Connection error", + "term_warning": "Commands run as the user that owns the console process. Add a sudoers rule to enable elevation.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "이름", + "files_size": "크기", + "files_perms": "Mode", + "files_mtime": "Modified", + "files_rename": "Rename", + "files_rename_prompt": "New name:", + "files_empty": "Empty directory", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "서비스", + "svc_state": "상태", + "svc_description": "Description", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "시작", + "svc_stop": "중지", + "svc_restart": "재시작", + "svc_reload": "Reload", + "svc_enable": "Enable", + "svc_disable": "Disable", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/nb.json b/web-nodejs/lang/nb.json index 9cef2420..19042b54 100644 --- a/web-nodejs/lang/nb.json +++ b/web-nodejs/lang/nb.json @@ -79,7 +79,8 @@ "toggle_theme": "Toggle theme", "sdk_studio": "SDK Studio", "login": "Login", - "permissions": "Permissions" + "permissions": "Permissions", + "server_management": "Serveradministrasjon" }, "auth": { "login": "Login", @@ -3006,5 +3007,80 @@ "preview_secondary": "Secondary", "reset": "Reset", "done": "Done" + }, + "server_mgmt": { + "title": "Serveradministrasjon", + "subtitle": "Cockpit-like control plane for the BetterDesk console host (BETA).", + "beta_tooltip": "Beta feature — under active development.", + "notice_title": "Beta feature", + "notice_body": "These tools run on the host where the BetterDesk console process lives. All operations are audited. Feature scope will expand in future releases.", + "tab_overview": "Oversikt", + "tab_terminal": "Terminal", + "tab_files": "Filer", + "tab_services": "Tjenester", + "cpu": "CPU", + "memory": "Minne", + "load": "Load average", + "disks": "Disker", + "history": "History (last 60 samples)", + "host_info": "Host info", + "cores": "cores", + "uptime": "Oppetid", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "Koble til", + "term_disconnect": "Koble fra", + "term_clear": "Clear", + "term_status": "Status", + "term_connecting": "Connecting…", + "term_connected": "Connected", + "term_disconnected": "Disconnected", + "term_error": "Connection error", + "term_warning": "Commands run as the user that owns the console process. Add a sudoers rule to enable elevation.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "Name", + "files_size": "Size", + "files_perms": "Mode", + "files_mtime": "Modified", + "files_rename": "Rename", + "files_rename_prompt": "New name:", + "files_empty": "Empty directory", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "Service", + "svc_state": "State", + "svc_description": "Description", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "Start", + "svc_stop": "Stop", + "svc_restart": "Restart", + "svc_reload": "Reload", + "svc_enable": "Enable", + "svc_disable": "Disable", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/nl.json b/web-nodejs/lang/nl.json index df799776..b20ba893 100644 --- a/web-nodejs/lang/nl.json +++ b/web-nodejs/lang/nl.json @@ -75,7 +75,8 @@ "toggle_theme": "Thema wisselen", "sdk_studio": "SDK Studio", "login": "Inloggen", - "permissions": "Permissions" + "permissions": "Permissions", + "server_management": "Serverbeheer" }, "auth": { "login": "Inloggen", @@ -3143,5 +3144,80 @@ "preview_secondary": "Secondary", "reset": "Resetten", "done": "Gereed" + }, + "server_mgmt": { + "title": "Serverbeheer", + "subtitle": "Cockpit-achtig paneel voor de BetterDesk-consolehost (BÈTA).", + "beta_tooltip": "Beta feature — under active development.", + "notice_title": "Beta feature", + "notice_body": "These tools run on the host where the BetterDesk console process lives. All operations are audited. Feature scope will expand in future releases.", + "tab_overview": "Overzicht", + "tab_terminal": "Terminal", + "tab_files": "Bestanden", + "tab_services": "Services", + "cpu": "CPU", + "memory": "Geheugen", + "load": "Load average", + "disks": "Schijven", + "history": "History (last 60 samples)", + "host_info": "Host info", + "cores": "cores", + "uptime": "Uptime", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "Verbinden", + "term_disconnect": "Verbinding verbreken", + "term_clear": "Clear", + "term_status": "Status", + "term_connecting": "Connecting…", + "term_connected": "Connected", + "term_disconnected": "Disconnected", + "term_error": "Connection error", + "term_warning": "Commands run as the user that owns the console process. Add a sudoers rule to enable elevation.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "Naam", + "files_size": "Grootte", + "files_perms": "Mode", + "files_mtime": "Modified", + "files_rename": "Rename", + "files_rename_prompt": "New name:", + "files_empty": "Empty directory", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "Service", + "svc_state": "Status", + "svc_description": "Description", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "Starten", + "svc_stop": "Stoppen", + "svc_restart": "Herstarten", + "svc_reload": "Reload", + "svc_enable": "Enable", + "svc_disable": "Disable", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/pl.json b/web-nodejs/lang/pl.json index 531cb599..10f9b051 100644 --- a/web-nodejs/lang/pl.json +++ b/web-nodejs/lang/pl.json @@ -73,7 +73,8 @@ "help": "Pomoc", "expand_sidebar": "Rozwiń pasek boczny", "toggle_theme": "Toggle theme", - "login": "Logowanie" + "login": "Logowanie", + "server_management": "Zarządzanie serwerem" }, "auth": { "login": "Zaloguj", @@ -3000,5 +3001,80 @@ "preview_secondary": "Drugorzędna", "reset": "Resetuj", "done": "Gotowe" + }, + "server_mgmt": { + "title": "Zarządzanie serwerem", + "subtitle": "Panel typu Cockpit dla hosta konsoli BetterDesk (BETA).", + "beta_tooltip": "Funkcja beta — w aktywnym rozwoju.", + "notice_title": "Funkcja w wersji beta", + "notice_body": "Te narzędzia działają na hoście, na którym uruchomiony jest proces konsoli BetterDesk. Wszystkie operacje są audytowane. Zakres funkcji będzie rozszerzany w kolejnych wydaniach.", + "tab_overview": "Przegląd", + "tab_terminal": "Terminal", + "tab_files": "Pliki", + "tab_services": "Usługi", + "cpu": "CPU", + "memory": "Pamięć", + "load": "Średnie obciążenie", + "disks": "Dyski", + "history": "Historia (ostatnie 60 próbek)", + "host_info": "Informacje o hoście", + "cores": "rdzeni", + "uptime": "Czas pracy", + "no_disks": "Brak danych o dyskach", + "info_hostname": "Nazwa hosta", + "info_platform": "Platforma", + "info_kernel": "Jądro", + "info_node": "Node.js", + "info_process_id": "ID procesu", + "info_pty_available": "PTY dostępne", + "term_connect": "Połącz", + "term_disconnect": "Rozłącz", + "term_clear": "Wyczyść", + "term_status": "Status", + "term_connecting": "Łączenie…", + "term_connected": "Połączono", + "term_disconnected": "Rozłączono", + "term_error": "Błąd połączenia", + "term_warning": "Polecenia wykonywane są jako użytkownik procesu konsoli. Dodaj regułę sudoers, aby umożliwić eskalację uprawnień.", + "term_hint": "Wskazówka: wpisz `sudo -i`, aby uzyskać uprawnienia root (wymaga konfiguracji sudoers).", + "term_lib_failed": "Nie udało się załadować biblioteki terminala", + "files_up": "Poziom wyżej", + "files_go": "Idź", + "files_mkdir": "Nowy folder", + "files_name": "Nazwa", + "files_size": "Rozmiar", + "files_perms": "Uprawnienia", + "files_mtime": "Zmodyfikowano", + "files_rename": "Zmień nazwę", + "files_rename_prompt": "Nowa nazwa:", + "files_empty": "Pusty katalog", + "files_error": "Nie udało się odczytać katalogu", + "read_failed": "Nie udało się odczytać pliku", + "save_failed": "Nie udało się zapisać pliku", + "rename_failed": "Nie udało się zmienić nazwy", + "delete_failed": "Nie udało się usunąć", + "delete_confirm": "Usunąć tę pozycję?", + "mkdir_prompt": "Nazwa nowego folderu:", + "mkdir_failed": "Nie udało się utworzyć folderu", + "file_saved": "Plik zapisany", + "binary_warn": "Plik binarny — tylko podgląd", + "encoding_utf8": "Tekst UTF-8", + "truncated": "obcięty", + "full": "pełny", + "svc_search_placeholder": "Szukaj usług…", + "svc_name": "Usługa", + "svc_state": "Stan", + "svc_description": "Opis", + "svc_empty": "Nie znaleziono usług", + "svc_load_failed": "Nie udało się załadować usług", + "svc_action_failed": "Akcja nie powiodła się", + "svc_confirm": "Uruchomić", + "svc_start": "Start", + "svc_stop": "Stop", + "svc_restart": "Restart", + "svc_reload": "Przeładuj", + "svc_enable": "Włącz", + "svc_disable": "Wyłącz", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/pt.json b/web-nodejs/lang/pt.json index fc6900df..c6640e8e 100644 --- a/web-nodejs/lang/pt.json +++ b/web-nodejs/lang/pt.json @@ -75,7 +75,8 @@ "toggle_theme": "Alternar tema", "sdk_studio": "SDK Studio", "login": "Entrar", - "permissions": "Permissions" + "permissions": "Permissions", + "server_management": "Gestão do servidor" }, "auth": { "login": "Entrar", @@ -3143,5 +3144,80 @@ "preview_secondary": "Secondary", "reset": "Repor", "done": "Concluído" + }, + "server_mgmt": { + "title": "Gestão do servidor", + "subtitle": "Painel tipo Cockpit para o host da console BetterDesk (BETA).", + "beta_tooltip": "Beta feature — under active development.", + "notice_title": "Beta feature", + "notice_body": "These tools run on the host where the BetterDesk console process lives. All operations are audited. Feature scope will expand in future releases.", + "tab_overview": "Visão geral", + "tab_terminal": "Terminal", + "tab_files": "Arquivos", + "tab_services": "Serviços", + "cpu": "CPU", + "memory": "Memória", + "load": "Load average", + "disks": "Discos", + "history": "History (last 60 samples)", + "host_info": "Host info", + "cores": "cores", + "uptime": "Tempo ativo", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "Conectar", + "term_disconnect": "Desconectar", + "term_clear": "Clear", + "term_status": "Status", + "term_connecting": "Connecting…", + "term_connected": "Connected", + "term_disconnected": "Disconnected", + "term_error": "Connection error", + "term_warning": "Commands run as the user that owns the console process. Add a sudoers rule to enable elevation.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "Nome", + "files_size": "Tamanho", + "files_perms": "Mode", + "files_mtime": "Modificado", + "files_rename": "Rename", + "files_rename_prompt": "New name:", + "files_empty": "Empty directory", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "Serviço", + "svc_state": "Estado", + "svc_description": "Description", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "Iniciar", + "svc_stop": "Parar", + "svc_restart": "Reiniciar", + "svc_reload": "Reload", + "svc_enable": "Enable", + "svc_disable": "Disable", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/ro.json b/web-nodejs/lang/ro.json index 125a3e41..377e2d63 100644 --- a/web-nodejs/lang/ro.json +++ b/web-nodejs/lang/ro.json @@ -79,7 +79,8 @@ "toggle_theme": "Toggle theme", "sdk_studio": "SDK Studio", "login": "Login", - "permissions": "Permissions" + "permissions": "Permissions", + "server_management": "Gestionare server" }, "auth": { "login": "Login", @@ -3006,5 +3007,80 @@ "preview_secondary": "Secondary", "reset": "Reset", "done": "Done" + }, + "server_mgmt": { + "title": "Gestionare server", + "subtitle": "Cockpit-like control plane for the BetterDesk console host (BETA).", + "beta_tooltip": "Beta feature — under active development.", + "notice_title": "Beta feature", + "notice_body": "These tools run on the host where the BetterDesk console process lives. All operations are audited. Feature scope will expand in future releases.", + "tab_overview": "Prezentare", + "tab_terminal": "Terminal", + "tab_files": "Fișiere", + "tab_services": "Servicii", + "cpu": "CPU", + "memory": "Memorie", + "load": "Load average", + "disks": "Discuri", + "history": "History (last 60 samples)", + "host_info": "Host info", + "cores": "cores", + "uptime": "Timp de funcționare", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "Conectează", + "term_disconnect": "Deconectează", + "term_clear": "Clear", + "term_status": "Status", + "term_connecting": "Connecting…", + "term_connected": "Connected", + "term_disconnected": "Disconnected", + "term_error": "Connection error", + "term_warning": "Commands run as the user that owns the console process. Add a sudoers rule to enable elevation.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "Name", + "files_size": "Size", + "files_perms": "Mode", + "files_mtime": "Modified", + "files_rename": "Rename", + "files_rename_prompt": "New name:", + "files_empty": "Empty directory", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "Service", + "svc_state": "State", + "svc_description": "Description", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "Start", + "svc_stop": "Stop", + "svc_restart": "Restart", + "svc_reload": "Reload", + "svc_enable": "Enable", + "svc_disable": "Disable", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/sv.json b/web-nodejs/lang/sv.json index 37f7b498..0cdcd0b1 100644 --- a/web-nodejs/lang/sv.json +++ b/web-nodejs/lang/sv.json @@ -79,7 +79,8 @@ "toggle_theme": "Toggle theme", "sdk_studio": "SDK Studio", "login": "Login", - "permissions": "Permissions" + "permissions": "Permissions", + "server_management": "Serverhantering" }, "auth": { "login": "Login", @@ -3006,5 +3007,80 @@ "preview_secondary": "Secondary", "reset": "Reset", "done": "Done" + }, + "server_mgmt": { + "title": "Serverhantering", + "subtitle": "Cockpit-like control plane for the BetterDesk console host (BETA).", + "beta_tooltip": "Beta feature — under active development.", + "notice_title": "Beta feature", + "notice_body": "These tools run on the host where the BetterDesk console process lives. All operations are audited. Feature scope will expand in future releases.", + "tab_overview": "Översikt", + "tab_terminal": "Terminal", + "tab_files": "Filer", + "tab_services": "Tjänster", + "cpu": "CPU", + "memory": "Minne", + "load": "Load average", + "disks": "Diskar", + "history": "History (last 60 samples)", + "host_info": "Host info", + "cores": "cores", + "uptime": "Drifttid", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "Anslut", + "term_disconnect": "Koppla från", + "term_clear": "Clear", + "term_status": "Status", + "term_connecting": "Connecting…", + "term_connected": "Connected", + "term_disconnected": "Disconnected", + "term_error": "Connection error", + "term_warning": "Commands run as the user that owns the console process. Add a sudoers rule to enable elevation.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "Name", + "files_size": "Size", + "files_perms": "Mode", + "files_mtime": "Modified", + "files_rename": "Rename", + "files_rename_prompt": "New name:", + "files_empty": "Empty directory", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "Service", + "svc_state": "State", + "svc_description": "Description", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "Starta", + "svc_stop": "Stoppa", + "svc_restart": "Starta om", + "svc_reload": "Reload", + "svc_enable": "Enable", + "svc_disable": "Disable", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/th.json b/web-nodejs/lang/th.json index 7b9ce1c2..553dbe97 100644 --- a/web-nodejs/lang/th.json +++ b/web-nodejs/lang/th.json @@ -79,7 +79,8 @@ "toggle_theme": "Toggle theme", "sdk_studio": "SDK Studio", "login": "Login", - "permissions": "Permissions" + "permissions": "Permissions", + "server_management": "จัดการเซิร์ฟเวอร์" }, "auth": { "login": "Login", @@ -3006,5 +3007,80 @@ "preview_secondary": "Secondary", "reset": "Reset", "done": "Done" + }, + "server_mgmt": { + "title": "จัดการเซิร์ฟเวอร์", + "subtitle": "Cockpit-like control plane for the BetterDesk console host (BETA).", + "beta_tooltip": "Beta feature — under active development.", + "notice_title": "Beta feature", + "notice_body": "These tools run on the host where the BetterDesk console process lives. All operations are audited. Feature scope will expand in future releases.", + "tab_overview": "ภาพรวม", + "tab_terminal": "เทอร์มินัล", + "tab_files": "ไฟล์", + "tab_services": "บริการ", + "cpu": "CPU", + "memory": "หน่วยความจำ", + "load": "Load average", + "disks": "ดิสก์", + "history": "History (last 60 samples)", + "host_info": "Host info", + "cores": "cores", + "uptime": "เวลาทำงาน", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "Connect", + "term_disconnect": "Disconnect", + "term_clear": "Clear", + "term_status": "Status", + "term_connecting": "Connecting…", + "term_connected": "Connected", + "term_disconnected": "Disconnected", + "term_error": "Connection error", + "term_warning": "Commands run as the user that owns the console process. Add a sudoers rule to enable elevation.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "Name", + "files_size": "Size", + "files_perms": "Mode", + "files_mtime": "Modified", + "files_rename": "Rename", + "files_rename_prompt": "New name:", + "files_empty": "Empty directory", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "Service", + "svc_state": "State", + "svc_description": "Description", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "Start", + "svc_stop": "Stop", + "svc_restart": "Restart", + "svc_reload": "Reload", + "svc_enable": "Enable", + "svc_disable": "Disable", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/tr.json b/web-nodejs/lang/tr.json index 244cb62b..78524773 100644 --- a/web-nodejs/lang/tr.json +++ b/web-nodejs/lang/tr.json @@ -79,7 +79,8 @@ "toggle_theme": "Toggle theme", "sdk_studio": "SDK Studio", "login": "Login", - "permissions": "Permissions" + "permissions": "Permissions", + "server_management": "Sunucu Yönetimi" }, "auth": { "login": "Login", @@ -3006,5 +3007,80 @@ "preview_secondary": "Secondary", "reset": "Sıfırla", "done": "Bitti" + }, + "server_mgmt": { + "title": "Sunucu Yönetimi", + "subtitle": "Cockpit-like control plane for the BetterDesk console host (BETA).", + "beta_tooltip": "Beta feature — under active development.", + "notice_title": "Beta feature", + "notice_body": "These tools run on the host where the BetterDesk console process lives. All operations are audited. Feature scope will expand in future releases.", + "tab_overview": "Genel Bakış", + "tab_terminal": "Terminal", + "tab_files": "Dosyalar", + "tab_services": "Hizmetler", + "cpu": "CPU", + "memory": "Bellek", + "load": "Load average", + "disks": "Diskler", + "history": "History (last 60 samples)", + "host_info": "Host info", + "cores": "cores", + "uptime": "Çalışma süresi", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "Bağlan", + "term_disconnect": "Bağlantıyı kes", + "term_clear": "Clear", + "term_status": "Status", + "term_connecting": "Connecting…", + "term_connected": "Connected", + "term_disconnected": "Disconnected", + "term_error": "Connection error", + "term_warning": "Commands run as the user that owns the console process. Add a sudoers rule to enable elevation.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "Name", + "files_size": "Size", + "files_perms": "Mode", + "files_mtime": "Modified", + "files_rename": "Rename", + "files_rename_prompt": "New name:", + "files_empty": "Empty directory", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "Service", + "svc_state": "State", + "svc_description": "Description", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "Başlat", + "svc_stop": "Durdur", + "svc_restart": "Yeniden başlat", + "svc_reload": "Reload", + "svc_enable": "Enable", + "svc_disable": "Disable", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/uk.json b/web-nodejs/lang/uk.json index 492ac1eb..16780189 100644 --- a/web-nodejs/lang/uk.json +++ b/web-nodejs/lang/uk.json @@ -79,7 +79,8 @@ "toggle_theme": "Toggle theme", "sdk_studio": "SDK Studio", "login": "Login", - "permissions": "Permissions" + "permissions": "Permissions", + "server_management": "Керування сервером" }, "auth": { "login": "Login", @@ -3006,5 +3007,80 @@ "preview_secondary": "Secondary", "reset": "Скинути", "done": "Готово" + }, + "server_mgmt": { + "title": "Керування сервером", + "subtitle": "Панель типу Cockpit для хоста консолі BetterDesk (БЕТА).", + "beta_tooltip": "Beta feature — under active development.", + "notice_title": "Beta feature", + "notice_body": "These tools run on the host where the BetterDesk console process lives. All operations are audited. Feature scope will expand in future releases.", + "tab_overview": "Огляд", + "tab_terminal": "Термінал", + "tab_files": "Файли", + "tab_services": "Служби", + "cpu": "CPU", + "memory": "Памʼять", + "load": "Load average", + "disks": "Диски", + "history": "History (last 60 samples)", + "host_info": "Host info", + "cores": "cores", + "uptime": "Час роботи", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "Зʼєднати", + "term_disconnect": "Розʼєднати", + "term_clear": "Clear", + "term_status": "Status", + "term_connecting": "Connecting…", + "term_connected": "Connected", + "term_disconnected": "Disconnected", + "term_error": "Connection error", + "term_warning": "Commands run as the user that owns the console process. Add a sudoers rule to enable elevation.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "Назва", + "files_size": "Розмір", + "files_perms": "Mode", + "files_mtime": "Modified", + "files_rename": "Rename", + "files_rename_prompt": "New name:", + "files_empty": "Empty directory", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "Служба", + "svc_state": "Стан", + "svc_description": "Description", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "Запустити", + "svc_stop": "Зупинити", + "svc_restart": "Перезапустити", + "svc_reload": "Reload", + "svc_enable": "Enable", + "svc_disable": "Disable", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/vi.json b/web-nodejs/lang/vi.json index d8180582..02978457 100644 --- a/web-nodejs/lang/vi.json +++ b/web-nodejs/lang/vi.json @@ -79,7 +79,8 @@ "toggle_theme": "Toggle theme", "sdk_studio": "SDK Studio", "login": "Login", - "permissions": "Permissions" + "permissions": "Permissions", + "server_management": "Quản lý máy chủ" }, "auth": { "login": "Login", @@ -3006,5 +3007,80 @@ "preview_secondary": "Secondary", "reset": "Reset", "done": "Done" + }, + "server_mgmt": { + "title": "Quản lý máy chủ", + "subtitle": "Cockpit-like control plane for the BetterDesk console host (BETA).", + "beta_tooltip": "Beta feature — under active development.", + "notice_title": "Beta feature", + "notice_body": "These tools run on the host where the BetterDesk console process lives. All operations are audited. Feature scope will expand in future releases.", + "tab_overview": "Tổng quan", + "tab_terminal": "Cửa sổ dòng lệnh", + "tab_files": "Tệp", + "tab_services": "Dịch vụ", + "cpu": "CPU", + "memory": "Bộ nhớ", + "load": "Load average", + "disks": "Ổ đĩa", + "history": "History (last 60 samples)", + "host_info": "Host info", + "cores": "cores", + "uptime": "Thời gian hoạt động", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "Kết nối", + "term_disconnect": "Ngắt kết nối", + "term_clear": "Clear", + "term_status": "Status", + "term_connecting": "Connecting…", + "term_connected": "Connected", + "term_disconnected": "Disconnected", + "term_error": "Connection error", + "term_warning": "Commands run as the user that owns the console process. Add a sudoers rule to enable elevation.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "Name", + "files_size": "Size", + "files_perms": "Mode", + "files_mtime": "Modified", + "files_rename": "Rename", + "files_rename_prompt": "New name:", + "files_empty": "Empty directory", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "Service", + "svc_state": "State", + "svc_description": "Description", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "Start", + "svc_stop": "Stop", + "svc_restart": "Restart", + "svc_reload": "Reload", + "svc_enable": "Enable", + "svc_disable": "Disable", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/zh-TW.json b/web-nodejs/lang/zh-TW.json index 8089a555..4b918ce8 100644 --- a/web-nodejs/lang/zh-TW.json +++ b/web-nodejs/lang/zh-TW.json @@ -73,7 +73,8 @@ "toggle_theme": "切換主題", "sdk_studio": "SDK Studio", "login": "登入", - "permissions": "Permissions" + "permissions": "Permissions", + "server_management": "伺服器管理" }, "auth": { "login": "登入", @@ -3007,5 +3008,80 @@ "preview_secondary": "Secondary", "reset": "重設", "done": "完成" + }, + "server_mgmt": { + "title": "伺服器管理", + "subtitle": "類似 Cockpit 的 BetterDesk 主控台主機管理面板(測試版)。", + "beta_tooltip": "Beta feature — under active development.", + "notice_title": "Beta feature", + "notice_body": "These tools run on the host where the BetterDesk console process lives. All operations are audited. Feature scope will expand in future releases.", + "tab_overview": "概覽", + "tab_terminal": "終端機", + "tab_files": "檔案", + "tab_services": "服務", + "cpu": "CPU", + "memory": "記憶體", + "load": "Load average", + "disks": "磁碟", + "history": "History (last 60 samples)", + "host_info": "Host info", + "cores": "cores", + "uptime": "運行時間", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "連線", + "term_disconnect": "中斷連線", + "term_clear": "Clear", + "term_status": "Status", + "term_connecting": "Connecting…", + "term_connected": "Connected", + "term_disconnected": "Disconnected", + "term_error": "Connection error", + "term_warning": "Commands run as the user that owns the console process. Add a sudoers rule to enable elevation.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "名稱", + "files_size": "大小", + "files_perms": "Mode", + "files_mtime": "Modified", + "files_rename": "Rename", + "files_rename_prompt": "New name:", + "files_empty": "Empty directory", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "服務", + "svc_state": "狀態", + "svc_description": "Description", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "啟動", + "svc_stop": "停止", + "svc_restart": "重新啟動", + "svc_reload": "Reload", + "svc_enable": "Enable", + "svc_disable": "Disable", + "svc_status": "Status" } } diff --git a/web-nodejs/lang/zh.json b/web-nodejs/lang/zh.json index 580da52b..d4675738 100644 --- a/web-nodejs/lang/zh.json +++ b/web-nodejs/lang/zh.json @@ -73,7 +73,8 @@ "expand_sidebar": "展开侧边栏", "toggle_theme": "切换主题", "sdk_studio": "SDK Studio", - "login": "登录" + "login": "登录", + "server_management": "服务器管理" }, "auth": { "login": "登录", @@ -3007,5 +3008,80 @@ "preview_secondary": "Secondary", "reset": "重置", "done": "完成" + }, + "server_mgmt": { + "title": "服务器管理", + "subtitle": "类似 Cockpit 的 BetterDesk 控制台主机管理面板(测试版)。", + "beta_tooltip": "测试版功能 — 正在积极开发中。", + "notice_title": "测试版功能", + "notice_body": "这些工具在运行 BetterDesk 控制台进程的主机上执行。所有操作均被审计。", + "tab_overview": "概览", + "tab_terminal": "终端", + "tab_files": "文件", + "tab_services": "服务", + "cpu": "CPU", + "memory": "内存", + "load": "负载平均值", + "disks": "磁盘", + "history": "历史记录(最近 60 个样本)", + "host_info": "主机信息", + "cores": "核心", + "uptime": "运行时间", + "no_disks": "No disk data available", + "info_hostname": "Hostname", + "info_platform": "Platform", + "info_kernel": "Kernel", + "info_node": "Node.js", + "info_process_id": "Process ID", + "info_pty_available": "PTY available", + "term_connect": "连接", + "term_disconnect": "断开", + "term_clear": "清除", + "term_status": "Status", + "term_connecting": "连接中…", + "term_connected": "已连接", + "term_disconnected": "已断开", + "term_error": "Connection error", + "term_warning": "Commands run as the user that owns the console process. Add a sudoers rule to enable elevation.", + "term_hint": "Tip: type `sudo -i` to escalate (requires sudoers configuration).", + "term_lib_failed": "Failed to load terminal library", + "files_up": "Up one level", + "files_go": "Go", + "files_mkdir": "New folder", + "files_name": "名称", + "files_size": "大小", + "files_perms": "权限", + "files_mtime": "修改时间", + "files_rename": "重命名", + "files_rename_prompt": "New name:", + "files_empty": "空目录", + "files_error": "Failed to list directory", + "read_failed": "Failed to read file", + "save_failed": "Failed to save file", + "rename_failed": "Rename failed", + "delete_failed": "Delete failed", + "delete_confirm": "Delete this entry?", + "mkdir_prompt": "New folder name:", + "mkdir_failed": "Failed to create folder", + "file_saved": "File saved", + "binary_warn": "Binary file — preview only", + "encoding_utf8": "UTF-8 text", + "truncated": "truncated", + "full": "full", + "svc_search_placeholder": "Search services…", + "svc_name": "服务", + "svc_state": "状态", + "svc_description": "说明", + "svc_empty": "No services found", + "svc_load_failed": "Failed to load services", + "svc_action_failed": "Action failed", + "svc_confirm": "Run", + "svc_start": "启动", + "svc_stop": "停止", + "svc_restart": "重启", + "svc_reload": "重新加载", + "svc_enable": "启用", + "svc_disable": "禁用", + "svc_status": "Status" } } diff --git a/web-nodejs/middleware/security.js b/web-nodejs/middleware/security.js index f80e1365..18ade1cb 100644 --- a/web-nodejs/middleware/security.js +++ b/web-nodejs/middleware/security.js @@ -12,8 +12,8 @@ const config = require('../config/config'); * Allow WebSocket connections (ws:// or wss:// depending on mode) */ const connectSources = config.httpsEnabled - ? ["'self'", "wss:"] - : ["'self'", "ws:"]; + ? ["'self'", "wss:", "https://cdn.jsdelivr.net"] + : ["'self'", "ws:", "https://cdn.jsdelivr.net"]; function buildHelmetMiddleware(req, res) { const nonce = crypto.randomBytes(16).toString('base64'); @@ -21,7 +21,7 @@ function buildHelmetMiddleware(req, res) { res.locals.cspNonce = nonce; - const scriptSources = ["'self'", `'nonce-${nonce}'`]; + const scriptSources = ["'self'", `'nonce-${nonce}'`, "https://cdn.jsdelivr.net"]; if (isRemoteViewerPage) { // The remote viewer still depends on protobuf.js runtime code generation. scriptSources.push("'unsafe-eval'"); @@ -35,7 +35,7 @@ function buildHelmetMiddleware(req, res) { // Allow inline event handlers (onclick=, onchange=, etc.) used by // several admin panel pages.