From d3fa8c4420551dc8e002a40fd8034897894069da Mon Sep 17 00:00:00 2001 From: LukeGus Date: Wed, 20 May 2026 13:13:56 -0500 Subject: [PATCH] feat: host manager bug fixes, i18n improvements, general tab fixes --- deploy/guacd.conf | 3 - deploy/guacd.service | 12 - deploy/nginx.conf | 568 ------ deploy/termix.env | 4 - deploy/termix.service | 16 - package-lock.json | 188 +- package.json | 2 +- src/backend/database/routes/host.ts | 12 +- src/backend/guacamole/routes.ts | 100 +- src/backend/ssh/file-manager.ts | 129 +- src/backend/ssh/terminal.ts | 63 +- src/types/ui-types.ts | 4 +- src/ui/AppShell.tsx | 43 +- src/ui/components/input.tsx | 2 + src/ui/features/file-manager/FileManager.tsx | 16 +- src/ui/locales/en.json | 1724 +----------------- src/ui/sidebar/AppRail.tsx | 57 +- src/ui/sidebar/HostManager.tsx | 794 ++++++-- 18 files changed, 998 insertions(+), 2739 deletions(-) delete mode 100644 deploy/guacd.conf delete mode 100644 deploy/guacd.service delete mode 100644 deploy/nginx.conf delete mode 100644 deploy/termix.env delete mode 100644 deploy/termix.service diff --git a/deploy/guacd.conf b/deploy/guacd.conf deleted file mode 100644 index 94bb0be0..00000000 --- a/deploy/guacd.conf +++ /dev/null @@ -1,3 +0,0 @@ -[server] -bind_host = 127.0.0.1 -bind_port = 4822 diff --git a/deploy/guacd.service b/deploy/guacd.service deleted file mode 100644 index b144e030..00000000 --- a/deploy/guacd.service +++ /dev/null @@ -1,12 +0,0 @@ -[Unit] -Description=Guacamole Proxy Daemon (guacd) -After=network.target - -[Service] -Type=simple -ExecStart=/usr/local/sbin/guacd -f -b 127.0.0.1 -l 4822 -Restart=on-failure -RestartSec=5 - -[Install] -WantedBy=multi-user.target diff --git a/deploy/nginx.conf b/deploy/nginx.conf deleted file mode 100644 index d950f61a..00000000 --- a/deploy/nginx.conf +++ /dev/null @@ -1,568 +0,0 @@ -worker_processes auto; -pid /run/nginx.pid; -error_log /var/log/nginx/error.log warn; - -events { - worker_connections 1024; -} - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - access_log /var/log/nginx/access.log; - - sendfile on; - keepalive_timeout 65; - client_header_timeout 300s; - - set_real_ip_from 127.0.0.1; - real_ip_header X-Forwarded-For; - - map $http_x_forwarded_proto $proxy_x_forwarded_proto { - default $http_x_forwarded_proto; - '' $scheme; - } - - map $http_x_forwarded_host $proxy_x_forwarded_host { - default $http_x_forwarded_host; - '' $http_host; - } - - map $http_x_forwarded_port $proxy_x_forwarded_port { - default $http_x_forwarded_port; - '' ''; - } - - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384; - ssl_prefer_server_ciphers off; - ssl_session_cache shared:SSL:10m; - ssl_session_timeout 10m; - - server { - listen 80; - server_name localhost; - - add_header X-Content-Type-Options nosniff always; - add_header X-XSS-Protection "1; mode=block" always; - - location = /sw.js { - root /opt/termix/html; - expires off; - add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always; - try_files $uri =404; - } - - location = /manifest.json { - root /opt/termix/html; - expires off; - add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always; - try_files $uri =404; - } - - location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { - root /opt/termix/html; - expires 1y; - add_header Cache-Control "public, max-age=31536000, immutable" always; - try_files $uri =404; - } - - location / { - root /opt/termix/html; - index index.html index.htm; - expires off; - add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always; - try_files $uri $uri/ /index.html; - } - - location ~* \.map$ { - return 404; - access_log off; - log_not_found off; - } - - location ~ ^/users/sessions(/.*)?$ { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; - } - - location ~ ^/users(/.*)?$ { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; - proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port; - proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host; - } - - location ~ ^/version(/.*)?$ { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location ~ ^/releases(/.*)?$ { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location ~ ^/alerts(/.*)?$ { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location ~ ^/rbac(/.*)?$ { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location ~ ^/credentials(/.*)?$ { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - proxy_connect_timeout 60s; - proxy_send_timeout 300s; - proxy_read_timeout 300s; - } - - location ~ ^/snippets(/.*)?$ { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location ~ ^/c2s-tunnel-presets(/.*)?$ { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location ~ ^/terminal(/.*)?$ { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location ~ ^/database(/.*)?$ { - client_max_body_size 5G; - client_body_timeout 300s; - - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - proxy_connect_timeout 60s; - proxy_send_timeout 300s; - proxy_read_timeout 300s; - - proxy_request_buffering off; - proxy_buffering off; - } - - location ~ ^/db(/.*)?$ { - client_max_body_size 5G; - client_body_timeout 300s; - - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - proxy_connect_timeout 60s; - proxy_send_timeout 300s; - proxy_read_timeout 300s; - - proxy_request_buffering off; - proxy_buffering off; - } - - location ~ ^/encryption(/.*)?$ { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location /host/quick-connect { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $http_host; - proxy_cache_bypass $http_upgrade; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location ~ ^/host/opkssh-chooser(/.*)?$ { - proxy_pass http://127.0.0.1:30001/host/opkssh-chooser$1$is_args$args; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host; - proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; - - proxy_cache_bypass 1; - proxy_no_cache 1; - add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0"; - } - - location ~ ^/host/opkssh-callback(/.*)?$ { - proxy_pass http://127.0.0.1:30001/host/opkssh-callback$1$is_args$args; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host; - proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; - - proxy_cache_bypass 1; - proxy_no_cache 1; - add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0"; - } - - location /host/ { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location /ssh/websocket/ { - proxy_pass http://127.0.0.1:30002/; - proxy_http_version 1.1; - - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $http_host; - proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host; - proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port; - proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; - proxy_cache_bypass $http_upgrade; - - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - - proxy_read_timeout 86400s; - proxy_send_timeout 86400s; - proxy_connect_timeout 10s; - - proxy_buffering off; - proxy_request_buffering off; - - proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; - } - - location ^~ /guacamole/websocket/ { - proxy_pass http://127.0.0.1:30008/; - proxy_http_version 1.1; - - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $http_host; - proxy_cache_bypass $http_upgrade; - - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Port $server_port; - proxy_set_header X-Forwarded-Host $http_host; - - proxy_read_timeout 86400s; - proxy_send_timeout 86400s; - proxy_connect_timeout 10s; - - proxy_buffering off; - proxy_request_buffering off; - - proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; - } - - location ~ ^/guacamole(/.*)?$ { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location /host/tunnel/ { - proxy_pass http://127.0.0.1:30003; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location /ssh/tunnel/ { - proxy_pass http://127.0.0.1:30003; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_read_timeout 86400s; - proxy_send_timeout 86400s; - proxy_buffering off; - proxy_cache off; - } - - location /host/file_manager/recent { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location /host/file_manager/pinned { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location /host/file_manager/shortcuts { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location /host/file_manager/sudo-password { - proxy_pass http://127.0.0.1:30004; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location /ssh/file_manager/ { - client_max_body_size 5G; - client_body_timeout 300s; - - add_header Cache-Control "no-store, no-cache, must-revalidate" always; - - proxy_pass http://127.0.0.1:30004; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - proxy_connect_timeout 60s; - proxy_send_timeout 300s; - proxy_read_timeout 300s; - - proxy_request_buffering off; - proxy_buffering off; - } - - location /host/file_manager/ssh/ { - client_max_body_size 5G; - client_body_timeout 300s; - - add_header Cache-Control "no-store, no-cache, must-revalidate" always; - - proxy_pass http://127.0.0.1:30004; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - proxy_connect_timeout 60s; - proxy_send_timeout 300s; - proxy_read_timeout 300s; - - proxy_request_buffering off; - proxy_buffering off; - } - - location ~ ^/network-topology(/.*)?$ { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location /health { - proxy_pass http://127.0.0.1:30001; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location ~ ^/status(/.*)?$ { - proxy_pass http://127.0.0.1:30005; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location ~ ^/metrics(/.*)?$ { - proxy_pass http://127.0.0.1:30005; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - proxy_connect_timeout 60s; - proxy_send_timeout 60s; - proxy_read_timeout 60s; - } - - location ~ ^/(refresh|host-updated)$ { - proxy_pass http://127.0.0.1:30005; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location ~ ^/global-settings(/.*)?$ { - proxy_pass http://127.0.0.1:30005; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location ~ ^/uptime(/.*)?$ { - proxy_pass http://127.0.0.1:30006; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location ~ ^/activity(/.*)?$ { - proxy_pass http://127.0.0.1:30006; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location ~ ^/dashboard/preferences(/.*)?$ { - proxy_pass http://127.0.0.1:30006; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location ^~ /docker/console/ { - proxy_pass http://127.0.0.1:30009/; - proxy_http_version 1.1; - - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $http_host; - proxy_cache_bypass $http_upgrade; - - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Port $server_port; - proxy_set_header X-Forwarded-Host $http_host; - - proxy_read_timeout 86400s; - proxy_send_timeout 86400s; - proxy_connect_timeout 10s; - - proxy_buffering off; - proxy_request_buffering off; - - proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; - } - - location ~ ^/docker(/.*)?$ { - proxy_pass http://127.0.0.1:30007; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - proxy_connect_timeout 60s; - proxy_send_timeout 300s; - proxy_read_timeout 300s; - } - - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root /opt/termix/html; - } - } -} diff --git a/deploy/termix.env b/deploy/termix.env deleted file mode 100644 index 58ab4c14..00000000 --- a/deploy/termix.env +++ /dev/null @@ -1,4 +0,0 @@ -NODE_ENV=production -DATA_DIR=/opt/termix/data -GUACD_HOST=127.0.0.1 -GUACD_PORT=4822 diff --git a/deploy/termix.service b/deploy/termix.service deleted file mode 100644 index 02ab41f4..00000000 --- a/deploy/termix.service +++ /dev/null @@ -1,16 +0,0 @@ -[Unit] -Description=Termix Backend -After=network.target guacd.service -Wants=guacd.service - -[Service] -Type=simple -User=root -WorkingDirectory=/opt/termix -EnvironmentFile=/opt/termix/.env -ExecStart=/usr/bin/node /opt/termix/dist/backend/backend/starter.js -Restart=on-failure -RestartSec=5 - -[Install] -WantedBy=multi-user.target diff --git a/package-lock.json b/package-lock.json index cac73091..dd31407e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -138,7 +138,7 @@ "tw-animate-css": "^1.4.0", "typescript": "~6.0.3", "typescript-eslint": "^8.59.0", - "vite": "^8.0.10", + "vite": "^8.0.13", "vite-plugin-svgr": "^5.2.0", "zod": "^4.3.6" }, @@ -3735,9 +3735,9 @@ "license": "MIT" }, "node_modules/@oxc-project/types": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", - "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", + "version": "0.130.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.130.0.tgz", + "integrity": "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==", "dev": true, "license": "MIT", "funding": { @@ -5678,9 +5678,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.1.tgz", + "integrity": "sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==", "cpu": [ "arm64" ], @@ -5695,9 +5695,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.1.tgz", + "integrity": "sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==", "cpu": [ "arm64" ], @@ -5712,9 +5712,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.1.tgz", + "integrity": "sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==", "cpu": [ "x64" ], @@ -5729,9 +5729,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.1.tgz", + "integrity": "sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==", "cpu": [ "x64" ], @@ -5746,9 +5746,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", - "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.1.tgz", + "integrity": "sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==", "cpu": [ "arm" ], @@ -5763,13 +5763,16 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.1.tgz", + "integrity": "sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -5780,13 +5783,16 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.1.tgz", + "integrity": "sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -5797,13 +5803,16 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.1.tgz", + "integrity": "sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -5814,13 +5823,16 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.1.tgz", + "integrity": "sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -5831,13 +5843,16 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.1.tgz", + "integrity": "sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -5848,13 +5863,16 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.1.tgz", + "integrity": "sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -5865,9 +5883,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.1.tgz", + "integrity": "sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==", "cpu": [ "arm64" ], @@ -5882,9 +5900,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", - "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.1.tgz", + "integrity": "sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==", "cpu": [ "wasm32" ], @@ -5901,9 +5919,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.1.tgz", + "integrity": "sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==", "cpu": [ "arm64" ], @@ -5918,9 +5936,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.1.tgz", + "integrity": "sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==", "cpu": [ "x64" ], @@ -15321,9 +15339,9 @@ } }, "node_modules/postcss": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", - "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -15341,7 +15359,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -15364,9 +15382,9 @@ } }, "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "dev": true, "funding": [ { @@ -16737,14 +16755,14 @@ "license": "MIT" }, "node_modules/rolldown": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz", - "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.1.tgz", + "integrity": "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.127.0", - "@rolldown/pluginutils": "1.0.0-rc.17" + "@oxc-project/types": "=0.130.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { "rolldown": "bin/cli.mjs" @@ -16753,27 +16771,27 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.17", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", - "@rolldown/binding-darwin-x64": "1.0.0-rc.17", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" + "@rolldown/binding-android-arm64": "1.0.1", + "@rolldown/binding-darwin-arm64": "1.0.1", + "@rolldown/binding-darwin-x64": "1.0.1", + "@rolldown/binding-freebsd-x64": "1.0.1", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.1", + "@rolldown/binding-linux-arm64-gnu": "1.0.1", + "@rolldown/binding-linux-arm64-musl": "1.0.1", + "@rolldown/binding-linux-ppc64-gnu": "1.0.1", + "@rolldown/binding-linux-s390x-gnu": "1.0.1", + "@rolldown/binding-linux-x64-gnu": "1.0.1", + "@rolldown/binding-linux-x64-musl": "1.0.1", + "@rolldown/binding-openharmony-arm64": "1.0.1", + "@rolldown/binding-wasm32-wasi": "1.0.1", + "@rolldown/binding-win32-arm64-msvc": "1.0.1", + "@rolldown/binding-win32-x64-msvc": "1.0.1" } }, "node_modules/rolldown/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", - "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, "license": "MIT" }, @@ -18507,16 +18525,16 @@ } }, "node_modules/vite": { - "version": "8.0.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz", - "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", + "version": "8.0.13", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz", + "integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", - "postcss": "^8.5.10", - "rolldown": "1.0.0-rc.17", + "postcss": "^8.5.14", + "rolldown": "1.0.1", "tinyglobby": "^0.2.16" }, "bin": { @@ -18533,7 +18551,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.0", + "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", diff --git a/package.json b/package.json index 58dce15e..814043e3 100644 --- a/package.json +++ b/package.json @@ -166,7 +166,7 @@ "tw-animate-css": "^1.4.0", "typescript": "~6.0.3", "typescript-eslint": "^8.59.0", - "vite": "^8.0.10", + "vite": "^8.0.13", "vite-plugin-svgr": "^5.2.0", "zod": "^4.3.6" }, diff --git a/src/backend/database/routes/host.ts b/src/backend/database/routes/host.ts index 2ba14a75..2bc23e01 100644 --- a/src/backend/database/routes/host.ts +++ b/src/backend/database/routes/host.ts @@ -671,6 +671,8 @@ router.post( authType || authMethod || (effectiveConnectionType !== "ssh" ? "password" : undefined); + const effectiveUsername = + username || rdpUser || vncUser || telnetUser || ""; const sshDataObj: Record = { userId: userId, connectionType: effectiveConnectionType, @@ -679,7 +681,7 @@ router.post( tags: Array.isArray(tags) ? tags.join(",") : tags || "", ip, port, - username, + username: effectiveUsername, authType: effectiveAuthType, credentialId: credentialId || null, overrideCredentialUsername: overrideCredentialUsername ? 1 : 0, @@ -1203,6 +1205,8 @@ router.put( } const effectiveAuthType = authType || authMethod; + const effectiveUsername = + username || rdpUser || vncUser || telnetUser || ""; const sshDataObj: Record = { connectionType: connectionType || "ssh", name, @@ -1210,7 +1214,7 @@ router.put( tags: Array.isArray(tags) ? tags.join(",") : tags || "", ip, port, - username, + username: effectiveUsername, authType: effectiveAuthType, credentialId: credentialId || null, overrideCredentialUsername: overrideCredentialUsername ? 1 : 0, @@ -3817,6 +3821,10 @@ router.post( overrideCredentialUsername: hostData.overrideCredentialUsername ? 1 : 0, + enableSsh: hostData.enableSsh ?? false, + enableRdp: hostData.enableRdp ?? false, + enableVnc: hostData.enableVnc ?? false, + enableTelnet: hostData.enableTelnet ?? false, updatedAt: new Date().toISOString(), }; diff --git a/src/backend/guacamole/routes.ts b/src/backend/guacamole/routes.ts index 5d4d7e3f..b9841c00 100644 --- a/src/backend/guacamole/routes.ts +++ b/src/backend/guacamole/routes.ts @@ -1,5 +1,4 @@ import express from "express"; -import express from "express"; import { GuacamoleTokenService } from "./token-service.js"; import { guacLogger } from "../utils/logger.js"; import { AuthManager } from "../utils/auth-manager.js"; @@ -197,7 +196,8 @@ router.post( if (guacConfig.dpi != null) { const parsed = parseInt(String(guacConfig.dpi), 10); - guacConfig.dpi = Number.isFinite(parsed) && parsed > 0 ? parsed : undefined; + guacConfig.dpi = + Number.isFinite(parsed) && parsed > 0 ? parsed : undefined; } let token: string; @@ -222,61 +222,57 @@ router.post( if (jumpHosts.length > 0) { try { - const { resolveHostById } = await import( - "../ssh/host-resolver.js" - ); - const jumpHost = await resolveHostById( - jumpHosts[0].hostId, - userId, - ); + const { resolveHostById } = await import("../ssh/host-resolver.js"); + const jumpHost = await resolveHostById(jumpHosts[0].hostId, userId); if (jumpHost) { - const tunnelPort = await new Promise( - (resolve, reject) => { - const sshClient = new Client(); - sshClient.on("ready", () => { - const server = net.createServer((sock) => { - sshClient.forwardOut( - "127.0.0.1", - 0, - hostname, - port, - (err, stream) => { - if (err) { - sock.destroy(); - return; - } - sock.pipe(stream).pipe(sock); - }, - ); - }); - server.listen(0, "127.0.0.1", () => { - const addr = server.address() as net.AddressInfo; - // Auto-cleanup after 1 hour - setTimeout(() => { + const tunnelPort = await new Promise((resolve, reject) => { + const sshClient = new Client(); + sshClient.on("ready", () => { + const server = net.createServer((sock) => { + sshClient.forwardOut( + "127.0.0.1", + 0, + hostname, + port, + (err, stream) => { + if (err) { + sock.destroy(); + return; + } + sock.pipe(stream).pipe(sock); + }, + ); + }); + server.listen(0, "127.0.0.1", () => { + const addr = server.address() as net.AddressInfo; + // Auto-cleanup after 1 hour + setTimeout( + () => { server.close(); sshClient.end(); - }, 60 * 60 * 1000); - resolve(addr.port); - }); + }, + 60 * 60 * 1000, + ); + resolve(addr.port); }); - sshClient.on("error", reject); + }); + sshClient.on("error", reject); - const connectOpts: Record = { - host: jumpHost.ip, - port: jumpHost.port || 22, - username: jumpHost.username, - readyTimeout: 30000, - }; - if (jumpHost.key) { - connectOpts.privateKey = jumpHost.key; - if (jumpHost.keyPassword) - connectOpts.passphrase = jumpHost.keyPassword; - } else if (jumpHost.password) { - connectOpts.password = jumpHost.password; - } - sshClient.connect(connectOpts); - }, - ); + const connectOpts: Record = { + host: jumpHost.ip, + port: jumpHost.port || 22, + username: jumpHost.username, + readyTimeout: 30000, + }; + if (jumpHost.key) { + connectOpts.privateKey = jumpHost.key; + if (jumpHost.keyPassword) + connectOpts.passphrase = jumpHost.keyPassword; + } else if (jumpHost.password) { + connectOpts.password = jumpHost.password; + } + sshClient.connect(connectOpts); + }); hostname = "127.0.0.1"; port = tunnelPort; guacLogger.info("SSH tunnel established for guacamole", { diff --git a/src/backend/ssh/file-manager.ts b/src/backend/ssh/file-manager.ts index 2aff9776..93e02331 100644 --- a/src/backend/ssh/file-manager.ts +++ b/src/backend/ssh/file-manager.ts @@ -151,10 +151,7 @@ async function resolveJumpHost( ): Promise { try { const hostResults = await SimpleDBOps.select( - getDb() - .select() - .from(hosts) - .where(eq(hosts.id, hostId)), + getDb().select().from(hosts).where(eq(hosts.id, hostId)), "ssh_data", userId, ); @@ -172,8 +169,10 @@ async function resolveJumpHost( const { SharedCredentialManager } = await import("../utils/shared-credential-manager.js"); const sharedCredManager = SharedCredentialManager.getInstance(); - const sharedCred = - await sharedCredManager.getSharedCredentialForUser(hostId, userId); + const sharedCred = await sharedCredManager.getSharedCredentialForUser( + hostId, + userId, + ); if (sharedCred) { return { ...host, @@ -889,7 +888,13 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => { ); // Resolve credentials server-side when frontend doesn't provide them - let resolvedCredentials = { password, sshKey, keyPassword, authType, sudoPassword: undefined as string | undefined }; + let resolvedCredentials = { + password, + sshKey, + keyPassword, + authType, + sudoPassword: undefined as string | undefined, + }; if (hostId && userId && !password && !sshKey) { try { const { resolveHostById } = await import("./host-resolver.js"); @@ -3088,47 +3093,36 @@ app.get("/ssh/file_manager/ssh/readFile", (req, res) => { stream.on("close", (code) => { if (code !== 0) { - const isPermissionDenied = - errorData.toLowerCase().includes("permission denied"); + const isPermissionDenied = errorData + .toLowerCase() + .includes("permission denied"); if (isPermissionDenied && sshConn.sudoPassword) { execWithSudo( sshConn, `cat '${escapedPath}'`, sshConn.sudoPassword, - (sudoErr, sudoStream) => { - if (sudoErr) { + ) + .then(({ stdout, stderr, code: sudoCode }) => { + if (sudoCode !== 0) { return res.status(403).json({ - error: "Permission denied", + error: `Permission denied: ${stderr}`, needsSudo: true, }); } - let sudoData = Buffer.alloc(0); - let sudoErrorData = ""; - sudoStream.on("data", (chunk: Buffer) => { - sudoData = Buffer.concat([sudoData, chunk]); + const sudoData = Buffer.from(stdout, "utf8"); + const isBinary = detectBinary(sudoData); + res.json({ + content: isBinary ? sudoData.toString("base64") : stdout, + isBinary, + size: sudoData.length, }); - sudoStream.stderr.on("data", (chunk: Buffer) => { - sudoErrorData += chunk.toString(); - }); - sudoStream.on("close", (sudoCode) => { - if (sudoCode !== 0) { - return res.status(403).json({ - error: `Permission denied: ${sudoErrorData}`, - needsSudo: true, - }); - } - const isBinary = detectBinary(sudoData); - res.json({ - content: isBinary - ? sudoData.toString("base64") - : sudoData.toString("utf8"), - isBinary, - size: sudoData.length, - }); - }); - }, - ); + }) + .catch(() => { + res + .status(403) + .json({ error: "Permission denied", needsSudo: true }); + }); return; } @@ -3564,30 +3558,38 @@ app.post("/ssh/file_manager/ssh/writeFile", async (req, res) => { } }); } else { - const isPermDenied = errorData.toLowerCase().includes("permission denied"); + const isPermDenied = errorData + .toLowerCase() + .includes("permission denied"); if (isPermDenied && sshConn.sudoPassword) { - const sudoWriteCmd = `echo '${base64Content}' | base64 -d | sudo tee '${escapedPath}' > /dev/null && echo "SUCCESS"`; - execWithSudo(sshConn, `bash -c "echo '${base64Content}' | base64 -d > '${escapedPath}' && echo SUCCESS"`, sshConn.sudoPassword, (sudoErr, sudoStream) => { - if (sudoErr) { - if (!res.headersSent) { - res.status(403).json({ error: "Permission denied", needsSudo: true }); - } - return; - } - let sudoOut = ""; - sudoStream.on("data", (chunk: Buffer) => { sudoOut += chunk.toString(); }); - sudoStream.on("close", () => { - if (sudoOut.includes("SUCCESS")) { + execWithSudo( + sshConn, + `bash -c "echo '${base64Content}' | base64 -d > '${escapedPath}' && echo SUCCESS"`, + sshConn.sudoPassword, + ) + .then(({ stdout, code: sudoCode }) => { + if (sudoCode === 0 && stdout.includes("SUCCESS")) { restoreOriginalMode(null, () => { if (!res.headersSent) { - res.json({ message: "File written successfully", path: filePath }); + res.json({ + message: "File written successfully", + path: filePath, + }); } }); } else if (!res.headersSent) { - res.status(403).json({ error: "Permission denied", needsSudo: true }); + res + .status(403) + .json({ error: "Permission denied", needsSudo: true }); + } + }) + .catch(() => { + if (!res.headersSent) { + res + .status(403) + .json({ error: "Permission denied", needsSudo: true }); } }); - }); return; } fileLogger.error( @@ -4981,7 +4983,9 @@ app.post("/ssh/file_manager/ssh/downloadFileStream", async (req, res) => { const sshConn = sshSessions[sessionId]; if (!sshConn?.isConnected) { - return res.status(400).json({ error: "SSH session not found or not connected" }); + return res + .status(400) + .json({ error: "SSH session not found or not connected" }); } if (!verifySessionOwnership(sshConn, userId)) { return res.status(403).json({ error: "Session access denied" }); @@ -4991,9 +4995,11 @@ app.post("/ssh/file_manager/ssh/downloadFileStream", async (req, res) => { try { const sftp = await getSessionSftp(sshConn); - const stats = await new Promise<{ size: number; isFile: () => boolean }>((resolve, reject) => { - sftp.stat(filePath, (err, s) => (err ? reject(err) : resolve(s))); - }); + const stats = await new Promise<{ size: number; isFile: () => boolean }>( + (resolve, reject) => { + sftp.stat(filePath, (err, s) => (err ? reject(err) : resolve(s))); + }, + ); if (!stats.isFile()) { return res.status(400).json({ error: "Cannot download directories" }); @@ -5001,7 +5007,10 @@ app.post("/ssh/file_manager/ssh/downloadFileStream", async (req, res) => { const fileName = filePath.split("/").pop() || "download"; res.setHeader("Content-Type", "application/octet-stream"); - res.setHeader("Content-Disposition", `attachment; filename="${encodeURIComponent(fileName)}"`); + res.setHeader( + "Content-Disposition", + `attachment; filename="${encodeURIComponent(fileName)}"`, + ); res.setHeader("Content-Length", String(stats.size)); const readStream = sftp.createReadStream(filePath); @@ -5015,7 +5024,9 @@ app.post("/ssh/file_manager/ssh/downloadFileStream", async (req, res) => { readStream.pipe(res); } catch (err) { if (!res.headersSent) { - res.status(500).json({ error: `Download failed: ${(err as Error).message}` }); + res + .status(500) + .json({ error: `Download failed: ${(err as Error).message}` }); } } }); diff --git a/src/backend/ssh/terminal.ts b/src/backend/ssh/terminal.ts index 050b1ef5..305bb12e 100644 --- a/src/backend/ssh/terminal.ts +++ b/src/backend/ssh/terminal.ts @@ -1,14 +1,14 @@ import { WebSocketServer, WebSocket, type RawData } from "ws"; -import { - Client, - type ClientChannel, - type PseudoTtyOptions, - BaseAgent, - utils as ssh2Utils, - type ParsedKey, - type SignCallback, - type SigningRequestOptions, - type IdentityCallback, +import ssh2pkg from "ssh2"; +const { Client, BaseAgent, utils: ssh2Utils } = ssh2pkg; +import type { + Client as ClientType, + ClientChannel, + PseudoTtyOptions, + ParsedKey, + SignCallback, + SigningRequestOptions, + IdentityCallback, } from "ssh2"; import net from "net"; import dgram from "dgram"; @@ -54,8 +54,7 @@ class MemoryAgent extends BaseAgent { cb?: SignCallback, ): void { const callback = typeof optionsOrCb === "function" ? optionsOrCb : cb!; - const options = - typeof optionsOrCb === "function" ? {} : optionsOrCb; + const options = typeof optionsOrCb === "function" ? {} : optionsOrCb; try { const algo = options.hash === "sha256" @@ -191,10 +190,7 @@ async function resolveJumpHost( }); try { const hostResults = await SimpleDBOps.select( - getDb() - .select() - .from(hosts) - .where(eq(hosts.id, hostId)), + getDb().select().from(hosts).where(eq(hosts.id, hostId)), "ssh_data", userId, ); @@ -212,8 +208,10 @@ async function resolveJumpHost( const { SharedCredentialManager } = await import("../utils/shared-credential-manager.js"); const sharedCredManager = SharedCredentialManager.getInstance(); - const sharedCred = - await sharedCredManager.getSharedCredentialForUser(hostId, userId); + const sharedCred = await sharedCredManager.getSharedCredentialForUser( + hostId, + userId, + ); if (sharedCred) { return { ...host, @@ -275,13 +273,13 @@ async function createJumpHostChain( jumpHosts: Array<{ hostId: number }>, userId: string, socks5Config?: SOCKS5Config | null, -): Promise { +): Promise { if (!jumpHosts || jumpHosts.length === 0) { return null; } - let currentClient: Client | null = null; - const clients: Client[] = []; + let currentClient: ClientType | null = null; + const clients: ClientType[] = []; try { const jumpHostConfigs = await Promise.all( @@ -562,9 +560,9 @@ wss.on("connection", async (ws: WebSocket, req) => { }); let currentSessionId: string | null = null; - let sshConn: Client | null = null; + let sshConn: ClientType | null = null; let sshStream: ClientChannel | null = null; - let lastJumpClient: Client | null = null; + let lastJumpClient: ClientType | null = null; let keyboardInteractiveFinish: ((responses: string[]) => void) | null = null; let totpPromptSent = false; let totpTimeout: NodeJS.Timeout | null = null; @@ -938,7 +936,9 @@ wss.on("connection", async (ws: WebSocket, req) => { sessionName: tmuxName, hostId: session.hostId, }); - ws.send(JSON.stringify({ type: "tmux_detached", sessionName: tmuxName })); + ws.send( + JSON.stringify({ type: "tmux_detached", sessionName: tmuxName }), + ); } break; } @@ -2468,7 +2468,9 @@ wss.on("connection", async (ws: WebSocket, req) => { hostConfig.userId; // Cloudflare Tunnel: connect via WebSocket proxy - const cfConfig = hostConfig.terminalConfig as Record | undefined; + const cfConfig = hostConfig.terminalConfig as + | Record + | undefined; if (cfConfig?.cfAccessClientId && cfConfig?.cfAccessClientSecret) { try { const WebSocket = (await import("ws")).default; @@ -2484,7 +2486,10 @@ wss.on("connection", async (ws: WebSocket, req) => { await new Promise((resolve, reject) => { cfWs.on("open", () => resolve()); cfWs.on("error", (err) => reject(err)); - setTimeout(() => reject(new Error("Cloudflare tunnel timeout")), 30000); + setTimeout( + () => reject(new Error("Cloudflare tunnel timeout")), + 30000, + ); }); const { Duplex } = await import("stream"); @@ -2497,7 +2502,8 @@ wss.on("connection", async (ws: WebSocket, req) => { cfWs.on("message", (data) => duplexStream.push(data)); cfWs.on("close", () => duplexStream.push(null)); - connectConfig.sock = duplexStream as unknown as typeof connectConfig.sock; + connectConfig.sock = + duplexStream as unknown as typeof connectConfig.sock; sendLog("handshake", "info", "Connected via Cloudflare Tunnel"); } catch (cfError) { sshLogger.error("Cloudflare tunnel connection failed", cfError, { @@ -2507,7 +2513,8 @@ wss.on("connection", async (ws: WebSocket, req) => { ws.send( JSON.stringify({ type: "error", - message: "Cloudflare tunnel connection failed: " + + message: + "Cloudflare tunnel connection failed: " + (cfError instanceof Error ? cfError.message : "Unknown error"), }), ); diff --git a/src/types/ui-types.ts b/src/types/ui-types.ts index aeac2a4a..8e0fba68 100644 --- a/src/types/ui-types.ts +++ b/src/types/ui-types.ts @@ -12,6 +12,7 @@ export type Host = { tags?: string[]; authType: "password" | "key" | "credential" | "none" | "opkssh"; credentialId?: string; + overrideCredentialUsername?: boolean; password?: string; key?: string; keyPassword?: string; @@ -45,6 +46,7 @@ export type Host = { keepaliveInterval?: number; keepaliveCountMax?: number; environmentVariables: { key: string; value: string }[]; + startupSnippetId?: number | null; }; useSocks5?: boolean; @@ -55,7 +57,7 @@ export type Host = { socks5ProxyChain?: { host: string; port: number; - type: 4 | 5 | "http"; + type: 4 | 5 | "http" | string; username?: string; password?: string; }[]; diff --git a/src/ui/AppShell.tsx b/src/ui/AppShell.tsx index 8088b0f3..3b1ddc84 100644 --- a/src/ui/AppShell.tsx +++ b/src/ui/AppShell.tsx @@ -27,7 +27,7 @@ import type { SplitMode, HostFolder, } from "@/types/ui-types"; -import { getSSHHosts } from "@/main-axios"; +import { getSSHHosts, getUserInfo } from "@/main-axios"; import { dbHealthMonitor } from "@/lib/db-health-monitor"; import type { SSHHostWithStatus } from "@/main-axios"; @@ -58,9 +58,9 @@ function sshHostToHost(h: SSHHostWithStatus): Host { enableTunnel: h.enableTunnel ?? false, enableFileManager: h.enableFileManager ?? false, enableDocker: h.enableDocker ?? false, - enableRdp: h.connectionType === "rdp", - enableVnc: h.connectionType === "vnc", - enableTelnet: h.connectionType === "telnet", + enableRdp: h.enableRdp ?? h.connectionType === "rdp", + enableVnc: h.enableVnc ?? h.connectionType === "vnc", + enableTelnet: h.enableTelnet ?? h.connectionType === "telnet", sshPort: h.port, rdpPort: 3389, vncPort: 5900, @@ -112,19 +112,6 @@ function buildHostTree(hosts: SSHHostWithStatus[]): HostFolder { export { tabIcon, renderTabContent } from "@/shell/tabUtils"; import { renderTabContent } from "@/shell/tabUtils"; -const DASHBOARD_TAB: Tab = { - id: "dashboard", - type: "dashboard", - label: "Dashboard", -}; - -const SINGLETON_TAB_LABELS: Partial> = { - "host-manager": "Host Manager", - docker: "Docker", - tunnel: "Tunnels", - network_graph: "Network Graph", -}; - // ─── AppShell ──────────────────────────────────────────────────────────────── export function AppShell({ @@ -135,7 +122,9 @@ export function AppShell({ onLogout: () => void; }) { const { t } = useTranslation(); - const [tabs, setTabs] = useState([DASHBOARD_TAB]); + const [tabs, setTabs] = useState([ + { id: "dashboard", type: "dashboard", label: t("nav.dashboard") }, + ]); const [activeTabId, setActiveTabId] = useState("dashboard"); const [commandPaletteOpen, setCommandPaletteOpen] = useState(false); const [splitMode, setSplitMode] = useState("none"); @@ -144,6 +133,7 @@ export function AppShell({ ); const [realHostTree, setRealHostTree] = useState(null); const [allHosts, setAllHosts] = useState([]); + const [isAdmin, setIsAdmin] = useState(false); const [sidebarOpen, setSidebarOpen] = useState(true); const [railView, setRailView] = useState("hosts"); @@ -159,6 +149,12 @@ export function AppShell({ if (isMobile) setSidebarOpen(false); }, [isMobile]); + useEffect(() => { + getUserInfo() + .then((info) => setIsAdmin(info.is_admin)) + .catch(() => setIsAdmin(false)); + }, []); + const pendingHostManagerEditId = useRef(null); const pendingHostManagerAction = useRef<"add-host" | "add-credential" | null>( null, @@ -378,7 +374,13 @@ export function AppShell({ const id = type; setTabs((prev) => { if (prev.find((t) => t.id === id)) return prev; - return [...prev, { id, type, label: SINGLETON_TAB_LABELS[type] ?? type }]; + const singletonLabels: Partial> = { + "host-manager": t("nav.hostManager"), + docker: t("nav.docker"), + tunnel: t("nav.tunnels"), + network_graph: t("nav.networkGraph"), + }; + return [...prev, { id, type, label: singletonLabels[type] ?? type }]; }); setActiveTabId(id); } @@ -530,7 +532,7 @@ export function AppShell({ )} - {railView === "admin-settings" && ( + {railView === "admin-settings" && isAdmin && (
@@ -582,6 +584,7 @@ export function AppShell({ sidebarOpen={sidebarOpen} splitMode={splitMode} username={username} + isAdmin={isAdmin} profileDropdownOpen={profileDropdownOpen} onProfileDropdownChange={setProfileDropdownOpen} onRailClick={handleRailClick} diff --git a/src/ui/components/input.tsx b/src/ui/components/input.tsx index bc0898a2..53ec593c 100644 --- a/src/ui/components/input.tsx +++ b/src/ui/components/input.tsx @@ -9,6 +9,8 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) { data-slot="input" className={cn( "h-8 w-full min-w-0 rounded-none border border-input bg-transparent px-2.5 py-1 text-xs transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-xs file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 md:text-xs dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", + type === "number" && + "[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none", className, )} {...props} diff --git a/src/ui/features/file-manager/FileManager.tsx b/src/ui/features/file-manager/FileManager.tsx index de591b27..a16a1e2c 100644 --- a/src/ui/features/file-manager/FileManager.tsx +++ b/src/ui/features/file-manager/FileManager.tsx @@ -70,7 +70,6 @@ import { listSSHFiles, resolveSSHPath, uploadSSHFile, - downloadSSHFile, createSSHFile, createSSHFolder, deleteSSHItem, @@ -410,6 +409,17 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) { async function initializeSSHConnection() { if (!currentHost || isConnectingRef.current) return; + if (currentHost.enableSsh === false) { + setHasConnectionError(true); + addLog({ + type: "error", + message: t("fileManager.sshRequiredForFileManager"), + timestamp: new Date().toISOString(), + }); + setIsLoading(false); + return; + } + isConnectingRef.current = true; try { @@ -813,9 +823,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) { ); files.push({ file, relativePath: path }); } else if (entry.isDirectory) { - const reader = ( - entry as FileSystemDirectoryEntry - ).createReader(); + const reader = (entry as FileSystemDirectoryEntry).createReader(); const dirEntries = await new Promise( (resolve, reject) => reader.readEntries(resolve, reject), ); diff --git a/src/ui/locales/en.json b/src/ui/locales/en.json index 69bc2626..8be051d5 100644 --- a/src/ui/locales/en.json +++ b/src/ui/locales/en.json @@ -1,199 +1,16 @@ { "credentials": { - "credentials": "Credentials", - "credentialsViewer": "Credentials Viewer", - "manageYourSSHCredentials": "Manage your SSH credentials securely", - "addCredential": "Add Credential", - "createCredential": "Create Credential", - "editCredential": "Edit Credential", - "viewCredential": "View Credential", - "duplicateCredential": "Duplicate Credential", - "deleteCredential": "Delete Credential", - "updateCredential": "Update Credential", - "credentialName": "Credential Name", - "credentialDescription": "Description", - "username": "Username", - "searchCredentials": "Search credentials...", - "selectFolder": "Select Folder", - "selectAuthType": "Select Auth Type", - "allFolders": "All Folders", - "allAuthTypes": "All Auth Types", - "uncategorized": "Uncategorized", - "totalCredentials": "Total", - "keyBased": "Key-based", - "passwordBased": "Password-based", "folders": "Folders", - "noCredentialsMatchFilters": "No credentials match your filters", - "noCredentialsYet": "No credentials created yet", - "createFirstCredential": "Create your first credential", - "failedToFetchCredentials": "Failed to fetch credentials", - "credentialDeletedSuccessfully": "Credential deleted successfully", - "failedToDeleteCredential": "Failed to delete credential", - "confirmDeleteCredential": "Are you sure you want to delete credential \"{{name}}\"?", - "credentialCreatedSuccessfully": "Credential created successfully", - "credentialUpdatedSuccessfully": "Credential updated successfully", - "failedToSaveCredential": "Failed to save credential", - "failedToFetchCredentialDetails": "Failed to fetch credential details", - "failedToFetchHostsUsing": "Failed to fetch hosts using this credential", - "loadingCredentials": "Loading credentials...", - "retry": "Retry", - "noCredentials": "No Credentials", - "noCredentialsMessage": "You haven't added any credentials yet. Click \"Add Credential\" to get started.", - "sshCredentials": "SSH Credentials", - "credentialsCount": "{{count}} credentials", - "refresh": "Refresh", - "passwordRequired": "Password is required", - "sshKeyRequired": "SSH key is required", - "credentialAddedSuccessfully": "Credential \"{{name}}\" added successfully", - "savingCredential": "Saving credential...", - "updatingCredential": "Updating credential...", - "general": "General", - "description": "Description", "folder": "Folder", - "tags": "Tags", - "addTagsSpaceToAdd": "Add tags (press space to add)", "password": "Password", "key": "Key", "sshPrivateKey": "SSH Private Key", "upload": "Upload", - "updateKey": "Update Key", "keyPassword": "Key Password", - "keyType": "Key Type", - "keyTypeRSA": "RSA", - "keyTypeECDSA": "ECDSA", - "keyTypeEd25519": "Ed25519", - "basicInfo": "Basic Info", - "authentication": "Authentication", - "organization": "Organization", - "basicInformation": "Basic Information", - "basicInformationDescription": "Enter the basic information for this credential", - "authenticationMethod": "Authentication Method", - "authenticationMethodDescription": "Choose how you want to authenticate with SSH servers", - "organizationDescription": "Organize your credentials with folders and tags", - "enterCredentialName": "Enter credential name", - "enterCredentialDescription": "Enter description (optional)", - "enterUsername": "Enter username", - "nameIsRequired": "Credential name is required", - "usernameIsRequired": "Username is required", - "authenticationType": "Authentication Type", - "passwordAuthDescription": "Use password authentication", - "sshKeyAuthDescription": "Use SSH key authentication", - "passwordIsRequired": "Password is required", - "sshKeyIsRequired": "SSH key is required", - "sshKeyType": "SSH Key Type", - "privateKey": "Private Key", - "enterPassword": "Enter password", - "enterPrivateKey": "Enter private key", - "keyPassphrase": "Key Passphrase", - "enterKeyPassphrase": "Enter key passphrase (optional)", - "keyPassphraseOptional": "Optional: leave empty if your key has no passphrase", - "leaveEmptyToKeepCurrent": "Leave empty to keep current value", - "uploadKeyFile": "Upload Key File", - "generateKeyPairButton": "Generate Key Pair", - "generateKeyPair": "Generate Key Pair", - "generateKeyPairDescription": "Generate a new SSH key pair. If you want to protect the key with a passphrase, enter it in the Key Password field below first.", - "deploySSHKey": "Deploy SSH Key", - "deploySSHKeyDescription": "Deploy public key to target server", - "sourceCredential": "Source Credential", - "targetHost": "Target Host", - "deploymentProcess": "Deployment Process", - "deploymentProcessDescription": "This will safely add the public key to the target host's ~/.ssh/authorized_keys file without overwriting existing keys. The operation is reversible.", - "chooseHostToDeploy": "Choose a host to deploy to...", - "deploying": "Deploying...", - "copyDeployCommand": "Copy Deploy Command", - "copyDeployCommandDescription": "Copy a shell command to manually add the public key to a host's authorized_keys file. Useful when password authentication is disabled.", - "deployCommandCopied": "Deploy command copied to clipboard", - "manualDeployInfo": "Paste this command in the target host's terminal (e.g., Proxmox console, IPMI, or physical access) to add the SSH key.", - "name": "Name", - "noHostsAvailable": "No hosts available", - "noHostsMatchSearch": "No hosts match your search", - "sshKeyGenerationNotImplemented": "SSH key generation feature coming soon", - "connectionTestingNotImplemented": "Connection testing feature coming soon", - "testConnection": "Test Connection", - "selectOrCreateFolder": "Select or create folder", - "noFolder": "No folder", - "orCreateNewFolder": "Or create new folder", - "addTag": "Add tag", - "saving": "Saving...", - "credentialId": "Credential ID", - "overview": "Overview", - "security": "Security", - "usage": "Usage", - "securityDetails": "Security Details", - "securityDetailsDescription": "View encrypted credential information", - "credentialSecured": "Credential Secured", - "credentialSecuredDescription": "All sensitive data is encrypted with AES-256", - "passwordAuthentication": "Password Authentication", - "keyAuthentication": "Key Authentication", - "securityReminder": "Security Reminder", - "securityReminderText": "Never share your credentials. All data is encrypted at rest.", - "hostsUsingCredential": "Hosts Using This Credential", - "noHostsUsingCredential": "No hosts are currently using this credential", - "timesUsed": "Times Used", - "lastUsed": "Last Used", - "connectedHosts": "Connected Hosts", - "created": "Created", - "lastModified": "Last Modified", - "usageStatistics": "Usage Statistics", - "copiedToClipboard": "{{field}} copied to clipboard", - "failedToCopy": "Failed to copy to clipboard", "sshKey": "SSH Key", - "createCredentialDescription": "Create a new SSH credential for secure access", - "editCredentialDescription": "Update the credential information", - "listView": "List", - "folderView": "Folders", - "unknownCredential": "Unknown", - "confirmRemoveFromFolder": "Are you sure you want to remove \"{{name}}\" from folder \"{{folder}}\"? The credential will be moved to \"Uncategorized\".", - "removedFromFolder": "Credential \"{{name}}\" removed from folder successfully", - "failedToRemoveFromFolder": "Failed to remove credential from folder", - "folderRenamed": "Folder \"{{oldName}}\" renamed to \"{{newName}}\" successfully", - "failedToRenameFolder": "Failed to rename folder", - "movedToFolder": "Credential \"{{name}}\" moved to \"{{folder}}\" successfully", - "failedToMoveToFolder": "Failed to move credential to folder", - "sshPublicKey": "SSH Public Key", - "publicKeyNote": "Public key is optional but recommended for key validation", - "publicKeyUploaded": "Public Key Uploaded", - "uploadPublicKey": "Upload Public Key", - "uploadPrivateKeyFile": "Upload Private Key File", - "uploadPublicKeyFile": "Upload Public Key File", - "privateKeyRequiredForGeneration": "Private key is required to generate public key", - "failedToGeneratePublicKey": "Failed to generate public key", - "generatePublicKey": "Generate from Private Key", - "publicKeyGeneratedSuccessfully": "Public key generated successfully", - "detectedKeyType": "Detected key type", - "detectingKeyType": "detecting...", - "optional": "Optional", - "generateKeyPairNew": "Generate New Key Pair", - "generateEd25519": "Generate Ed25519", - "generateECDSA": "Generate ECDSA", - "generateRSA": "Generate RSA", - "keyTypeEcdsaP256": "ECDSA P-256 (SSH)", - "keyTypeEcdsaP384": "ECDSA P-384 (SSH)", - "keyTypeEcdsaP521": "ECDSA P-521 (SSH)", - "keyTypeDsa": "DSA (SSH)", - "keyTypeRsaSha256": "RSA-SHA2-256", - "keyTypeRsaSha512": "RSA-SHA2-512", - "keyPairGeneratedSuccessfully": "{{keyType}} key pair generated successfully", - "failedToGenerateKeyPair": "Failed to generate key pair", - "generateKeyPairNote": "Generate a new SSH key pair directly. This will replace any existing keys in the form.", - "invalidKey": "Invalid Key", - "detectionError": "Detection Error", - "removing": "Removing:", - "clickToEditCredential": "Click to edit credential", - "dragToMoveBetweenFolders": "Drag to move between folders", - "keyBasedOnlyForDeployment": "Only SSH key-based credentials can be deployed", - "publicKeyRequiredForDeployment": "Public key is required for deployment", - "selectTargetHost": "Please select a target host", - "keyDeployedSuccessfully": "SSH key deployed successfully", - "deploymentFailed": "Deployment failed", - "failedToDeployKey": "Failed to deploy SSH key", - "clickToRenameFolder": "Click to rename folder", - "renameFolder": "Rename folder", - "idLabel": "ID:" + "uploadPrivateKeyFile": "Upload Private Key File" }, "homepage": { - "loggedInTitle": "Logged in!", - "loggedInMessage": "You are logged in! Use the sidebar to access all available tools. To get started, create an SSH Host in the SSH Manager tab. Once created, you can connect to that host using the other apps in the sidebar.", "failedToLoadAlerts": "Failed to load alerts", "failedToDismissAlert": "Failed to dismiss alert" }, @@ -202,20 +19,11 @@ "description": "Configure the Termix server URL to connect to your backend services", "serverUrl": "Server URL", "enterServerUrl": "Please enter a server URL", - "testConnectionFirst": "Please test the connection first", - "connectionSuccess": "Connection successful!", - "connectionFailed": "Connection failed", - "connectionError": "Connection error occurred", - "connected": "Connected", - "disconnected": "Disconnected", - "configSaved": "Configuration saved successfully", "saveFailed": "Failed to save configuration", "saveError": "Error saving configuration", "saving": "Saving...", "saveConfig": "Save Configuration", "helpText": "Enter the URL where your Termix server is running (e.g., http://localhost:30001 or https://your-server.com)", - "warning": "Warning", - "notValidatedWarning": "URL not validated - ensure it's correct", "changeServer": "Change Server", "mustIncludeProtocol": "Server URL must start with http:// or https://", "useEmbedded": "Use Local Server", @@ -234,14 +42,10 @@ "betaVersionDesc": "You are running {{current}}, which is newer than the latest stable release {{latest}}.", "releasedOn": "Released on {{date}}", "downloadUpdate": "Download Update", - "dismiss": "Dismiss", "checking": "Checking for updates...", "checkUpdates": "Check for Updates", "checkingUpdates": "Checking for updates...", - "refresh": "Refresh", - "updateRequired": "Update Required", - "updateDismissed": "Update notification dismissed", - "noUpdatesFound": "No updates found" + "updateRequired": "Update Required" }, "common": { "close": "Close", @@ -251,128 +55,61 @@ "continue": "Continue", "maintenance": "Maintenance", "degraded": "Degraded", - "discord": "Discord", "error": "Error", "warning": "Warning", - "info": "Info", - "success": "Success", "unsavedChanges": "Unsaved changes", "dismiss": "Dismiss", "loading": "Loading...", - "required": "Required", "optional": "Optional", "connect": "Connect", "copied": "Copied", "connecting": "Connecting...", - "creating": "Creating...", - "clear": "Clear", - "toggleSidebar": "Toggle Sidebar", - "sidebar": "Sidebar", - "home": "Home", - "expired": "Expired", - "expiresToday": "Expires today", - "expiresTomorrow": "Expires in {{days}} days", "updateAvailable": "Update Available", - "sshPath": "SSH Path", - "localPath": "Local Path", "appName": "Termix", "openInNewTab": "Open in New Tab", - "resetSidebarWidth": "Reset sidebar width", - "dragToResizeSidebar": "Drag to resize sidebar", - "noAuthCredentials": "No authentication credentials available for this SSH host", "noReleases": "No Releases", "updatesAndReleases": "Updates & Releases", "newVersionAvailable": "A new version ({{version}}) is available.", "failedToFetchUpdateInfo": "Failed to fetch update information", "preRelease": "Pre-release", - "loginFailed": "Login failed", "noReleasesFound": "No releases found.", - "yourBackupCodes": "Your Backup Codes", - "sendResetCode": "Send Reset Code", - "verifyCode": "Verify Code", - "resetPassword": "Reset Password", - "resetCode": "Reset Code", - "newPassword": "New Password", - "folder": "Folder", - "file": "File", - "renamedSuccessfully": "renamed successfully", - "deletedSuccessfully": "deleted successfully", - "noTunnelConnections": "No tunnel connections configured", - "sshTools": "SSH Tools", - "english": "English", - "chinese": "Chinese", - "german": "German", "cancel": "Cancel", - "done": "Done", "username": "Username", - "name": "Name", "login": "Login", - "logout": "Logout", "register": "Register", "password": "Password", - "version": "Version", "confirmPassword": "Confirm Password", "back": "Back", - "email": "Email", - "submit": "Submit", - "change": "Change", "save": "Save", "saving": "Saving...", "delete": "Delete", "rename": "Rename", "edit": "Edit", "add": "Add", - "search": "Search", "confirm": "Confirm", - "yes": "Yes", "no": "No", "or": "OR", - "ok": "OK", - "enabled": "Enabled", - "disabled": "Disabled", - "important": "Important", - "notEnabled": "Not Enabled", - "settingUp": "Setting up...", "next": "Next", "previous": "Previous", "refresh": "Refresh", - "settings": "Settings", - "profile": "Profile", - "help": "Help", - "about": "About", "language": "Language", - "autoDetect": "Auto-detect", - "changeAccountPassword": "Change your account password", - "passwordResetTitle": "Password Reset", - "passwordResetDescription": "You are about to reset your password. This will log you out of all active sessions.", - "enterSixDigitCode": "Enter the 6-digit code from the docker container logs for user:", - "enterNewPassword": "Enter your new password for user:", - "passwordsDoNotMatch": "Passwords do not match", - "passwordMinLength": "Password must be at least 6 characters long", - "passwordResetSuccess": "Password reset successfully! You can now log in with your new password.", - "failedToInitiatePasswordReset": "Failed to initiate password reset", - "failedToVerifyResetCode": "Failed to verify reset code", - "failedToCompletePasswordReset": "Failed to complete password reset", - "documentation": "Documentation", - "retry": "Retry", "checking": "Checking...", "checkingDatabase": "Checking database connection...", "checkingAuthentication": "Checking authentication...", "backendReconnected": "Server connection restored", "connectionDegraded": "Server connection lost, recovering…", "reload": "Reload", - "actions": "Actions", "remove": "Remove", - "revoke": "Revoke", "create": "Create", "update": "Update", - "pageOutdated": "This page is outdated. Please refresh to load the latest version.", - "refreshPage": "Refresh Page" + "copy": "Copy", + "copyFailed": "Failed to copy to clipboard", + "maximize": "Maximize", + "restore": "Restore", + "of": "of" }, "nav": { "home": "Home", - "hosts": "Hosts", - "credentials": "Credentials", "terminal": "Terminal", "docker": "Docker", "tunnels": "Tunnels", @@ -380,173 +117,64 @@ "serverStats": "Server Stats", "admin": "Admin", "userProfile": "User Profile", - "tools": "Tools", - "snippets": "Snippets", - "newTab": "New Tab", "splitScreen": "Split Screen", - "closeTab": "Close Tab", "confirmClose": "Close this active session?", "close": "Close", "cancel": "Cancel", "sshManager": "SSH Manager", - "hostManager": "Host Manager", "cannotSplitTab": "Cannot split this tab", - "tabNavigation": "Tab Navigation", "hostTabTitle": "{{username}}@{{ip}}:{{port}}", "copyPassword": "Copy Password", "copySudoPassword": "Copy Sudo Password", "passwordCopied": "Password copied to clipboard", - "sudoPasswordCopied": "Sudo password copied to clipboard", "noPasswordAvailable": "No password available", "failedToCopyPassword": "Failed to copy password", - "openFileManager": "Open File Manager" + "openFileManager": "Open File Manager", + "dashboard": "Dashboard", + "networkGraph": "Network Graph", + "quickConnect": "Quick Connect", + "sshTools": "SSH Tools", + "history": "History", + "hosts": "Hosts", + "snippets": "Snippets", + "hostManager": "Host Manager" }, "hosts": { - "title": "Host Manager", "hosts": "Hosts", - "sshHosts": "SSH Hosts", "noHosts": "No SSH Hosts", - "noHostsMessage": "You haven't added any SSH hosts yet. Click \"Add Host\" to get started.", - "loadingHosts": "Loading hosts...", - "failedToLoadHosts": "Failed to load hosts", "retry": "Retry", "refresh": "Refresh", "optional": "Optional", - "hostsCount": "{{count}} hosts", - "importJson": "Import JSON", - "importing": "Importing...", - "exportAllJson": "Export All", - "exporting": "Exporting...", - "exportedAllHosts": "Exported {{count}} hosts", - "failedToExportAllHosts": "Failed to export hosts. Please ensure you're logged in and have access to the host data.", - "exportAllSensitiveWarning": "The exported file will contain sensitive authentication data (passwords/SSH keys) in plaintext. Please keep the file secure and delete it after use.", - "importJsonTitle": "Import SSH Hosts from JSON", - "importJsonDesc": "Upload a JSON file to bulk import multiple SSH hosts (max 100).", "downloadSample": "Download Sample", - "formatGuide": "Format Guide", - "exportCredentialWarning": "Warning: Host \"{{name}}\" uses credential authentication. The exported file will not include the credential data and will need to be manually reconfigured after import. Do you want to continue?", - "exportSensitiveDataWarning": "Warning: Host \"{{name}}\" contains sensitive authentication data (password/SSH key). The exported file will include this data in plaintext. Please keep the file secure and delete it after use. Do you want to continue?", - "uncategorized": "Uncategorized", - "confirmDelete": "Are you sure you want to delete \"{{name}}\" ?", - "failedToDeleteHost": "Failed to delete host", - "failedToExportHost": "Failed to export host. Please ensure you're logged in and have access to the host data.", - "jsonMustContainHosts": "JSON must contain a \"hosts\" array or be an array of hosts", - "noHostsInJson": "No hosts found in JSON file", - "maxHostsAllowed": "Maximum 100 hosts allowed per import", - "importCompleted": "Import completed: {{success}} successful, {{failed}} failed", - "importCreated": "created", - "importUpdated": "updated", - "importFailedCount": "failed", + "failedToDeleteHost": "Failed to delete {{name}}", "importSkipExisting": "Import (skip existing)", - "importOverwriteExisting": "Import (overwrite existing)", - "importFailed": "Import failed", - "importError": "Import error", - "failedToImportJson": "Failed to import JSON file", "connectionDetails": "Connection Details", - "connectionType": "Connection Type", "ssh": "SSH", - "rdp": "Remote Desktop (RDP)", - "vnc": "VNC", "telnet": "Telnet", "remoteDesktop": "Remote Desktop", - "guacamoleSettings": "Remote Desktop Settings", - "organization": "Organization", - "ipAddress": "IP Address or Hostname", - "macAddress": "MAC Address", - "macAddressDesc": "For Wake-on-LAN. Format: AA:BB:CC:DD:EE:FF", - "wakeOnLan": "Wake on LAN", - "wolSent": "Wake-on-LAN packet sent", - "wolFailed": "Failed to send Wake-on-LAN packet", - "ipRequired": "IP address is required", - "portRequired": "Port is required", "port": "Port", - "name": "Name", "username": "Username", - "usernameRequired": "Username is required (SSH/Telnet only)", "folder": "Folder", "tags": "Tags", "pin": "Pin", - "notes": "Notes", - "expirationDate": "Expiration Date", - "passwordRequired": "Password is required when using password authentication", - "sshKeyRequired": "SSH Private Key is required when using key authentication", - "keyTypeRequired": "Key Type is required when using key authentication", - "mustSelectValidSshConfig": "Must select a valid SSH configuration from the list", "addHost": "Add Host", "editHost": "Edit Host", "cloneHost": "Clone Host", - "updateHost": "Update Host", - "hostUpdatedSuccessfully": "Host \"{{name}}\" updated successfully!", - "hostAddedSuccessfully": "Host \"{{name}}\" added successfully!", - "hostDeletedSuccessfully": "Host \"{{name}}\" deleted successfully!", - "failedToSaveHost": "Failed to save host. Please try again.", - "savingHost": "Saving host...", - "updatingHost": "Updating host...", - "cloningHost": "Cloning host...", "enableTerminal": "Enable Terminal", - "enableTerminalDesc": "Enable/disable host visibility in Terminal tab", "enableTunnel": "Enable Tunnel", - "enableTunnelDesc": "Enable/disable host visibility in Tunnel tab", "enableFileManager": "Enable File Manager", - "enableFileManagerDesc": "Enable/disable host visibility in File Manager tab", - "enableDockerDesc": "Enable/disable host visibility in Docker tab", "enableDocker": "Enable Docker", "defaultPath": "Default Path", - "defaultPathDesc": "Default directory when opening file manager for this host", "connection": "Connection", - "remove": "Remove", - "sshpassRequired": "Sshpass Required For Password Authentication", - "sshpassRequiredDesc": "For password authentication in tunnels, sshpass must be installed on the system.", - "otherInstallMethods": "Other installation methods:", - "debianUbuntuEquivalent": "(Debian/Ubuntu) or the equivalent for your OS.", - "or": "or", - "centosRhelFedora": "CentOS/RHEL/Fedora", - "macos": "macOS", - "windows": "Windows (WSL, no official binary)", - "sshServerConfigRequired": "SSH Server Configuration Required", - "sshServerConfigDesc": "For tunnel connections, the SSH server must be configured to allow port forwarding:", - "gatewayPortsYes": "to bind remote ports to all interfaces", - "allowTcpForwardingYes": "to enable port forwarding", - "permitRootLoginYes": "if using root user for tunneling", - "editSshConfig": "Edit the SSH configuration file:", - "sshConfigPath": "/etc/ssh/sshd_config", - "restartSshService": "Then restart the SSH service:", - "restartSshCommand": "sudo systemctl restart sshd", "upload": "Upload", "authentication": "Authentication", "password": "Password", "key": "Key", "credential": "Credential", "none": "None", - "opkssh": "OPKSSH", - "opksshAuthDescription": "Prompts you for OPKSSH web-auth upon every connect. Your auth token will last for 24 hours until it must be renewed.", - "selectCredential": "Select Credential", - "selectCredentialPlaceholder": "Choose a credential...", - "credentialRequired": "Credential is required when using credential authentication", - "credentialDescription": "Selecting a credential will overwrite the current username and use the credential's authentication details.", - "cannotChangeAuthAsSharedUser": "Cannot change authentication as shared user", "sshPrivateKey": "SSH Private Key", - "keyPassword": "Key Password", "keyType": "Key Type", - "autoDetect": "Auto-detect", - "rsa": "RSA", - "ed25519": "ED25519", - "ecdsaNistP256": "ECDSA NIST P-256", - "ecdsaNistP384": "ECDSA NIST P-384", - "ecdsaNistP521": "ECDSA NIST P-521", - "dsa": "DSA", - "rsaSha2256": "RSA SHA2-256", - "rsaSha2512": "RSA SHA2-512", "uploadFile": "Upload File", - "pasteKey": "Paste Key", - "updateKey": "Update Key", - "existingKey": "Existing Key (click to change)", - "existingCredential": "Existing Credential (click to change)", - "addTagsSpaceToAdd": "add tags (space to add)", - "terminalBadge": "Terminal", - "tunnelBadge": "Tunnel", - "fileManagerBadge": "File Manager", - "general": "General", "tabGeneral": "General", "tabTunnels": "Tunnels", "tabDocker": "Docker", @@ -560,483 +188,61 @@ "fileManager": "File Manager", "serverStats": "Server Stats", "status": "Status", - "statistics": "Statistics", - "hostViewer": "Host Viewer", - "enableServerStats": "Enable Server Stats", - "enableServerStatsDesc": "Enable/disable server statistics collection for this host", - "displayItems": "Display Items", - "displayItemsDesc": "Choose which metrics to display on the server stats page", - "enableCpu": "CPU Usage", - "enableMemory": "Memory Usage", - "enableDisk": "Disk Usage", - "enableNetwork": "Network Statistics (Coming Soon)", - "enableProcesses": "Process Count (Coming Soon)", - "enableUptime": "Uptime (Coming Soon)", - "enableHostname": "Hostname (Coming Soon)", - "enableOs": "Operating System (Coming Soon)", - "customCommands": "Custom Commands (Coming Soon)", - "customCommandsDesc": "Define custom shutdown and reboot commands for this server", - "shutdownCommand": "Shutdown Command", - "rebootCommand": "Reboot Command", - "confirmRemoveFromFolder": "Are you sure you want to remove \"{{name}}\" from folder \"{{folder}}\"? The host will be moved to \"No Folder\".", - "removedFromFolder": "Host \"{{name}}\" removed from folder successfully", - "failedToRemoveFromFolder": "Failed to remove host from folder", "folderRenamed": "Folder \"{{oldName}}\" renamed to \"{{newName}}\" successfully", "failedToRenameFolder": "Failed to rename folder", - "editFolderAppearance": "Edit Folder Appearance", - "editFolderAppearanceDesc": "Customize the color and icon for folder", - "folderColor": "Folder Color", - "folderIcon": "Folder Icon", - "preview": "Preview", - "folderAppearanceUpdated": "Folder appearance updated successfully", - "failedToUpdateFolderAppearance": "Failed to update folder appearance", - "deleteAllHostsInFolder": "Delete All Hosts in Folder", - "confirmDeleteAllHostsInFolder": "Are you sure you want to delete all {{count}} hosts in folder \"{{folder}}\"? This action cannot be undone.", - "allHostsInFolderDeleted": "Deleted {{count}} hosts from folder \"{{folder}}\" successfully", - "failedToDeleteHostsInFolder": "Failed to delete hosts in folder", - "movedToFolder": "Host \"{{name}}\" moved to \"{{folder}}\" successfully", - "failedToMoveToFolder": "Failed to move host to folder", - "clickToRenameFolder": "Click to rename folder", - "renameFolder": "Rename folder", - "removeFromFolder": "Remove from folder \"{{folder}}\"", + "movedToFolder": "Moved to \"{{folder}}\"", "editHostTooltip": "Edit host", - "deleteHostTooltip": "Delete host", - "exportHostTooltip": "Export host", - "cloneHostTooltip": "Clone host", - "clickToEditHost": "Click to edit host", - "dragToMoveBetweenFolders": "Drag to move between folders", - "exportedHostConfig": "Exported host configuration for {{name}}", - "openTerminal": "Open Terminal", - "openRdp": "Open RDP", - "openVnc": "Open VNC", - "openTelnet": "Open Telnet", - "openFileManager": "Open File Manager", - "openTunnels": "Open Tunnels", - "openServerDetails": "Open Server Details", - "statistics": "Statistics", - "enabledWidgets": "Enabled Widgets", - "openServerStats": "Open Server Stats", - "openFileManager": "Open File Manager", - "openTunnels": "Open Tunnels", - "enabledWidgetsDesc": "Select which statistics widgets to display for this host", - "monitoringConfiguration": "Monitoring Configuration", - "monitoringConfigurationDesc": "Configure how often server statistics and status are checked", - "statusCheckEnabled": "Enable Status Monitoring", - "statusCheckEnabledDesc": "Check if the server is online or offline", - "statusCheckInterval": "Status Check Interval", - "statusCheckIntervalDesc": "How often to check if host is online (5s - 1h)", "statusChecks": "Status Checks", - "disableTcpPing": "Disable TCP Ping", - "disableTcpPingDescription": "Turn off status checks (online/offline detection) for this host", - "useGlobalStatusInterval": "Use global default", - "useGlobalMetricsInterval": "Use global default", - "usingGlobalDefault": "Using global default ({{value}}s)", "metricsCollection": "Metrics Collection", - "metricsEnabled": "Enable Metrics Monitoring", - "metricsEnabledDesc": "Collect CPU, RAM, disk, and other system statistics", "metricsInterval": "Metrics Collection Interval", "metricsIntervalDesc": "How often to collect server statistics (5s - 1h)", - "metricsNotAvailableForConnectionType": "Server metrics are only available for SSH hosts. TCP ping status checks can still be enabled above.", - "intervalSeconds": "seconds", - "intervalMinutes": "minutes", - "intervalValidation": "Monitoring intervals must be between 5 seconds and 1 hour (3600 seconds)", - "monitoringDisabled": "Server monitoring is disabled for this host", - "enableMonitoring": "Enable monitoring in Host Manager → Statistics tab", - "monitoringDisabledBadge": "Monitoring Off", - "statusMonitoring": "Status", - "metricsMonitoring": "Metrics", - "terminalCustomization": "Terminal Customization", - "appearance": "Appearance", "behavior": "Behavior", - "advanced": "Advanced", "themePreview": "Theme Preview", "theme": "Theme", - "selectTheme": "Select theme", - "chooseColorTheme": "Choose a color theme for the terminal", "fontFamily": "Font Family", - "selectFont": "Select font", - "selectFontDesc": "Select the font to use in the terminal", "fontSize": "Font Size", - "fontSizeValue": "Font Size: {{value}}px", - "adjustFontSize": "Adjust the terminal font size", "letterSpacing": "Letter Spacing", - "letterSpacingValue": "Letter Spacing: {{value}}px", - "adjustLetterSpacing": "Adjust spacing between characters", "lineHeight": "Line Height", - "lineHeightValue": "Line Height: {{value}}", - "adjustLineHeight": "Adjust spacing between lines", "cursorStyle": "Cursor Style", - "selectCursorStyle": "Select cursor style", - "cursorStyleBlock": "Block", - "cursorStyleUnderline": "Underline", - "cursorStyleBar": "Bar", - "chooseCursorAppearance": "Choose the cursor appearance", "cursorBlink": "Cursor Blink", - "enableCursorBlink": "Enable cursor blinking animation", "scrollbackBuffer": "Scrollback Buffer", - "scrollbackBufferValue": "Scrollback Buffer: {{value}} lines", - "scrollbackBufferDesc": "Number of lines to keep in scrollback history", "bellStyle": "Bell Style", - "selectBellStyle": "Select bell style", - "bellStyleNone": "None", - "bellStyleSound": "Sound", - "bellStyleVisual": "Visual", - "bellStyleBoth": "Both", - "bellStyleDesc": "How to handle terminal bell (BEL character, \\x07). Programs trigger this when completing tasks, encountering errors, or for notifications. \"Sound\" plays an audio beep, \"Visual\" flashes the screen briefly, \"Both\" does both, \"None\" disables bell alerts.", "rightClickSelectsWord": "Right Click Selects Word", - "rightClickSelectsWordDesc": "Right-clicking selects the word under cursor", "fastScrollModifier": "Fast Scroll Modifier", - "selectModifier": "Select modifier", - "modifierAlt": "Alt", - "modifierCtrl": "Ctrl", - "modifierShift": "Shift", - "fastScrollModifierDesc": "Modifier key for fast scrolling", "fastScrollSensitivity": "Fast Scroll Sensitivity", - "fastScrollSensitivityValue": "Fast Scroll Sensitivity: {{value}}", - "fastScrollSensitivityDesc": "Scroll speed multiplier when modifier is held", - "minimumContrastRatio": "Minimum Contrast Ratio", - "minimumContrastRatioValue": "Minimum Contrast Ratio: {{value}}", - "minimumContrastRatioDesc": "Automatically adjust colors for better readability", "sshAgentForwarding": "SSH Agent Forwarding", - "sshAgentForwardingDesc": "Forward SSH authentication agent to remote host", "backspaceMode": "Backspace Mode", - "selectBackspaceMode": "Select backspace mode", - "backspaceModeNormal": "Normal (DEL)", - "backspaceModeControlH": "Control-H (^H)", - "backspaceModeDesc": "Backspace key behavior for compatibility", "startupSnippet": "Startup Snippet", "selectSnippet": "Select snippet", - "searchSnippets": "Search snippets...", - "snippetNone": "None", - "noneAuthTitle": "Keyboard-Interactive Authentication", - "noneAuthDescription": "This authentication method will use keyboard-interactive authentication when connecting to the SSH server.", - "noneAuthDetails": "Keyboard-interactive authentication allows the server to prompt you for credentials during connection. This is useful for servers that require multi-factor authentication or if you do not want to save credentials locally.", - "opksshAuthTitle": "OPKSSH Authentication", "forceKeyboardInteractive": "Force Keyboard-Interactive", - "forceKeyboardInteractiveDesc": "Forces the use of keyboard-interactive authentication. This is sometimes required for servers that use Two-Factor Authentication (TOTP/2FA).", "overrideCredentialUsername": "Override Credential Username", - "overrideCredentialUsernameDesc": "Use a different username than the one stored in the credential. This allows you to use the same credential with different usernames.", - "jumpHosts": "Jump Hosts", - "jumpHostsDescription": "Jump hosts (also known as bastion hosts) allow you to connect to a target server through one or more intermediate servers. This is useful for accessing servers behind firewalls or in private networks.", + "overrideCredentialUsernameDesc": "Use the username specified above instead of the credential's username", "jumpHostChain": "Jump Host Chain", - "addJumpHost": "Add Jump Host", - "selectServer": "Select Server", - "searchServers": "Search servers...", - "noServerFound": "No server found", - "jumpHostsOrder": "Connections will be made in order: Jump Host 1 → Jump Host 2 → ... → Target Server", - "socks5Proxy": "SOCKS5 Proxy", "portKnocking": "Port Knocking", - "portKnockingDesc": "Send a sequence of connection attempts to specified ports before connecting. Used to open firewall rules dynamically.", "addKnock": "Add Port", - "delayMs": "Delay", - "socks5Description": "Configure SOCKS5 proxy for SSH connection. All traffic will be routed through the specified proxy server.", - "enableSocks5": "Enable SOCKS5 Proxy", - "enableSocks5Description": "Use SOCKS5 proxy for this SSH connection", - "socks5Host": "Proxy Host", - "socks5Port": "Proxy Port", - "socks5Username": "Proxy Username", - "socks5Password": "Proxy Password", - "socks5UsernameOptional": "Optional: leave empty if proxy doesn't require authentication", - "socks5PasswordOptional": "Optional: leave empty if proxy doesn't require authentication", - "socks5ProxyChain": "Proxy Chain", - "socks5ProxyChainDescription": "Configure a chain of SOCKS proxies. Each proxy in the chain will connect through the previous one.", - "socks5ProxyMode": "Proxy Mode", - "socks5UseSingleProxy": "Use Single Proxy", - "socks5UseProxyChain": "Use Proxy Chain", - "socks5UsePreset": "Use Saved Preset", - "socks5SelectPreset": "Select Preset", - "socks5ManagePresets": "Manage Presets", - "socks5ProxyNode": "Proxy {{number}}", - "socks5AddProxy": "Add Proxy to Chain", - "socks5RemoveProxy": "Remove Proxy", - "socks5ProxyType": "Proxy Type", - "socks5SaveAsPreset": "Save as Preset", - "socks5SavePresetTitle": "Save Proxy Chain as Preset", - "socks5SavePresetDescription": "Save the current proxy chain configuration as a reusable preset", - "socks5PresetName": "Preset Name", - "socks5PresetDescription": "Description (optional)", - "socks5PresetCreated": "Proxy chain preset created", - "socks5PresetUpdated": "Proxy chain preset updated", - "socks5PresetDeleted": "Proxy chain preset deleted", - "socks5PresetSaved": "Preset \"{{name}}\" saved successfully", - "socks5PresetSaveError": "Failed to save preset", - "socks5PresetNameRequired": "Preset name is required", - "socks5EmptyChainError": "Cannot save an empty proxy chain", - "socks5ProxyChainEmpty": "Add at least one proxy to the chain", - "socks5HostDescription": "Hostname or IP address of the SOCKS proxy server", - "socks5PortDescription": "Port number of the SOCKS proxy server (default: 1080)", - "addProxyNode": "Add Proxy Node", - "noProxyNodes": "No proxy nodes configured. Click 'Add Proxy Node' to add one.", + "addProxyNode": "Add Node", "proxyNode": "Proxy Node", "proxyType": "Proxy Type", "quickActions": "Quick Actions", - "quickActionsDescription": "Quick actions allow you to create custom buttons that execute SSH snippets on this server. These buttons will appear at the top of the Server Stats page for quick access.", - "quickActionsList": "Quick Actions List", - "addQuickAction": "Add Quick Action", - "quickActionName": "Action name", - "noSnippetFound": "No snippet found", - "quickActionsOrder": "Quick action buttons will appear in the order listed above on the Server Stats page", - "sidebarCustomization": "Sidebar Button Customization", - "sidebarCustomizationDesc": "Choose which actions appear as quick buttons in the sidebar. Actions not shown as buttons will appear in the dropdown menu.", - "showTerminalInSidebar": "Show Terminal Button", - "showTerminalInSidebarDesc": "Display terminal as a quick button in the sidebar", - "showFileManagerInSidebar": "Show File Manager Button", - "showFileManagerInSidebarDesc": "Display file manager as a quick button in the sidebar", - "showTunnelInSidebar": "Show Tunnel Button", - "showTunnelInSidebarDesc": "Display tunnel management as a quick button in the sidebar", - "showDockerInSidebar": "Show Docker Button", - "showDockerInSidebarDesc": "Display docker management as a quick button in the sidebar", - "showServerStatsInSidebar": "Show Server Stats Button", - "showServerStatsInSidebarDesc": "Display server statistics as a quick button in the sidebar", - "atLeastOneActionRequired": "At least one enabled action must be shown in the sidebar", - "advancedAuthSettings": "Advanced Authentication Settings", "sudoPasswordAutoFill": "Sudo Password Auto-Fill", - "sudoPasswordAutoFillDesc": "Automatically offer to insert SSH password when sudo prompts for password", "sudoPassword": "Sudo Password", - "sudoPasswordDesc": "Optional password for sudo commands (useful with key authentication)", - "keepaliveSettings": "SSH Keepalive", - "keepaliveSettingsDesc": "Configure SSH-level keepalive behavior. Set interval to 0 to disable SSH keepalives (useful for MikroTik and other devices that ignore keepalive requests).", "keepaliveInterval": "Keepalive Interval (ms)", - "keepaliveIntervalDesc": "How often to send SSH keepalive pings. Default: 30000 (30s). Set to 0 to disable.", - "keepaliveCountMax": "Keepalive Max Failures", - "keepaliveCountMaxDesc": "Disconnect after this many unanswered keepalives. Default: 3.", - "socks4": "SOCKS4", - "socks5": "SOCKS5", - "httpConnect": "HTTP CONNECT", - "testProxy": "Test Connection", - "testingProxy": "Testing...", - "proxyTestSuccess": "Proxy connection successful ({{latency}}ms)", - "proxyTestFailed": "Proxy test failed: {{error}}", - "connectionPath": "Connection Path", - "executeSnippetOnConnect": "Execute a snippet when the terminal connects", - "autoMosh": "Auto-MOSH", - "autoMoshDesc": "Automatically run MOSH command on connect", "moshCommand": "MOSH Command", - "moshCommandDesc": "The MOSH command to execute", - "autoTmux": "Auto-tmux", - "autoTmuxDesc": "Automatically attach to an existing (or start a new) tmux session for persistent sessions and cross-device continuity", "environmentVariables": "Environment Variables", - "environmentVariablesDesc": "Set custom environment variables for the terminal session", - "variableName": "Variable name", - "variableValue": "Value", "addVariable": "Add Variable", "docker": "Docker", - "openDocker": "Open Docker", - "copyFullScreenUrl": "Copy Full-Screen URL", - "fullScreenUrlCopied": "Full-screen URL copied to clipboard", - "failedToCopyUrl": "Failed to copy URL to clipboard", "copyTerminalUrl": "Copy Terminal URL", "copyFileManagerUrl": "Copy File Manager URL", - "copyTunnelUrl": "Copy Tunnel URL", - "copyServerStatsUrl": "Copy Server Stats URL", - "copyDockerUrl": "Copy Docker URL", "copyRemoteDesktopUrl": "Copy Remote Desktop URL", - "fullScreenUrlTooltip": "Copy URL to open this app in full-screen mode", - "notEnabled": "Docker is not enabled for this host. Enable it in Host Settings to use Docker features.", - "validating": "Validating Docker...", - "error": "Error", - "errorCode": "Error code: {{code}}", - "version": "Docker v{{version}}", - "current": "Current", - "used_limit": "Used / Limit", - "percentage": "Percentage", - "input": "Input", - "output": "Output", - "read": "Read", - "write": "Write", - "pids": "PIDs", - "name": "Name", - "id": "ID", - "state": "State", - "console": "Console", - "containerMustBeRunning": "Container must be running to connect to console", - "authenticationRequired": "Authentication required", - "connectedTo": "Connected to {{containerName}}", - "disconnected": "Disconnected", - "consoleError": "Console error", - "errorMessage": "Error: {{message}}", "failedToConnect": "Failed to connect to console", - "disconnectedFromContainer": "Disconnected from container console.", - "containerNotRunning": "Container is not running", - "startContainerToAccess": "Start the container to access the console", - "selectShell": "Select shell", - "bash": "Bash", - "sh": "Sh", - "ash": "Ash", - "connecting": "Connecting...", "connect": "Connect", "disconnect": "Disconnect", - "notConnected": "Not connected", - "clickToConnect": "Click Connect to start an interactive shell", - "connectingTo": "Connecting to {{containerName}}...", - "containerMustBeRunningToViewStats": "Container must be running to view stats", - "failedToFetchStats": "Failed to fetch stats", - "noContainersFound": "No containers found", - "noContainersFoundHint": "Start by creating containers on your server", - "searchPlaceholder": "Search by name, image, or ID...", - "filterByStatusPlaceholder": "Filter by status", - "allContainersCount": "All ({{count}})", - "statusCount": "{{status}} ({{count}})", - "noContainersMatchFilters": "No containers match your filters", - "noContainersMatchFiltersHint": "Try adjusting your search or filter", - "containerStarted": "Container {{name}} started", - "failedToStartContainer": "Failed to start container: {{error}}", - "containerStopped": "Container {{name}} stopped", - "failedToStopContainer": "Failed to stop container: {{error}}", - "containerRestarted": "Container {{name}} restarted", - "failedToRestartContainer": "Failed to restart container: {{error}}", - "containerUnpaused": "Container {{name}} unpaused", - "containerPaused": "Container {{name}} paused", - "failedToTogglePauseContainer": "Failed to {{action}} container: {{error}}", - "containerRemoved": "Container {{name}} removed", - "failedToRemoveContainer": "Failed to remove container: {{error}}", - "image": "Image:", - "idLabel": "ID:", - "ports": "Ports:", - "noPorts": "None", - "created": "Created:", "start": "Start", - "stop": "Stop", - "unpause": "Unpause", - "pause": "Pause", - "restart": "Restart", - "remove": "Remove", - "removeContainer": "Remove Container", - "confirmRemoveContainer": "Are you sure you want to remove container \"{{name}}\"?", - "runningContainerWarning": "Warning: This container is currently running and will be force-removed.", - "removing": "Removing:", - "containerNotFound": "Container not found", - "backToList": "Back to list", - "logs": "Logs", - "stats": "Stats", - "consoleTab": "Console", - "failedToFetchLogs": "Failed to fetch logs: {{error}}", - "failedToDownloadLogs": "Failed to download logs: {{error}}", - "linesToShow": "Lines to show", - "last50Lines": "Last 50 lines", - "last100Lines": "Last 100 lines", - "last500Lines": "Last 500 lines", - "last1000Lines": "Last 1000 lines", - "allLogs": "All logs", - "showTimestamps": "Show Timestamps", - "autoRefresh": "Auto Refresh", - "filterLogsPlaceholder": "Filter logs...", - "noLogsAvailable": "No logs available", - "selectMode": "Select", - "exitSelectMode": "Cancel", - "selectedCount": "{{count}} selected", - "bulkMonitoring": "Monitoring", - "bulkFeatures": "Features", - "bulkMoveFolder": "Move to Folder", - "bulkPin": "Pin", - "bulkUnpin": "Unpin", - "enableAllFeatures": "Enable All Features", - "disableAllFeatures": "Disable All Features", "enableStatusCheck": "Enable Status Check", - "disableStatusCheck": "Disable Status Check", "enableMetrics": "Enable Metrics", - "disableMetrics": "Disable Metrics", - "bulkEnableTerminal": "Enable Terminal", - "bulkDisableTerminal": "Disable Terminal", - "bulkEnableTunnel": "Enable Tunnel", - "bulkDisableTunnel": "Disable Tunnel", - "bulkEnableFileManager": "Enable File Manager", - "bulkDisableFileManager": "Disable File Manager", - "bulkEnableDocker": "Enable Docker", - "bulkDisableDocker": "Disable Docker", - "bulkUpdateSuccess": "Updated {{count}} host(s) successfully", "bulkUpdateFailed": "Bulk update failed", - "selectAll": "Select all", - "deselectAll": "Deselect all", - "useGlobalStatusDefault": "Use Global Default (Status)", - "useGlobalMetricsDefault": "Use Global Default (Metrics)", - "connectionType": "Connection Type", - "rdp": "RDP", - "vnc": "VNC", - "telnet": "Telnet", - "ssh": "SSH", - "remoteDesktop": "Remote Desktop", - "remoteDesktopSettings": "Remote Desktop Settings", - "domain": "Domain", - "securityMode": "Security Mode", - "ignoreCert": "Ignore Certificate", - "ignoreCertDesc": "Skip TLS certificate verification for this connection", - "displaySettings": "Display Settings", - "colorDepth": "Color Depth", - "width": "Width", - "height": "Height", - "dpi": "DPI", - "resizeMethod": "Resize Method", - "forceLossless": "Force Lossless", - "audioSettings": "Audio Settings", - "disableAudio": "Disable Audio", - "enableAudioInput": "Enable Audio Input", - "rdpPerformance": "Performance", - "enableWallpaper": "Enable Wallpaper", - "enableTheming": "Enable Theming", - "enableFontSmoothing": "Enable Font Smoothing", - "enableFullWindowDrag": "Enable Full Window Drag", - "enableDesktopComposition": "Enable Desktop Composition", - "enableMenuAnimations": "Enable Menu Animations", - "disableBitmapCaching": "Disable Bitmap Caching", - "disableOffscreenCaching": "Disable Offscreen Caching", - "disableGlyphCaching": "Disable Glyph Caching", - "enableGfx": "Enable GFX", - "deviceRedirection": "Device Redirection", - "enablePrinting": "Enable Printing", - "enableDrive": "Enable Drive Redirection", - "driveName": "Drive Name", - "drivePath": "Drive Path", - "createDrivePath": "Create Drive Path", - "disableDownload": "Disable Download", - "disableUpload": "Disable Upload", - "enableTouch": "Enable Touch", - "rdpSession": "Session", - "clientName": "Client Name", - "consoleSession": "Console Session", - "initialProgram": "Initial Program", - "serverLayout": "Server Keyboard Layout", - "timezone": "Timezone", - "gatewaySettings": "Gateway", - "gatewayHostname": "Gateway Hostname", - "gatewayPort": "Gateway Port", - "gatewayUsername": "Gateway Username", - "gatewayPassword": "Gateway Password", - "gatewayDomain": "Gateway Domain", - "remoteApp": "RemoteApp", - "remoteAppProgram": "Remote Application", - "remoteAppDir": "Remote App Directory", - "remoteAppArgs": "Remote App Arguments", - "clipboardSettings": "Clipboard", - "normalizeClipboard": "Normalize Clipboard", - "disableCopy": "Disable Copy", - "disablePaste": "Disable Paste", - "vncSettings": "VNC Settings", - "cursorMode": "Cursor Mode", - "swapRedBlue": "Swap Red/Blue", - "readOnly": "Read Only", - "recordingSettings": "Recording", - "recordingPath": "Recording Path", - "recordingName": "Recording Name", - "createRecordingPath": "Create Recording Path", - "excludeOutput": "Exclude Output", - "excludeMouse": "Exclude Mouse", - "includeKeys": "Include Keys", - "wakeOnLan": "Wake-on-LAN", - "sendWolPacket": "Send WoL Packet", - "wolMacAddr": "MAC Address", - "wolBroadcastAddr": "Broadcast Address", - "wolUdpPort": "UDP Port", - "wolWaitTime": "Wait Time (seconds)", - "connectionSettings": "Connection Settings", - "rdpOnly": "RDP only", - "vncOnly": "VNC only", - "telnetTerminalSettings": "Terminal Settings", - "terminalType": "Terminal Type", - "guacFontName": "Font Name", - "guacFontSize": "Font Size", - "guacColorScheme": "Color Scheme", - "guacBackspaceKey": "Backspace Key", + "selectAll": "Select All", + "deselectAll": "Deselect All", "protocols": "Protocols", "secureShell": "Secure Shell", "virtualNetwork": "Virtual Network", @@ -1060,6 +266,9 @@ "proxyPort": "Proxy Port", "proxyUsername": "Proxy Username", "proxyPassword": "Proxy Password", + "proxySingleMode": "Single Proxy", + "proxyChainMode": "Proxy Chain", + "you": "You", "jumpHostChainLabel": "Jump Host Chain", "addJumpBtn": "Add Jump", "noJumpHosts": "No jump hosts configured.", @@ -1068,6 +277,13 @@ "authMethod": "Auth Method", "storedCredential": "Stored Credential", "selectACredential": "Select a credential...", + "keyTypeLabel": "Key Type", + "keyTypeAuto": "Auto Detect", + "keyPasteTab": "Paste", + "keyUploadTab": "Upload", + "keyFileLoaded": "Key file loaded", + "keyUploadClick": "Click to upload .pem / .key / .ppk", + "clearKey": "Clear key", "forceKeyboardInteractiveLabel": "Force Keyboard Interactive", "forceKeyboardInteractiveShortDesc": "Force manual password entry even if keys are present", "terminalAppearance": "Terminal Appearance", @@ -1112,6 +328,9 @@ "noTunnelsConfigured": "No tunnels configured.", "tunnelLabel": "Tunnel {{number}}", "tunnelType": "Tunnel Type", + "tunnelModeLocalDesc": "Forward a local port to a port on the remote server (or a host reachable from it).", + "tunnelModeRemoteDesc": "Forward a port on the remote server back to a local port on your machine.", + "tunnelModeDynamicDesc": "Create a SOCKS5 proxy on a local port for dynamic port forwarding.", "endpointHost": "Endpoint Host", "endpointPort": "Endpoint Port", "bindHost": "Bind Host", @@ -1148,11 +367,11 @@ "visibleWidgets": "Visible Widgets", "cpuUsageLabel": "CPU Usage", "cpuUsageDesc": "CPU percent, load averages, sparkline graph", - "memoryLabel": "Memory", + "memoryLabel": "Memory Usage", "memoryDesc": "RAM usage, swap, cached", - "storageLabel": "Storage", + "storageLabel": "Disk Usage", "storageDesc": "Disk usage per mount point", - "networkLabel": "Network", + "networkLabel": "Network Interfaces", "networkDesc": "Interface list and bandwidth", "uptimeLabel": "Uptime", "uptimeDesc": "System uptime and boot time", @@ -1166,96 +385,18 @@ "listeningPortsDesc": "Open ports with process and state", "firewallLabel": "Firewall", "firewallDesc": "Firewall, AppArmor, SELinux status", + "quickActionsLabel": "Quick Actions", "quickActionsToolbar": "Quick actions appear as buttons in the Server Stats toolbar for one-click command execution.", "noQuickActions": "No quick actions yet.", "buttonLabel": "Button label", "selectSnippetPlaceholder": "Select snippet...", "addActionBtn": "Add Action", - "rdpPort": "RDP Port", - "vncPort": "VNC Port", - "telnetPort": "Telnet Port", - "ignoreCertificate": "Ignore Certificate", - "ignoreCertificateDesc": "Allow connections to hosts with self-signed certificates", - "forceLosslessDesc": "Force lossless image encoding (higher quality, more bandwidth)", - "disableAudioDesc": "Mute all audio from the remote session", - "enableAudioInputLabel": "Enable Audio Input (Microphone)", - "enableAudioInputDesc": "Forward local microphone to the remote session", - "wallpaper": "Wallpaper", - "wallpaperDesc": "Show desktop wallpaper (disabling improves performance)", - "theming": "Theming", - "themingDesc": "Enable visual themes and styles", - "fontSmoothing": "Font Smoothing", - "fontSmoothingDesc": "Enable ClearType font rendering", - "fullWindowDrag": "Full Window Drag", - "fullWindowDragDesc": "Show window contents while dragging", - "desktopComposition": "Desktop Composition", - "desktopCompositionDesc": "Enable Aero glass effects", - "menuAnimations": "Menu Animations", - "menuAnimationsDesc": "Enable menu fade and slide animations", - "disableBitmapCachingDesc": "Turn off bitmap cache (may help with glitches)", - "disableOffscreenCachingDesc": "Turn off offscreen cache", - "disableGlyphCachingDesc": "Turn off glyph cache", - "enableGfxDesc": "Use RemoteFX graphics pipeline", - "enablePrintingDesc": "Redirect local printers to the remote session", - "enableDriveRedirection": "Enable Drive Redirection", - "enableDriveRedirectionDesc": "Map a local folder as a drive in the remote session", - "createDrivePathDesc": "Automatically create the folder if it does not exist", - "disableDownloadDesc": "Prevent downloading files from the remote session", - "disableUploadDesc": "Prevent uploading files to the remote session", - "enableTouchDesc": "Enable touch input forwarding", - "sessionLabel": "Session", - "consoleSessionDesc": "Connect to the console (session 0) instead of a new session", - "rdpGateway": "Gateway", - "normalizeLineEndings": "Normalize Line Endings", - "disableCopyDesc": "Prevent copying text from the remote session", - "disablePasteDesc": "Prevent pasting text into the remote session", - "recordingPathLabel": "Recording Path", - "recordingNameLabel": "Recording Name", - "createPathIfMissingDesc": "Automatically create the recording directory", - "excludeOutputDesc": "Do not record screen output (metadata only)", - "excludeMouseDesc": "Do not record mouse movements", - "includeKeystrokesDesc": "Record raw keystrokes in addition to screen output", - "sendWolPacketDesc": "Send a magic packet to wake this host before connecting", - "broadcastAddress": "Broadcast Address", - "udpPort": "UDP Port", - "waitTimeS": "Wait Time (s)", - "vncPassword": "VNC Password", - "vncUsernameOptional": "Username (optional)", - "vncLeaveBlank": "Leave blank if not required", - "swapRedBlueDesc": "Swap the red and blue color channels (fixes some colour issues)", - "readOnlyDesc": "View the remote screen without sending any input", - "telnetPortLabel": "Telnet Port", - "sharingTabSaveFirst": "Save the host first.", - "sharingAvailableAfterSave": "Sharing options are available after the host has been saved.", - "failedToLoadSharingData": "Failed to load sharing data. Check your connection and try again.", - "shareHostSection": "Share Host", - "shareWithUser": "Share with User", - "shareWithRole": "Share with Role", - "selectUser": "Select User", - "selectRole": "Select Role", - "selectUserPlaceholder": "Select a user...", - "selectRolePlaceholder": "Select a role...", - "permissionLevel": "Permission Level", - "expiresInHours": "Expires in (hours)", - "noExpiryPlaceholder": "Leave empty for no expiry", - "shareBtn": "Share", - "currentAccessSection": "Current Access", - "typeCol": "Type", - "targetCol": "Target", - "permissionCol": "Permission", - "grantedByCol": "Granted By", - "expiresCol": "Expires", - "noAccessEntries": "No access entries yet.", - "expiredLabel": "Expired", - "neverExpires": "Never", - "revokeBtn": "Revoke", "hostSharedSuccessfully": "Host shared successfully", "failedToShareHost": "Failed to share host", "accessRevoked": "Access revoked", "failedToRevokeAccess": "Failed to revoke access", "cancelBtn": "Cancel", "savingBtn": "Saving...", - "updateHostBtn": "Update Host", "addHostBtn": "Add Host", "hostUpdated": "Host updated", "hostCreated": "Host created", @@ -1265,42 +406,30 @@ "failedToSaveCredential": "Failed to save credential", "backToHosts": "Back to Hosts", "backToCredentials": "Back to Credentials", - "hostsSection": "Hosts", - "credentialsSection": "Credentials", "pinned": "Pinned", - "checkingStatuses": "Checking host statuses...", "noHostsFound": "No hosts found", "tryDifferentTerm": "Try a different term", "addFirstHost": "Add your first host to get started", "noCredentialsFound": "No credentials found", "addCredentialBtn": "Add Credential", "updateCredentialBtn": "Update Credential", - "selectAll": "Select All", - "deselectAll": "Deselect All", "features": "Features", - "moveFolder": "Move", "noFolder": "(No folder)", "deleteSelected": "Delete", "exitSelection": "Exit selection", "importSkip": "Import (skip existing)", "importOverwrite": "Import (overwrite)", - "exportAllBtn": "Export All", - "downloadSampleBtn": "Download Sample", "collapseBtn": "Collapse", "importExportBtn": "Import / Export", - "hostsExportedSuccessfully": "Hosts exported successfully", - "sampleFileDownloaded": "Sample file downloaded", "hostStatusesRefreshed": "Host statuses refreshed", "failedToRefreshHosts": "Failed to refresh hosts", - "movedHostTo": "Moved {{name}} to \"{{folder}}\"", + "movedHostTo": "Moved {{host}} to \"{{folder}}\"", "failedToMoveHost": "Failed to move host", "folderRenamedTo": "Folder renamed to \"{{name}}\"", "deletedFolder": "Deleted folder \"{{name}}\"", "failedToDeleteFolder": "Failed to delete folder", "deleteAllInFolder": "Delete all hosts in \"{{name}}\"? This cannot be undone.", "deletedHost": "Deleted {{name}}", - "failedToDeleteThisHost": "Failed to delete {{name}}", - "hostCloned": "Host cloned", "copiedToClipboard": "Copied to clipboard", "terminalUrlCopied": "Terminal URL copied", "fileManagerUrlCopied": "File Manager URL copied", @@ -1310,27 +439,14 @@ "copyTerminalUrlAction": "Copy Terminal URL", "copyFileManagerUrlAction": "Copy File Manager URL", "copyRemoteDesktopUrlAction": "Copy Remote Desktop URL", - "deployKeyToHost": "Deploy key to host", - "copyDeployCommandTooltip": "Copy deploy command", - "noPublicKeyAvailable": "No public key available — open the credential editor first", - "deployCommandCopied": "Deploy command copied", "deleteCredentialConfirm": "Delete credential \"{{name}}\"?", "deletedCredential": "Deleted {{name}}", - "failedToDeleteCredentialAction": "Failed to delete credential", "deploySSHKeyTitle": "Deploy SSH Key", - "deployToAuthorizedKeys": "Deploy {{name}} to a host's authorized_keys.", - "selectAHost": "Select a host...", "deployingBtn": "Deploying...", "deployBtn": "Deploy", - "keyDeployedSuccessfully": "Key deployed successfully", "failedToDeployKey": "Failed to deploy key", "deleteHostsConfirm": "Delete {{count}} host{{plural}}? This cannot be undone.", - "deletedHosts": "Deleted {{count}} hosts", - "failedToDeleteSomeHosts": "Failed to delete {{count}} hosts", - "bulkUpdatedHosts": "Updated {{count}} hosts", - "bulkUpdateFailedMsg": "Bulk update failed", "movedToRoot": "Moved to root", - "movedToFolderBulk": "Moved to \"{{folder}}\"", "failedToMoveHosts": "Failed to move hosts", "enableTerminalFeature": "Enable Terminal", "disableTerminalFeature": "Disable Terminal", @@ -1340,13 +456,9 @@ "disableTunnelsFeature": "Disable Tunnels", "enableDockerFeature": "Enable Docker", "disableDockerFeature": "Disable Docker", - "credFriendlyName": "Friendly Name", - "credDescription": "Description", - "credTags": "Tags", "addTagsPlaceholder": "Add tags...", "authDetails": "Authentication Details", "credType": "Type", - "generateKeyPairSection": "Generate Key Pair", "generateKeyPairDesc": "Generate a new key pair — both private and public keys will be filled automatically.", "generatingKey": "Generating...", "generateLabel": "Generate {{label}}", @@ -1358,7 +470,6 @@ "publicKeyCopied": "Public key copied", "keyPairGenerated": "{{label}} key pair generated", "failedToGenerateKeyPair": "Failed to generate key pair", - "filterByStatus": "Filter by status", "searchHostsPlaceholder": "Search hosts, addresses, tags…", "searchCredentialsPlaceholder": "Search credentials…", "refreshBtn": "Refresh", @@ -1366,252 +477,44 @@ "deleteConfirmBtn": "Delete", "tunnelRequirementsText": "The SSH server must have GatewayPorts yes, AllowTcpForwarding yes, and PermitRootLogin yes set in /etc/ssh/sshd_config.", "deleteHostConfirm": "Delete \"{{name}}\"?", - "importComplete": "Import complete: {{msg}}", - "noHostsInFile": "No hosts found in file", - "tooManyHosts": "Cannot import more than 100 hosts at once", - "importFailed2": "Failed to import hosts", "enableAtLeastOneProtocol": "Enable at least one protocol above to configure authentication and connection settings.", - "macAddress": "MAC Address", - "folder": "Folder", - "tags": "Tags", - "themePreview": "Theme Preview", - "username": "Username", - "password": "Password", - "sshPrivateKey": "SSH Private Key", "keyPassphrase": "Key Passphrase", - "requirements": "Requirements", - "tunnelNumber": "Tunnel {{number}}", "connectBtn": "Connect", "disconnectBtn": "Disconnect", "basicInformation": "Basic Information", "authDetailsSection": "Authentication Details", "credTypeLabel": "Type", - "domainLabel": "Domain", - "securityModeLabel": "Security Mode", - "colorDepthLabel": "Color Depth", - "widthLabel": "Width", - "heightLabel": "Height", - "resizeMethodLabel": "Resize Method", - "dpiLabel": "DPI", - "clientNameLabel": "Client Name", - "initialProgramLabel": "Initial Program", - "serverLayoutLabel": "Server Layout", - "timezoneLabel": "Timezone", - "gatewayHostnameLabel": "Gateway Hostname", - "gatewayPortLabel": "Gateway Port", - "gatewayUsernameLabel": "Gateway Username", - "gatewayPasswordLabel": "Gateway Password", - "gatewayDomainLabel": "Gateway Domain", - "remoteAppProgramLabel": "RemoteApp Program", - "workingDirectoryLabel": "Working Directory", - "argumentsLabel": "Arguments", - "vncCursorModeLabel": "Cursor Mode", - "driveNameLabel": "Drive Name", - "drivePathLabel": "Drive Path", - "backBtn": "Back", "hostsTab": "Hosts", "credentialsTab": "Credentials", "selectMultiple": "Select multiple", "connectionLabel": "Connection", "authenticationLabel": "Authentication", - "sharingTab": "Sharing", - "generalTab": "General", - "sshTab": "SSH", - "tunnelsTab": "Tunnels", - "dockerTab": "Docker", - "filesTab": "Files", - "statsTab": "Stats", - "sharingTabLabel": "Sharing", - "none": "None", - "rdpPort": "RDP Port", - "vncPort": "VNC Port", - "telnetPort": "Telnet Port", - "vncPassword": "VNC Password", - "vncUsernameOptional": "Username (optional)", - "vncLeaveBlank": "Leave blank if not required", - "connectionSettings": "Connection Settings", - "ignoreCertLabel": "Ignore Certificate", - "ignoreCertDesc": "Allow connections to hosts with self-signed certificates", - "displaySettings": "Display Settings", - "forceLosslessLabel": "Force Lossless", - "forceLosslessDesc": "Force lossless image encoding (higher quality, more bandwidth)", - "audioSettings": "Audio Settings", - "disableAudioLabel": "Disable Audio", - "disableAudioDesc": "Mute all audio from the remote session", - "enableAudioInputLabel": "Enable Audio Input (Microphone)", - "enableAudioInputDesc": "Forward local microphone to the remote session", - "rdpPerformance": "RDP Performance", - "wallpaperLabel": "Wallpaper", - "wallpaperDesc": "Show desktop wallpaper (disabling improves performance)", - "themingLabel": "Theming", - "themingDesc": "Enable visual themes and styles", - "fontSmoothingLabel": "Font Smoothing", - "fontSmoothingDesc": "Enable ClearType font rendering", - "fullWindowDragLabel": "Full Window Drag", - "fullWindowDragDesc": "Show window contents while dragging", - "desktopCompositionLabel": "Desktop Composition", - "desktopCompositionDesc": "Enable Aero glass effects", - "menuAnimationsLabel": "Menu Animations", - "menuAnimationsDesc": "Enable menu fade and slide animations", - "disableBitmapCachingLabel": "Disable Bitmap Caching", - "disableBitmapCachingDesc": "Turn off bitmap cache (may help with glitches)", - "disableOffscreenCachingLabel": "Disable Offscreen Caching", - "disableOffscreenCachingDesc": "Turn off offscreen cache", - "disableGlyphCachingLabel": "Disable Glyph Caching", - "disableGlyphCachingDesc": "Turn off glyph cache", - "enableGfxLabel": "Enable GFX", - "enableGfxDesc": "Use RemoteFX graphics pipeline", - "deviceRedirection": "Device Redirection", - "enablePrintingLabel": "Enable Printing", - "enablePrintingDesc": "Redirect local printers to the remote session", - "enableDriveLabel": "Enable Drive Redirection", - "enableDriveDesc": "Map a local folder as a drive in the remote session", - "createDrivePathLabel": "Create Drive Path", - "createDrivePathDesc": "Automatically create the folder if it does not exist", - "disableDownloadLabel": "Disable Download", - "disableDownloadDesc": "Prevent downloading files from the remote session", - "disableUploadLabel": "Disable Upload", - "disableUploadDesc": "Prevent uploading files to the remote session", - "enableTouchLabel": "Enable Touch", - "enableTouchDesc": "Enable touch input forwarding", - "sessionSection": "Session", - "consoleSessionLabel": "Console Session", - "consoleSessionDesc": "Connect to the console (session 0) instead of a new session", - "gatewaySection": "Gateway", - "remoteAppSection": "RemoteApp", - "clipboardSection": "Clipboard", - "normalizeLineEndingsLabel": "Normalize Line Endings", - "disableCopyLabel": "Disable Copy", - "disableCopyDesc": "Prevent copying text from the remote session", - "disablePasteLabel": "Disable Paste", - "disablePasteDesc": "Prevent pasting text into the remote session", - "sessionRecording": "Session Recording", - "recordingPathLabel": "Recording Path", - "recordingNameLabel": "Recording Name", - "createPathIfMissingLabel": "Create Path if Missing", - "createPathIfMissingDesc": "Automatically create the recording directory", - "excludeOutputLabel": "Exclude Output", - "excludeOutputDesc": "Do not record screen output (metadata only)", - "excludeMouseLabel": "Exclude Mouse", - "excludeMouseDesc": "Do not record mouse movements", - "includeKeystrokesLabel": "Include Keystrokes", - "includeKeystrokesDesc": "Record raw keystrokes in addition to screen output", - "wakeOnLan": "Wake-on-LAN", - "sendWolPacketLabel": "Send WOL Packet", - "sendWolPacketDesc": "Send a magic packet to wake this host before connecting", - "broadcastAddressLabel": "Broadcast Address", - "udpPortLabel": "UDP Port", - "waitTimeLabel": "Wait Time (s)", - "vncSettingsSection": "VNC Settings", - "swapRedBlueLabel": "Swap Red/Blue", - "swapRedBlueDesc": "Swap the red and blue color channels (fixes some colour issues)", - "readOnlyLabel": "Read-only", - "readOnlyDesc": "View the remote screen without sending any input", - "terminalSettingsSection": "Terminal Settings", - "terminalTypeLabel": "Terminal Type", - "fontNameLabel": "Font Name", - "fontSizeLabel": "Font Size", - "colorSchemeLabel": "Color Scheme", - "backspaceKeyLabel": "Backspace Key", - "saveHostFirst": "Save the host first.", - "sharingOptionsAfterSave": "Sharing options are available after the host has been saved.", - "sharingLoadError": "Failed to load sharing data. Check your connection and try again.", - "shareHostSection": "Share Host", - "shareWithUser": "Share with User", - "shareWithRole": "Share with Role", - "selectUser": "Select User", - "selectRole": "Select Role", - "selectUserOption": "Select a user...", - "selectRoleOption": "Select a role...", - "permissionLevel": "Permission Level", - "expiresInHours": "Expires in (hours)", - "expiresInHoursPlaceholder": "Leave empty for no expiry", - "shareBtn": "Share", - "currentAccess": "Current Access", - "typeHeader": "Type", - "targetHeader": "Target", - "permissionHeader": "Permission", - "grantedByHeader": "Granted By", - "expiresHeader": "Expires", - "noAccessEntries": "No access entries yet.", - "expiredLabel": "Expired", - "neverLabel": "Never", - "revokeBtn": "Revoke", - "cancelBtn": "Cancel", - "savingBtn": "Saving...", - "updateHostBtn": "Update Host", - "addHostBtn": "Add Host", - "updateCredentialBtn": "Update Credential", - "addCredentialBtn": "Add Credential", - "credentialUpdated": "Credential updated", - "credentialCreated": "Credential created", - "failedToSaveCredential": "Failed to save credential", "generateKeyPairTitle": "Generate Key Pair", "generateKeyPairDescription": "Generate a new key pair — both private and public keys will be filled automatically.", - "generatingBtn": "Generating...", - "generateBtn": "Generate {{label}}", - "uploadFileBtn2": "Upload file", "generateFromPrivateKey": "Generate from Private Key", - "hostsTabLabel": "Hosts", - "credentialsTabLabel": "Credentials", - "collapseBtn": "Collapse", "refreshBtn2": "Refresh", - "importExportBtn": "Import / Export", "exitSelectionTitle": "Exit selection", - "importSkipExisting": "Import (skip existing)", - "importOverwrite": "Import (overwrite)", "exportAll": "Export All", - "downloadSample": "Download Sample", "addHostBtn2": "Add Host", "addCredentialBtn2": "Add Credential", "checkingHostStatuses": "Checking host statuses...", "pinnedSection": "Pinned", - "noHostsFound": "No hosts found", - "tryDifferentTerm": "Try a different term", - "addFirstHost": "Add your first host to get started", - "noCredentialsFound": "No credentials found", "hostsExported": "Hosts exported successfully", "sampleDownloaded": "Sample file downloaded", - "hostStatusesRefreshed": "Host statuses refreshed", - "failedToRefreshHosts": "Failed to refresh hosts", - "backToHosts": "Back to Hosts", - "backToCredentials": "Back to Credentials", - "deletedFolder": "Deleted folder \"{{name}}\"", - "failedToDeleteFolder": "Failed to delete folder", - "folderRenamedTo": "Folder renamed to \"{{name}}\"", - "failedToRenameFolder": "Failed to rename folder", - "movedHostTo": "Moved {{host}} to \"{{folder}}\"", - "failedToMoveHost": "Failed to move host", - "deletedHost": "Deleted {{name}}", - "failedToDeleteHost": "Failed to delete {{name}}", - "deletedCredential": "Deleted {{name}}", "failedToDeleteCredential2": "Failed to delete credential", - "deleteAllInFolder": "Delete all hosts in \"{{name}}\"? This cannot be undone.", "noFolderOption": "(No folder)", "nSelected": "{{count}} selected", - "deselectAll": "Deselect All", - "selectAll": "Select All", "featuresMenu": "Features", "moveMenu": "Move", - "deleteSelected": "Delete", "cancelSelection": "Cancel", - "deploySSHKeyTitle": "Deploy SSH Key", "deployDialogDesc": "Deploy {{name}} to a host's authorized_keys.", "targetHostLabel": "Target Host", "selectHostOption": "Select a host...", - "deployingBtn": "Deploying...", - "deployBtn": "Deploy", "keyDeployedSuccess": "Key deployed successfully", "failedToDeployKey2": "Failed to deploy key", "deletedCount": "Deleted {{count}} hosts", "failedToDeleteCount": "Failed to delete {{count}} hosts", "updatedCount": "Updated {{count}} hosts", - "bulkUpdateFailed": "Bulk update failed", - "movedToRoot": "Moved to root", - "movedToFolder": "Moved to \"{{folder}}\"", - "failedToMoveHosts": "Failed to move hosts", - "deleteHostsConfirm": "Delete {{count}} host{{plural}}? This cannot be undone.", - "authTabLabel": "Authentication", "friendlyNameLabel": "Friendly Name", "descriptionLabel": "Description", "loadingHost": "Loading host...", @@ -1792,9 +695,6 @@ }, "guacamole": { "connecting": "Connecting to {{type}} session...", - "rdpConnecting": "Connecting to RDP server...", - "vncConnecting": "Connecting to VNC server...", - "telnetConnecting": "Connecting to Telnet server...", "connectionError": "Connection error", "connectionFailed": "Connection failed", "failedToConnect": "Failed to get connection token", @@ -1804,55 +704,16 @@ "retry": "Retry" }, "terminal": { - "title": "Split Screen", - "none": "None", - "twoSplit": "2-Split", - "threeSplit": "3-Split", - "fourSplit": "4-Split", - "fiveSplit": "5-Split", - "sixSplit": "6-Split", - "availableTabs": "Available Tabs", - "dragTabsHint": "Drag tabs into the grid below to position them", - "layout": "Layout", - "dropHere": "Drop tab here", - "apply": "Apply Split", - "clear": "Clear", - "selectMode": "Select a split mode to get started", - "helpText": "Choose how many tabs you want to display at once", - "error": { - "noAssignments": "Please drag tabs to cells before applying", - "fillAllSlots": "Please fill all {{count}} layout spots before applying" - }, - "success": "Split screen applied", - "cleared": "Split screen cleared" - }, - "terminal": { - "title": "Terminal", "connect": "Connect to Host", - "disconnect": "Disconnect", "clear": "Clear", - "copy": "Copy", "paste": "Paste", - "find": "Find", - "fullscreen": "Fullscreen", - "splitHorizontal": "Split Horizontal", - "splitVertical": "Split Vertical", - "closePanel": "Close Panel", "reconnect": "Reconnect", - "sessionEnded": "Session Ended", - "connectionLost": "Connection Lost", - "error": "ERROR: {{message}}", - "disconnected": "Disconnected", - "connectionClosed": "Connection closed", - "connectionError": "Connection error: {{message}}", + "connectionLost": "Connection lost", "connected": "Connected", "clipboardWriteFailed": "Failed to copy to clipboard. Make sure the page is served over HTTPS or localhost.", "clipboardReadFailed": "Failed to read from clipboard. Make sure clipboard permissions are granted.", "clipboardHttpWarning": "Paste requires HTTPS. Use Ctrl+Shift+V or serve Termix over HTTPS.", - "sshConnected": "SSH connection established", - "authError": "Authentication failed: {{message}}", "unknownError": "Unknown error occurred", - "messageParseError": "Failed to parse server message", "websocketError": "WebSocket connection error", "connecting": "Connecting...", "noHostSelected": "No host selected", @@ -1865,7 +726,6 @@ "tmuxSessionPickerDesc": "Existing tmux sessions found on this host. Select one to reattach or create a new session.", "tmuxWindows": "Windows", "tmuxWindowCount": "{{count}} window", - "tmuxWindowCount_other": "{{count}} windows", "tmuxAttached": "Attached clients", "tmuxAttachedCount": "{{count}} attached", "tmuxLastActivity": "Last activity", @@ -1878,8 +738,6 @@ "tmuxDetach": "Detach from tmux session", "tmuxDetached": "Detached from tmux session", "maxReconnectAttemptsReached": "Maximum reconnection attempts reached", - "connectionLost": "Connection lost", - "reconnect": "Reconnect", "closeTab": "Close", "connectionTimeout": "Connection timeout", "terminalTitle": "Terminal - {{host}}", @@ -1887,59 +745,37 @@ "runTitle": "Running {{command}} - {{host}}", "totpRequired": "Two-Factor Authentication Required", "totpCodeLabel": "Verification Code", - "totpPlaceholder": "000000", "totpVerify": "Verify", "warpgateAuthRequired": "Warpgate Authentication Required", "warpgateSecurityKey": "Security Key", "warpgateAuthUrl": "Authentication URL", "warpgateOpenBrowser": "Open in Browser", "warpgateContinue": "I've Completed Authentication", - "warpgateTimeout": "Warpgate authentication timeout. Please reconnect.", "opksshAuthRequired": "OPKSSH Authentication Required", "opksshAuthDescription": "Complete authentication in your browser to continue. This session will remain valid for 24 hours.", - "opksshAuthUrl": "Authentication URL", "opksshOpenBrowser": "Open Browser to Authenticate", "opksshWaitingForAuth": "Waiting for authentication in browser...", "opksshAuthenticating": "Processing authentication...", "opksshTimeout": "Authentication timed out. Please try again.", "opksshAuthFailed": "Authentication failed. Please check your credentials and try again.", - "opksshConfigMissing": "OPKSSH configuration not found. Please create ~/.opk/config.yml with your OIDC provider settings. See documentation: https://github.com/openpubkey/opkssh#configuration", "opksshSignInWith": "Sign in with {{provider}}", "sudoPasswordPopupTitle": "Insert Password?", - "sudoPasswordPopupHint": "Press Enter to insert, Esc to dismiss", - "sudoPasswordPopupConfirm": "Insert", - "sudoPasswordPopupDismiss": "Dismiss", "websocketAbnormalClose": "Connection closed unexpectedly. This may be due to a reverse proxy or SSL configuration issue. Please check server logs.", - "totpInvalid": "Invalid code. Please try again.", - "authMethodFallback": "{{method}} authentication failed. Trying alternative method...", - "authenticationRequired": "Authentication required", "connectionLogTitle": "Connection Log", "connectionLogCopy": "Copy logs to clipboard", - "connectionLogClear": "Clear logs", "connectionLogEmpty": "No connection logs yet", - "connectionLogConnecting": "Connecting to host...", "connectionLogWaiting": "Waiting for connection logs...", "connectionLogCopied": "Connection logs copied to clipboard", "connectionLogCopyFailed": "Failed to copy logs to clipboard", - "allAuthMethodsFailed": "All authentication methods failed. Please check your credentials.", - "methodNotSupported": "Server does not support {{method}} authentication. Available methods: {{available}}", - "retryWithMethod": "Retry with {{method}}", - "automaticFallback": "Automatically trying {{method}} authentication...", - "totpTimeout": "TOTP verification timeout. Please reconnect.", - "passwordTimeout": "Password verification timeout. Please reconnect.", - "sessionEnded": "Session ended.", "connectionRejected": "Connection rejected by server. Please check your authentication and network configuration.", "hostKeyRejected": "SSH host key verification rejected. Connection cancelled.", - "sessionTakenOver": "Session was opened in another tab. Reconnecting...", - "sessionAttachTimeout": "Session attachment timed out. Creating new connection...", - "openFileManagerHere": "Open File Manager Here" + "sessionTakenOver": "Session was opened in another tab. Reconnecting..." }, "fileManager": { - "title": "File Manager", "noHostSelected": "No host selected", + "initializingEditor": "Initializing editor...", "file": "File", "folder": "Folder", - "connectToSsh": "Connect to SSH to use file operations", "uploadFile": "Upload File", "downloadFile": "Download", "extractArchive": "Extract Archive", @@ -1968,52 +804,19 @@ "newFile": "New File", "newFolder": "New Folder", "rename": "Rename", - "renameItem": "Rename Item", - "deleteItem": "Delete Item", - "currentPath": "Current Path", - "uploadFileTitle": "Upload File", - "maxFileSize": "Max: 1GB (JSON) / 5GB (Binary) - Large files supported", - "removeFile": "Remove File", - "clickToSelectFile": "Click to select a file", - "chooseFile": "Choose File", "uploading": "Uploading...", - "downloading": "Downloading...", "uploadingFile": "Uploading {{name}}...", - "uploadingLargeFile": "Uploading large file {{name}} ({{size}})...", - "downloadingFile": "Downloading {{name}}...", - "creatingFile": "Creating {{name}}...", - "creatingFolder": "Creating {{name}}...", - "deletingItem": "Deleting {{type}} {{name}}...", - "renamingItem": "Renaming {{type}} {{oldName}} to {{newName}}...", - "createNewFile": "Create New File", "fileName": "File Name", - "creating": "Creating...", - "createFile": "Create File", - "createNewFolder": "Create New Folder", "folderName": "Folder Name", - "createFolder": "Create Folder", - "warningCannotUndo": "Warning: This action cannot be undone", - "itemPath": "Item Path", - "thisIsDirectory": "This is a directory (will delete recursively)", - "deleting": "Deleting...", - "currentPathLabel": "Current Path", - "newName": "New Name", - "thisIsDirectoryRename": "This is a directory", - "renaming": "Renaming...", "fileUploadedSuccessfully": "File \"{{name}}\" uploaded successfully", "failedToUploadFile": "Failed to upload file", "fileDownloadedSuccessfully": "File \"{{name}}\" downloaded successfully", "failedToDownloadFile": "Failed to download file", - "noFileContent": "No file content received", - "filePath": "File Path", "fileCreatedSuccessfully": "File \"{{name}}\" created successfully", - "failedToCreateFile": "Failed to create file", "folderCreatedSuccessfully": "Folder \"{{name}}\" created successfully", - "failedToCreateFolder": "Failed to create folder", "failedToCreateItem": "Failed to create item", "operationFailed": "{{operation}} operation failed for {{name}}: {{error}}", "failedToResolveSymlink": "Failed to resolve symlink", - "itemDeletedSuccessfully": "{{type}} deleted successfully", "itemsDeletedSuccessfully": "{{count}} items deleted successfully", "failedToDeleteItems": "Failed to delete items", "sudoPasswordRequired": "Administrator Password Required", @@ -2021,19 +824,15 @@ "sudoPassword": "Sudo password", "sudoOperationFailed": "Sudo operation failed", "sudoAuthFailed": "Sudo authentication failed", - "deleteOperation": "Delete files/folders", "dragFilesToUpload": "Drop files here to upload", "emptyFolder": "This folder is empty", - "itemCount": "{{count}} items", - "selectedCount": "{{count}} selected", "searchFiles": "Search files...", "upload": "Upload", "selectHostToStart": "Select a host to start file management", + "sshRequiredForFileManager": "File manager requires SSH. This host does not have SSH enabled.", "failedToConnect": "Failed to connect to SSH", "failedToLoadDirectory": "Failed to load directory", "noSSHConnection": "No SSH connection available", - "enterFolderName": "Enter folder name:", - "enterFileName": "Enter file name:", "copy": "Copy", "cut": "Cut", "paste": "Paste", @@ -2061,67 +860,25 @@ "modified": "Modified", "path": "Path", "confirmDelete": "Are you sure you want to delete {{name}}?", - "uploadSuccess": "File uploaded successfully", - "uploadFailed": "File upload failed", - "downloadSuccess": "File downloaded successfully", - "downloadFailed": "File download failed", "permissionDenied": "Permission denied", - "checkDockerLogs": "Check the Docker logs for detailed error information", - "internalServerError": "Internal server error occurred", "serverError": "Server Error", - "error": "Error", - "requestFailed": "Request failed with status code", - "unknownFileError": "unknown", - "cannotReadFile": "Cannot read file", - "noSshSessionId": "No SSH session ID available", - "noFilePath": "No file path available", - "noCurrentHost": "No current host available", "fileSavedSuccessfully": "File saved successfully", - "saveTimeout": "Save operation timed out. The file may have been saved successfully, but the operation took too long to complete. Check the Docker logs for confirmation.", "failedToSaveFile": "Failed to save file", - "deletedSuccessfully": "deleted successfully", - "connectToServer": "Connect to a Server", - "selectServerToEdit": "Select a server from the sidebar to start editing files", - "fileOperations": "File Operations", - "confirmDeleteMessage": "Are you sure you want to delete {{name}}?", "confirmDeleteSingleItem": "Are you sure you want to permanently delete \"{{name}}\"?", "confirmDeleteMultipleItems": "Are you sure you want to permanently delete {{count}} items?", "confirmDeleteMultipleItemsWithFolders": "Are you sure you want to permanently delete {{count}} items? This includes folders and their contents.", "confirmDeleteFolder": "Are you sure you want to permanently delete the folder \"{{name}}\" and all its contents?", - "deleteDirectoryWarning": "This will delete the folder and all its contents.", - "actionCannotBeUndone": "This action cannot be undone.", "permanentDeleteWarning": "This action cannot be undone. The item(s) will be permanently deleted from the server.", "recent": "Recent", "pinned": "Pinned", "folderShortcuts": "Folder Shortcuts", - "noRecentFiles": "No recent files.", - "noPinnedFiles": "No pinned files.", - "enterFolderPath": "Enter folder path", - "noShortcuts": "No shortcuts.", - "searchFilesAndFolders": "Search files and folders...", - "noFilesOrFoldersFound": "No files or folders found.", - "failedToConnectSSH": "Failed to connect to SSH", "failedToReconnectSSH": "Failed to reconnect SSH session", - "failedToListFiles": "Failed to list files", - "fetchHomeDataTimeout": "Fetch home data timed out", - "sshStatusCheckTimeout": "SSH status check timed out", - "sshReconnectionTimeout": "SSH reconnection timed out", - "saveOperationTimeout": "Save operation timed out", - "cannotSaveFile": "Cannot save file", - "dragSystemFilesToUpload": "Drag system files here to upload", - "dragFilesToWindowToDownload": "Drag files outside window to download", "openTerminalHere": "Open Terminal Here", "run": "Run", - "saveToSystem": "Save as...", - "selectLocationToSave": "Select Location to Save", "openTerminalInFolder": "Open Terminal in This Folder", "openTerminalInFileLocation": "Open Terminal at File Location", - "terminalWithPath": "Terminal - {{host}}:{{path}}", "runningFile": "Running - {{file}}", "onlyRunExecutableFiles": "Can only run executable files", - "noHostSelected": "No host selected", - "starred": "Starred", - "shortcuts": "Shortcuts", "directories": "Directories", "removedFromRecentFiles": "Removed \"{{name}}\" from recent files", "removeFailed": "Remove failed", @@ -2135,10 +892,8 @@ "clearAllRecentFiles": "Clear all recent files", "unpinFile": "Unpin file", "removeShortcut": "Remove shortcut", - "saveFilesToSystem": "Save {{count}} files as...", "pinFile": "Pin file", "addToShortcuts": "Add to shortcuts", - "downloadToDefaultLocation": "Download to default location", "pasteFailed": "Paste failed", "noUndoableActions": "No undoable actions", "undoCopySuccess": "Undid copy operation: Deleted {{count}} copied files", @@ -2151,20 +906,13 @@ "undoTypeNotSupported": "Unsupported undo operation type", "undoOperationFailed": "Undo operation failed", "unknownError": "Unknown error", - "enterPath": "Enter path...", - "editPath": "Edit path", "confirm": "Confirm", - "cancel": "Cancel", "find": "Find...", - "replaceWith": "Replace with...", "replace": "Replace", - "replaceAll": "Replace All", "downloadInstead": "Download Instead", "keyboardShortcuts": "Keyboard Shortcuts", "searchAndReplace": "Search & Replace", "editing": "Editing", - "navigation": "Navigation", - "code": "Code", "search": "Search", "findNext": "Find Next", "findPrevious": "Find Previous", @@ -2172,16 +920,11 @@ "selectAll": "Select All", "undo": "Undo", "redo": "Redo", - "goToLine": "Go to Line", "moveLineUp": "Move Line Up", "moveLineDown": "Move Line Down", "toggleComment": "Toggle Comment", - "indent": "Indent", - "outdent": "Outdent", "autoComplete": "Auto Complete", "imageLoadError": "Failed to load image", - "rotate": "Rotate", - "originalSize": "Original Size", "startTyping": "Start typing...", "unknownSize": "Unknown size", "fileIsEmpty": "File is empty", @@ -2233,9 +976,7 @@ "authenticationFailed": "Authentication failed", "verificationCodePrompt": "Verification code:", "changePermissions": "Change Permissions", - "changePermissionsDesc": "Modify file permissions for", "currentPermissions": "Current Permissions", - "newPermissions": "New Permissions", "owner": "Owner", "group": "Group", "others": "Others", @@ -2264,24 +1005,12 @@ "toggleSidebar": "Toggle Sidebar" }, "tunnels": { - "title": "SSH Tunnels", - "noTunnelsConfigured": "No Tunnels Configured", - "configureTunnelsInHostSettings": "Configure tunnel connections in the Host Manager to get started", "noSshTunnels": "No SSH Tunnels", "createFirstTunnelMessage": "You haven't created any SSH tunnels yet. Configure tunnel connections in the Host Manager to get started.", "connected": "Connected", "disconnected": "Disconnected", "connecting": "Connecting...", - "disconnecting": "Disconnecting...", - "unknownTunnelStatus": "Unknown", - "statusUnknown": "Unknown", - "unknown": "Unknown", "error": "Error", - "failed": "Failed", - "retrying": "Retrying", - "waiting": "Waiting", - "waitingForRetry": "Waiting for retry", - "retryingConnection": "Retrying connection", "canceling": "Canceling...", "connect": "Connect", "disconnect": "Disconnect", @@ -2292,38 +1021,19 @@ "currentHostPort": "Current Host Port", "endpointPort": "Endpoint Port", "bindIp": "Local IP", - "currentHostIp": "Current Host IP", "endpointSshConfig": "Endpoint SSH Configuration", - "endpointSshConfigRequired": "Endpoint SSH configuration is required", "endpointSshHost": "Endpoint SSH Host", "endpointSshHostPlaceholder": "Select a configured host", "endpointSshHostRequired": "Select an endpoint SSH host for each client tunnel.", "attempt": "Attempt {{current}} of {{max}}", "nextRetryIn": "Next retry in {{seconds}} seconds", - "checkDockerLogs": "Check your Docker logs for the error reason, join the", - "orCreate": "or create a ", - "noTunnelConnections": "No tunnel connections configured", - "tunnelConnections": "Tunnel Connections", - "serverTunnels": "Server Tunnels", - "serverTunnelsDesc": "Backend-managed tunnels stored with this host.", "clientTunnels": "Client Tunnels", - "clientTunnelsUnavailable": "Client tunnels require a desktop client.", - "serverTunnel": "Server Tunnel", "clientTunnel": "Client Tunnel", - "addServerTunnel": "Add Server Tunnel", "addClientTunnel": "Add Client Tunnel", - "noServerTunnels": "No server tunnels configured.", "noClientTunnels": "No client tunnels configured on this desktop.", - "manageClientTunnels": "Manage Client Tunnels", - "addTunnel": "Add Tunnel", - "editTunnel": "Edit Tunnel", - "deleteTunnel": "Delete Tunnel", "tunnelName": "Tunnel Name", "remoteHost": "Remote Host", "autoStart": "Auto Start", - "autoStartContainer": "Auto Start on Launch", - "autoStartContainerDesc": "Automatically start this tunnel when your Termix server launches.", - "autoStartEnableFailed": "Host saved, but failed to start auto-start tunnels for {{name}}.", "clientAutoStartDesc": "Starts when this desktop client opens and stays connected.", "clientManualStartDesc": "Use Start and Stop from this row. Termix will not open it automatically.", "clientRemoteServerNote": "Remote forwarding may require AllowTcpForwarding and GatewayPorts on the endpoint SSH server. The remote port closes when this desktop disconnects.", @@ -2335,22 +1045,16 @@ "localSaveError": "Failed to save local client tunnels", "invalidBindIp": "Local IP must be a valid IPv4 address.", "invalidLocalTargetIp": "Local target IP must be a valid IPv4 address.", - "invalidCurrentHostIp": "Current Host IP must be a valid IPv4 address.", "invalidLocalPort": "Local port must be between 1 and 65535.", "invalidRemotePort": "Remote port must be between 1 and 65535.", "invalidLocalTargetPort": "Local target port must be between 1 and 65535.", "invalidEndpointPort": "Endpoint port must be between 1 and 65535.", "duplicateAutoStartBind": "Only one auto-start client tunnel can use {{bind}}.", "manualControlError": "Failed to update tunnel state.", - "saveHostBeforeManualControl": "Save this host before starting or stopping its server tunnels.", - "status": "Status", "active": "Active", - "inactive": "Inactive", "start": "Start", "stop": "Stop", "test": "Test", - "restart": "Restart", - "connectionType": "Connection Type", "type": "Tunnel Type", "typeLocal": "Local (-L)", "typeRemote": "Remote (-R)", @@ -2383,13 +1087,6 @@ "retryIntervalDescription": "Time to wait between retry attempts.", "local": "Local", "remote": "Remote", - "dynamic": "Dynamic", - "unknownConnectionStatus": "Unknown", - "portMapping": "Port {{sourcePort}} → {{endpointHost}}:{{endpointPort}}", - "endpointHostNotFound": "Endpoint host not found", - "discord": "Discord", - "githubIssue": "GitHub issue", - "forHelp": "for help", "destination": "Destination", "host": "Host", "mode": "Mode", @@ -2397,82 +1094,36 @@ "working": "Working..." }, "serverStats": { - "title": "Server Statistics", "cpu": "CPU", "memory": "Memory", "disk": "Disk", "network": "Network", "uptime": "Uptime", - "loadAverage": "Avg: {{avg1}}, {{avg5}}, {{avg15}}", "processes": "Processes", - "connections": "Connections", - "usage": "Usage", "available": "Available", - "total": "Total", "free": "Free", - "used": "Used", - "percentage": "Percentage", - "refreshStatusAndMetrics": "Refresh status and metrics", - "refreshStatus": "Refresh Status", - "fileManagerAlreadyOpen": "File Manager already open for this host", - "openFileManager": "Open File Manager", "connecting": "Connecting...", "connectionFailed": "Failed to connect to server", - "cpuCores_one": "{{count}} CPU", - "cpuCores_other": "{{count}} CPUs", "naCpus": "N/A CPU(s)", - "loadAverageNA": "Avg: N/A", "cpuUsage": "CPU Usage", "memoryUsage": "Memory Usage", "diskUsage": "Disk Usage", - "rootStorageSpace": "Root Storage Space", - "of": "of", - "feedbackMessage": "Have ideas for what should come next for server management? Share them on", "failedToFetchHostConfig": "Failed to fetch host configuration", - "failedToFetchStatus": "Failed to fetch server status", - "failedToFetchMetrics": "Failed to fetch server metrics", - "failedToFetchHomeData": "Failed to fetch home data", - "loadingMetrics": "Loading metrics...", - "connecting": "Connecting...", - "refreshing": "Refreshing...", "serverOffline": "Server Offline", "cannotFetchMetrics": "Cannot fetch metrics from offline server", - "totpRequired": "TOTP Authentication Required", - "totpUnavailable": "Server Stats unavailable for TOTP-enabled servers", - "totpVerified": "TOTP verified, metrics collection started", "totpFailed": "TOTP verification failed", - "totpInvalidCode": "Invalid verification code", - "totpCancelled": "Metrics collection cancelled", - "authenticationFailed": "Authentication failed", "noneAuthNotSupported": "Server Stats does not support 'none' authentication type.", "load": "Load", - "editLayout": "Edit Layout", - "cancelEdit": "Cancel", - "addWidget": "Add Widget", - "saveLayout": "Save Layout", - "unsavedChanges": "Unsaved changes", - "layoutSaved": "Layout saved successfully", - "failedToSaveLayout": "Failed to save layout", "systemInfo": "System Information", "hostname": "Hostname", "operatingSystem": "Operating System", "kernel": "Kernel", - "totalUptime": "Total Uptime", "seconds": "seconds", "networkInterfaces": "Network Interfaces", "noInterfacesFound": "No network interfaces found", - "totalProcesses": "Total Processes", - "running": "Running", "noProcessesFound": "No processes found", "loginStats": "SSH Login Statistics", - "totalLogins": "Total Logins", - "uniqueIPs": "Unique IPs", - "recentSuccessfulLogins": "Recent Successful Logins", - "recentFailedAttempts": "Recent Failed Attempts", "noRecentLoginData": "No recent login data", - "from": "from", - "quickActions": "Quick Actions", - "executeQuickAction": "Execute {{name}}", "executingQuickAction": "Executing {{name}}...", "quickActionSuccess": "{{name}} completed successfully", "quickActionFailed": "{{name}} failed", @@ -2482,90 +1133,39 @@ "protocol": "Protocol", "port": "Port", "address": "Address", - "state": "State", "process": "Process", "noData": "No listening ports data" }, "firewall": { "title": "Firewall", - "active": "Active", "inactive": "Inactive", - "notDetected": "Not Detected", "policy": "Policy", "rules": "rules", - "noRules": "No rules", "noData": "No firewall data available", "action": "Action", "protocol": "Proto", "port": "Port", "source": "Source", - "accept": "ACCEPT", - "drop": "DROP", - "reject": "REJECT", "anywhere": "Anywhere" }, "loadAvg": "Load Avg", "swap": "Swap", "architecture": "Architecture", - "refresh": "Refresh", - "online": "Online", - "offline": "Offline", - "liveMetrics": "Live Metrics" + "refresh": "Refresh" }, "auth": { "tagline": "SSH SERVER MANAGER", - "description": "Secure, powerful, and intuitive SSH connection management", - "welcomeBack": "Welcome back to TERMIX", - "createAccount": "Create your TERMIX account", - "continueExternal": "Continue with external provider", "loginTitle": "Login to Termix", "registerTitle": "Create Account", - "loginButton": "Login", - "registerButton": "Register", "forgotPassword": "Forgot Password?", "rememberMe": "Remember Device for 30 Days (includes TOTP)", "noAccount": "Don't have an account?", "hasAccount": "Already have an account?", - "loginSuccess": "Login successful", - "loginFailed": "Login failed", - "registerSuccess": "Registration successful", - "registerFailed": "Registration failed", - "logoutSuccess": "Logged out successfully", - "invalidCredentials": "Invalid username or password", - "accountCreated": "Account created successfully", - "passwordReset": "Password reset link sent", "twoFactorAuth": "Two-Factor Authentication", "enterCode": "Enter verification code", "backupCode": "Or use backup code", "verifyCode": "Verify Code", "redirectingToApp": "Redirecting to app...", - "enableTwoFactor": "Enable Two-Factor Authentication", - "disableTwoFactor": "Disable Two-Factor Authentication", - "scanQRCode": "Scan this QR code with your authenticator app", - "backupCodes": "Backup Codes", - "saveBackupCodes": "Save these backup codes in a safe place", - "twoFactorEnabledSuccess": "Two-factor authentication enabled successfully!", - "twoFactorDisabled": "Two-factor authentication disabled", - "newBackupCodesGenerated": "New backup codes generated", - "backupCodesDownloaded": "Backup codes downloaded", - "pleaseEnterSixDigitCode": "Please enter a 6-digit code", - "invalidVerificationCode": "Invalid verification code", - "failedToDisableTotp": "Failed to disable TOTP", - "failedToGenerateBackupCodes": "Failed to generate backup codes", - "enterPassword": "Enter your password", - "lockedOidcAuth": "Locked (OIDC Auth)", - "twoFactorTitle": "Two-Factor Authentication", - "twoFactorProtected": "Your account is protected with two-factor authentication", - "twoFactorActive": "Two-factor authentication is currently active on your account", - "disable2FA": "Disable 2FA", - "disableTwoFactorWarning": "Disabling two-factor authentication will make your account less secure", - "passwordOrTotpCode": "Password or TOTP Code", - "or": "Or", - "generateNewBackupCodesText": "Generate new backup codes if you've lost your existing ones", - "generateNewBackupCodes": "Generate New Backup Codes", - "yourBackupCodes": "Your Backup Codes", - "download": "Download", - "setupTwoFactorTitle": "Set Up Two-Factor Authentication", "sshAuthenticationRequired": "SSH Authentication Required", "sshNoKeyboardInteractive": "Keyboard-Interactive Authentication Unavailable", "sshAuthenticationFailed": "Authentication Failed", @@ -2578,22 +1178,7 @@ "sshKeyPasswordDescription": "If your SSH key is encrypted, enter the passphrase here.", "passphraseRequired": "Passphrase Required", "passphraseRequiredDescription": "The SSH key is encrypted. Please enter the passphrase to unlock it.", - "step1ScanQR": "Step 1: Scan the QR code with your authenticator app", - "manualEntryCode": "Manual Entry Code", - "cannotScanQRText": "If you can't scan the QR code, enter this code manually in your authenticator app", - "nextVerifyCode": "Next: Verify Code", - "verifyAuthenticator": "Verify Your Authenticator", - "step2EnterCode": "Step 2: Enter the 6-digit code from your authenticator app", - "verificationCode": "Verification Code", "back": "Back", - "verifyAndEnable": "Verify and Enable", - "saveBackupCodesTitle": "Save Your Backup Codes", - "step3StoreCodesSecurely": "Step 3: Store these codes in a safe place", - "importantBackupCodesText": "Save these backup codes in a secure location. You can use them to access your account if you lose your authenticator device.", - "completeSetup": "Complete Setup", - "notEnabledText": "Two-factor authentication adds an extra layer of security by requiring a code from your authenticator app when signing in.", - "enableTwoFactorButton": "Enable Two-Factor Authentication", - "addExtraSecurityLayer": "Add an extra layer of security to your account", "firstUser": "First User", "firstUserMessage": "You are the first user and will be made an admin. You can view admin settings in the sidebar user dropdown. If you think this is a mistake, check the docker logs, or create a GitHub issue.", "external": "External", @@ -2606,23 +1191,17 @@ "resetCode": "Reset Code", "verifyCodeButton": "Verify Code", "enterResetCode": "Enter the 6-digit code from the docker container logs for user:", - "goToLogin": "Go to Login", "newPassword": "New Password", "confirmNewPassword": "Confirm Password", "enterNewPassword": "Enter your new password for user:", "signUp": "Sign Up", - "mobileApp": "Mobile App", - "loggingInToMobileApp": "Logging in to the mobile app", "desktopApp": "Desktop App", "loggingInToDesktopApp": "Logging in to the desktop app", - "loggingInToDesktopAppViaWeb": "Logging in to the desktop app via web interface", "loadingServer": "Loading server...", - "authenticating": "Authenticating...", "dataLossWarning": "Resetting your password this way will delete all your saved SSH hosts, credentials, and other encrypted data. This action cannot be undone. Only use this if you have forgotten your password and are not logged in.", "authenticationDisabled": "Authentication Disabled", "authenticationDisabledDesc": "All authentication methods are currently disabled. Please contact your administrator.", - "passwordResetSuccess": "Password Reset Successful", - "passwordResetSuccessDesc": "Your password has been reset successfully. You can now log in with your new password." + "attemptsRemaining": "{{count}} attempts remaining" }, "hostKey": { "verifyNewHost": "Verify SSH Host Key", @@ -2639,11 +1218,6 @@ "acceptNewKey": "Accept New Key & Continue" }, "errors": { - "notFound": "Page not found", - "unauthorized": "Unauthorized access", - "forbidden": "Access forbidden", - "serverError": "Server error", - "networkError": "Network error", "databaseConnection": "Could not connect to the database", "unknownError": "Unknown error", "loginFailed": "Login failed", @@ -2654,46 +1228,23 @@ "failedOidcLogin": "Failed to start OIDC login", "failedUserInfo": "Failed to get user info after login", "oidcAuthFailed": "OIDC authentication failed", - "noTokenReceived": "No token received from login", "invalidAuthUrl": "Invalid authorization URL received from backend", - "invalidInput": "Invalid input", "requiredField": "This field is required", "minLength": "Minimum length is {{min}}", - "maxLength": "Maximum length is {{max}}", - "invalidEmail": "Invalid email address", "passwordMismatch": "Passwords do not match", "passwordLoginDisabled": "Username/password login is currently disabled", - "weakPassword": "Password is too weak", - "usernameExists": "Username already exists", - "emailExists": "Email already exists", - "loadFailed": "Failed to load data", - "saveError": "Failed to save", "sessionExpired": "Session expired - please log in again", "totpRateLimited": "Rate limited: Too many TOTP verification attempts. Please try again later.", "totpRateLimitedWithTime": "Rate limited: Too many TOTP verification attempts. Please wait {{time}} seconds before trying again.", "resetCodeRateLimited": "Rate limited: Too many verification attempts. Please try again later.", - "resetCodeRateLimitedWithTime": "Rate limited: Too many verification attempts. Please wait {{time}} seconds before trying again." + "resetCodeRateLimitedWithTime": "Rate limited: Too many verification attempts. Please wait {{time}} seconds before trying again.", + "authTokenSaveFailed": "Failed to save authentication token", + "failedToLoadServer": "Failed to load server" }, "messages": { - "saveSuccess": "Saved successfully", - "saveError": "Failed to save", - "deleteSuccess": "Deleted successfully", - "deleteError": "Failed to delete", - "updateSuccess": "Updated successfully", - "updateError": "Failed to update", - "copySuccess": "Copied to clipboard", - "copyError": "Failed to copy", - "copiedToClipboard": "{{item}} copied to clipboard", - "connectionEstablished": "Connection established", - "connectionClosed": "Connection closed", - "reconnecting": "Reconnecting...", - "processing": "Processing...", - "pleaseWait": "Please wait...", "registrationDisabled": "New account registration is currently disabled by an admin. Please log in or contact an administrator.", "userNotAllowed": "Your account is not authorized to register. Please contact an administrator.", - "databaseConnected": "Database connected successfully", "databaseConnectionFailed": "Failed to connect to the database server", - "checkServerConnection": "Please check your server connection and try again", "resetCodeSent": "Reset code sent to Docker logs", "codeVerified": "Code verified successfully", "passwordResetSuccess": "Password reset successfully", @@ -2701,33 +1252,6 @@ "registrationSuccess": "Registration successful" }, "profile": { - "title": "User Profile", - "description": "Manage your account settings and security", - "security": "Security", - "changePassword": "Change Password", - "twoFactorAuth": "Two-Factor Authentication", - "accountInfo": "Account Information", - "role": "Role", - "admin": "Administrator", - "user": "User", - "authMethod": "Authentication Method", - "local": "Local", - "external": "External (OIDC)", - "externalAndLocal": "Dual Auth", - "selectPreferredLanguage": "Select your preferred language for the interface", - "fileColorCoding": "File Color Coding", - "fileColorCodingDesc": "Color-code files by type: folders (red), files (blue), symlinks (green)", - "commandAutocomplete": "Command Autocomplete", - "commandAutocompleteDesc": "Enable Tab key autocomplete suggestions for terminal commands based on your command history", - "commandHistoryTracking": "Save Command History", - "commandHistoryTrackingDesc": "Store terminal commands in history for autocomplete and sidebar. Disabled by default for privacy.", - "defaultSnippetFoldersCollapsed": "Collapse Snippet Folders by Default", - "defaultSnippetFoldersCollapsedDesc": "When enabled, all snippet folders will be collapsed when you open the snippets tab", - "terminalSyntaxHighlighting": "Terminal Syntax Highlighting", - "showHostTags": "Show Host Tags", - "showHostTagsDesc": "Display tags under each host in the sidebar. Disable to hide all tags.", - "account": "Account", - "appearance": "Appearance", "c2sTunnelConfigDesc": "Local desktop tunnels targeting configured SSH hosts.", "c2sTunnelPresets": "Client Tunnel Presets", "c2sTunnelPresetsDesc": "Save this desktop client's local tunnel list as a named server preset, or load a preset back into this client.", @@ -2744,96 +1268,20 @@ "c2sPresetLoaded": "Client tunnel preset loaded locally", "c2sPresetRenamed": "Client tunnel preset renamed", "c2sPresetDeleted": "Client tunnel preset deleted", - "c2sPresetLoadError": "Failed to load client tunnel presets", - "languageLocalization": "Language & Localization", - "fileManagerSettings": "File Manager", - "terminalSettings": "Terminal", - "hostSidebarSettings": "Host & Sidebar", - "snippetsSettings": "Snippets", - "confirmSnippetExecution": "Confirm Snippet Execution", - "confirmSnippetExecutionDesc": "Show confirmation dialog before executing snippets", - "updateSettings": "Updates", - "disableUpdateCheck": "Disable Update Check", - "disableUpdateCheckDesc": "Stop checking for new versions on startup and dashboard. Reduces network requests.", - "currentPassword": "Current Password", - "passwordChangedSuccess": "Password changed successfully! Please log in again.", - "failedToChangePassword": "Failed to change password. Please check your current password and try again.", - "theme": "Theme", - "themeLight": "Light", - "themeDark": "Dark", - "themeSystem": "System", - "appearanceDesc": "Select the color theme for the application", - "accentColor": "Accent Color", - "customColor": "Custom:", - "fontSize": "Font Size", - "fontSizeDesc": "Adjust the base font size across the application", - "terminalSyntaxHighlightingDesc": "Automatically highlight commands, paths, IPs, and log levels in terminal output", - "enableCommandPaletteShortcut": "Enable Command Palette Shortcut", - "enableCommandPaletteShortcutDesc": "Double-tap left Shift to open the Command Palette for quick access to hosts", - "enableTerminalSessionPersistence": "Persistent Tabs/Sessions", - "enableTerminalSessionPersistenceDesc": "Maintain SSH connections when switching tabs or closing the browser (may be unstable)" - }, - "user": { - "failedToLoadVersionInfo": "Failed to load version information" + "c2sPresetLoadError": "Failed to load client tunnel presets" }, "placeholders": { - "enterCode": "000000", - "ipAddress": "192.168.1.1 or example.com", - "port": "22", "maxRetries": "3", "retryInterval": "10", "language": "Language", - "username": "username", - "hostname": "host name", - "folder": "folder", - "password": "password", "keyPassword": "key password", - "sudoPassword": "sudo password (optional)", - "notes": "add notes about this host...", - "expirationDate": "Select expiration date", "pastePrivateKey": "Paste your private key here...", - "pastePublicKey": "Paste your public key here...", - "credentialName": "My SSH Server", - "description": "SSH credential description", - "searchCredentials": "Search credentials by name, username, or tags...", - "sshConfig": "endpoint ssh configuration", - "bindLocalhost": "127.0.0.1 (bind to localhost)", "localListenerHost": "127.0.0.1 (listen locally)", "localTargetHost": "127.0.0.1 (target on this computer)", "socksListenerHost": "127.0.0.1 (SOCKS listener)", - "homePath": "/home", - "clientId": "your-client-id", - "clientSecret": "your-client-secret", - "authUrl": "https://your-provider.com/application/o/authorize/", - "redirectUrl": "https://your-provider.com/application/o/termix/", - "tokenUrl": "https://your-provider.com/application/o/token/", - "userIdField": "sub", - "usernameField": "name", - "scopes": "openid email profile", - "userinfoUrl": "https://your-provider.com/application/o/userinfo/", - "allowedUsers": "user@example.com, @company.com", - "enterUsername": "Enter username to make admin", - "searchHosts": "Search hosts by name, username, IP, folder, tags...", "enterPassword": "Enter your password", - "totpCode": "6-digit TOTP code", - "searchHostsAny": "Search hosts (try: tag:prod, user:root, ip:192.168)...", - "confirmPassword": "Enter your password to confirm", - "typeHere": "Type here", - "fileName": "Enter file name (e.g., example.txt)", - "folderName": "Enter folder name", - "fullPath": "Enter full path to item", - "currentPath": "Enter current path to item", - "newName": "Enter new name", - "socks5Host": "127.0.0.1", - "socks5Username": "proxy username", - "socks5Password": "proxy password", - "socks5PresetName": "e.g., Work VPN Chain", - "socks5PresetDescription": "e.g., Proxy chain for accessing work servers", - "moshCommand": "mosh user@server", "defaultPort": "22", - "defaultEndpointPort": "224", - "defaultMaxRetries": "3", - "defaultRetryInterval": "10" + "defaultEndpointPort": "224" }, "dashboard": { "title": "Dashboard", @@ -2841,7 +1289,6 @@ "github": "GitHub", "support": "Support", "discord": "Discord", - "donate": "Donate", "serverOverview": "Server Overview", "version": "Version", "upToDate": "Up to Date", @@ -2868,7 +1315,6 @@ "noServerData": "No server data available", "cpu": "CPU", "ram": "RAM", - "notAvailable": "N/A", "customizeLayout": "Customize Dashboard", "dashboardSettings": "Dashboard Settings", "enableDisableCards": "Enable/Disable Cards", @@ -2896,9 +1342,7 @@ "online": "ONLINE", "offline": "OFFLINE", "onlineLower": "Online", - "offlineLower": "Offline", "nodes": "{{count}} nodes", - "noHostsToDisplay": "No hosts to display", "add": "Add:", "commandPalette": "Command Palette", "done": "Done", @@ -2907,16 +1351,12 @@ "clear": "Clear" }, "networkGraph": { - "title": "Network Graph", "addHost": "Add Host", "addGroup": "Add Group", "addLink": "Add Link", - "deleteSelected": "Delete Selected", "zoomIn": "Zoom In", "zoomOut": "Zoom Out", "resetView": "Reset View", - "exportJson": "Export JSON", - "importJson": "Import JSON", "selectHost": "Select Host", "chooseHost": "Choose a host...", "parentGroup": "Parent Group", @@ -2929,48 +1369,28 @@ "selectGroup": "Select group...", "addConnection": "Add Connection", "hostDetails": "Host Details", - "connectToHost": "Connect to Host", - "openTerminal": "Open Terminal", - "openFileManager": "Open File Manager", - "openTunnel": "Open Tunnels", - "openDocker": "Open Docker", - "openServerStats": "Open Server Details", - "moveToGroupAction": "Move to Group", "removeFromGroup": "Remove from Group", "addHostHere": "Add Host Here", "editGroup": "Edit Group", "delete": "Delete", - "cancel": "Cancel", "add": "Add", "create": "Create", - "update": "Update", "move": "Move", "connect": "Connect", - "close": "Close", "createGroup": "Create Group", - "noAvailableHosts": "No available hosts", "selectSourcePlaceholder": "Select Source...", "selectTargetPlaceholder": "Select Target...", "invalidFile": "Invalid File", "hostAlreadyExists": "Host is already in the topology", - "sourceTargetMustDiffer": "Source and target must be different", "connectionExists": "Connection already exists", "unknown": "Unknown", "name": "Name", "ip": "IP", "status": "Status", - "id": "ID", - "failedToLoadData": "Failed to load network topology data", "failedToAddNode": "Failed to add node", "sourceDifferentFromTarget": "Source and target must be different", "exportJSON": "Export JSON", "importJSON": "Import JSON", - "searchHost": "Search hosts...", - "noHostFound": "No hosts found", - "searchGroup": "Search groups...", - "noGroupFound": "No groups found", - "searchNode": "Search nodes...", - "noNodeFound": "No nodes found", "terminal": "Terminal", "fileManager": "File Manager", "tunnel": "Tunnel", @@ -2981,10 +1401,8 @@ "docker": { "notEnabled": "Docker is not enabled for this host", "validating": "Validating Docker...", - "connectingToHost": "Connecting to host...", "connecting": "Connecting...", "error": "Error", - "errorCode": "Error code: {{code}}", "version": "Docker {{version}}", "connectionFailed": "Failed to connect to Docker", "containerStarted": "Container {{name}} started", @@ -2999,20 +1417,11 @@ "containerRemoved": "Container {{name}} removed", "failedToRemoveContainer": "Failed to remove container {{name}}", "image": "Image", - "idLabel": "ID", "ports": "Ports", "noPorts": "No ports", - "created": "Created", "start": "Start", - "stop": "Stop", - "pause": "Pause", - "unpause": "Unpause", - "restart": "Restart", - "remove": "Remove", - "removeContainer": "Remove Container", "confirmRemoveContainer": "Are you sure you want to remove the container '{{name}}'? This action cannot be undone.", "runningContainerWarning": "Warning: This container is currently running. Removing it will stop the container first.", - "removing": "Removing...", "loadingContainers": "Loading containers...", "manager": "Docker Manager", "autoRefresh": "Auto Refresh", @@ -3033,17 +1442,13 @@ "noContainersFound": "No containers found", "noContainersFoundHint": "No Docker containers are available on this host", "searchPlaceholder": "Search containers...", - "filterByStatusPlaceholder": "Filter by status", "allStatuses": "All Statuses", "stateRunning": "Running", "statePaused": "Paused", "stateExited": "Exited", "stateRestarting": "Restarting", - "allContainersCount": "All ({{count}})", - "statusCount": "{{status}} ({{count}})", "noContainersMatchFilters": "No containers match your filters", "noContainersMatchFiltersHint": "Try adjusting your search or filter criteria", - "containerMustBeRunningToViewStats": "Container must be running to view statistics", "failedToFetchStats": "Failed to fetch container statistics", "containerNotRunning": "Container not running", "startContainerToViewStats": "Start the container to view statistics", @@ -3053,8 +1458,6 @@ "cpuUsage": "CPU Usage", "current": "Current", "memoryUsage": "Memory Usage", - "usedLimit": "Used / Limit", - "percentage": "Percentage", "networkIo": "Network I/O", "input": "Input", "output": "Output", @@ -3066,9 +1469,7 @@ "name": "Name", "id": "ID", "state": "State", - "disconnectedFromContainer": "Disconnected from container", "containerMustBeRunning": "Container must be running to access console", - "authenticationRequired": "Authentication required", "verificationCodePrompt": "Enter verification code", "totpVerificationFailed": "TOTP verification failed. Please try again.", "warpgateVerificationFailed": "Warpgate authentication failed. Please try again.", @@ -3082,7 +1483,6 @@ "bash": "Bash", "sh": "sh", "ash": "ash", - "connecting": "Connecting...", "connect": "Connect", "disconnect": "Disconnect", "notConnected": "Not connected", @@ -3093,25 +1493,7 @@ "logs": "Logs", "stats": "Stats", "consoleTab": "Console", - "startContainerToAccess": "Start the container to access the console", - "log": { - "connecting": "Connecting to Docker host...", - "authStarted": "Resolving authentication credentials...", - "totpRequired": "TOTP verification required", - "warpgateRequired": "Warpgate authentication required", - "sessionCreated": "Docker SSH session established", - "sessionReady": "Ready for Docker operations", - "error": "Docker connection failed: {{error}}" - } - }, - "sftp": { - "log": { - "connecting": "Connecting to SFTP server...", - "jumpHost": "Connecting via jump host {{host}}...", - "connected": "SFTP connection established", - "authFailed": "Authentication failed", - "error": "SFTP operation failed: {{error}}" - } + "startContainerToAccess": "Start the container to access the console" }, "newUi": { "sidebar": { @@ -3240,8 +1622,6 @@ "twoFaOn": "On", "twoFaOff": "Off", "versionLabel": "Version", - "versionStable": "stable", - "upToDate": "Up to date", "deleteAccount": "Delete Account", "deleteAccountDescription": "Permanently delete your account", "deleteButton": "Delete", diff --git a/src/ui/sidebar/AppRail.tsx b/src/ui/sidebar/AppRail.tsx index b76ea84e..307cbec9 100644 --- a/src/ui/sidebar/AppRail.tsx +++ b/src/ui/sidebar/AppRail.tsx @@ -1,4 +1,5 @@ import { useState } from "react"; +import { useTranslation } from "react-i18next"; import { Clock, Hammer, @@ -35,22 +36,29 @@ type RailItem = } | { kind: "separator" }; -function buildRailButtons(splitMode: SplitMode): RailItem[] { +function buildRailButtons( + splitMode: SplitMode, + t: (key: string) => string, +): RailItem[] { return [ - { view: "hosts", icon: , title: "Hosts" }, + { view: "hosts", icon: , title: t("nav.hosts") }, { kind: "separator" }, - { view: "quick-connect", icon: , title: "Quick Connect" }, + { + view: "quick-connect", + icon: , + title: t("nav.quickConnect"), + }, { kind: "separator" }, - { view: "ssh-tools", icon: , title: "SSH Tools" }, + { view: "ssh-tools", icon: , title: t("nav.sshTools") }, { kind: "separator" }, - { view: "snippets", icon: , title: "Snippets" }, + { view: "snippets", icon: , title: t("nav.snippets") }, { kind: "separator" }, - { view: "history", icon: , title: "History" }, + { view: "history", icon: , title: t("nav.history") }, { kind: "separator" }, { view: "split-screen", icon: , - title: "Split Screen", + title: t("nav.splitScreen"), dot: splitMode !== "none", }, { kind: "separator" }, @@ -66,6 +74,7 @@ export function AppRail({ sidebarOpen, splitMode, username, + isAdmin, profileDropdownOpen, onProfileDropdownChange, onRailClick, @@ -75,14 +84,16 @@ export function AppRail({ sidebarOpen: boolean; splitMode: SplitMode; username: string; + isAdmin: boolean; profileDropdownOpen: boolean; onProfileDropdownChange: (open: boolean) => void; onRailClick: (view: RailView) => void; onLogout: () => void; }) { + const { t } = useTranslation(); const [hovered, setHovered] = useState(false); const railExpanded = hovered || profileDropdownOpen; - const railButtons = buildRailButtons(splitMode); + const railButtons = buildRailButtons(splitMode, t); return (
- {( - [ - { - view: "user-profile" as RailView, - icon: , - title: "Profile", - }, - { - view: "admin-settings" as RailView, - icon: , - title: "Admin", - }, - ] as const - ).map((item) => ( + {[ + { + view: "user-profile" as RailView, + icon: , + title: t("nav.userProfile"), + }, + ...(isAdmin + ? [ + { + view: "admin-settings" as RailView, + icon: , + title: t("nav.admin"), + }, + ] + : []), + ].map((item) => ( + ))}
+ + {form.socks5ProxyMode === "single" && ( +
+
+ + + setField("socks5Host", e.target.value) + } + /> +
+
+ + + setField( + "socks5Port", + Number(e.target.value) as any, + ) + } + /> +
+
+ + + setField("socks5Username", e.target.value) + } + /> +
+
+ + + setField("socks5Password", e.target.value) + } + /> +
+
+ )} + + {form.socks5ProxyMode === "chain" && ( +
+ {form.socks5ProxyChain.map((node, ni) => ( +
+
+ + {t("hosts.proxyNode")} {ni + 1} + + +
+
+
+ + { + const u = [...form.socks5ProxyChain]; + u[ni] = { ...u[ni], host: e.target.value }; + setField("socks5ProxyChain", u); + }} + /> +
+
+ + { + const u = [...form.socks5ProxyChain]; + u[ni] = { + ...u[ni], + port: Number(e.target.value), + }; + setField("socks5ProxyChain", u); + }} + /> +
+
+ + +
+
+ + { + const u = [...form.socks5ProxyChain]; + u[ni] = { + ...u[ni], + username: e.target.value, + }; + setField("socks5ProxyChain", u); + }} + /> +
+
+ + { + const u = [...form.socks5ProxyChain]; + u[ni] = { + ...u[ni], + password: e.target.value, + }; + setField("socks5ProxyChain", u); + }} + /> +
+
+
+ ))} + +
+ )} + + {/* Connection path visualization */} + {(form.socks5ProxyMode === "single" && form.socks5Host) || + (form.socks5ProxyMode === "chain" && + form.socks5ProxyChain.length > 0) ? ( +
+ + {t("hosts.you")} + + {form.socks5ProxyMode === "single" && + form.socks5Host ? ( + <> + + + {form.socks5Host}:{form.socks5Port} + + + ) : ( + form.socks5ProxyChain + .filter((n) => n.host) + .map((n, ni) => ( + + + + {n.host}:{n.port} + + + )) + )} + {form.jumpHosts + .filter((j) => j.hostId) + .map((j, ji) => { + const jh = hosts.find((h) => h.id === j.hostId); + return jh ? ( + + + + {jh.name || jh.ip} + + + ) : null; + })} + + + {form.ip || "target"}:{form.sshPort} + +
+ ) : null}
)}
@@ -1619,16 +1864,68 @@ function HostEditor({ {authMethod === "key" && ( <>
- -