diff --git a/app/__init__.py b/app/__init__.py index 66e2e997..af980156 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -27,7 +27,8 @@ save_user_settings, ) from .app_settings import (is_registration_enabled, set_registration_enabled, - is_ssh_gateway_enabled, set_ssh_gateway_enabled) + is_ssh_gateway_enabled, set_ssh_gateway_enabled, + is_support_links_enabled, set_support_links_enabled) from .storage_errors import StorageCorruptionError from .tailscale_ssh import user_can_use_tailscale_ssh from .version import display_version @@ -641,6 +642,9 @@ def inject_url_prefix(): 'webssh_version': ( display_version() if current_user.is_authenticated else '' ), + 'support_links_enabled': ( + current_user.is_authenticated and is_support_links_enabled() + ), } @app.before_request @@ -1735,6 +1739,7 @@ def admin_get_settings(): policies = {name: runtime_feature_status(name) for name in RUNTIME_FEATURES} return jsonify({'setting_sources': native_setting_sources(), 'runtime_features': policies, **{name: status['enabled'] for name, status in policies.items()}, 'registration_enabled': is_registration_enabled(), 'ssh_gateway_enabled': is_ssh_gateway_enabled(), + 'support_links_enabled': is_support_links_enabled(), 'credentials_enabled': is_credentials_enabled(), 'automation_enabled': is_automation_enabled()}) @@ -1764,6 +1769,9 @@ def admin_set_settings(): if ('ssh_gateway_enabled' in data and type(data['ssh_gateway_enabled']) is not bool): return jsonify({'error': 'ssh_gateway_enabled must be a boolean'}), 400 + if ('support_links_enabled' in data + and type(data['support_links_enabled']) is not bool): + return jsonify({'error': 'support_links_enabled must be a boolean'}), 400 if 'registration_enabled' in data: if type(data['registration_enabled']) is not bool: return jsonify({ @@ -1786,6 +1794,10 @@ def admin_set_settings(): val = set_ssh_gateway_enabled(data['ssh_gateway_enabled']) log_info("Admin changed SSH gateway setting", admin=current_user.username, ssh_gateway_enabled=val) + if 'support_links_enabled' in data: + val = set_support_links_enabled(data['support_links_enabled']) + log_info("Admin changed support links setting", + admin=current_user.username, support_links_enabled=val) from .app_settings import set_credentials_enabled, set_automation_enabled for feature, setter in (('credentials_enabled', set_credentials_enabled), ('automation_enabled', set_automation_enabled)): diff --git a/app/app_settings.py b/app/app_settings.py index a581d2df..cc909d57 100644 --- a/app/app_settings.py +++ b/app/app_settings.py @@ -22,6 +22,7 @@ 'server_operations_enabled', 'tmux_enabled', 'tmux_default', + 'support_links_enabled', ) @@ -124,6 +125,23 @@ def set_ssh_gateway_enabled(value): return value +def is_support_links_enabled(): + """Project support links are shown unless an administrator hid them.""" + return _load().get('support_links_enabled') is not False + + +def set_support_links_enabled(value): + if type(value) is not bool: + return False + with storage_lock(f'app-settings:{_SETTINGS_FILE}'): + data = _load_with_lock_held() + data['support_links_enabled'] = value + if not _valid_settings(data): + return False + _save(data) + return value + + def set_audit_backup_count(value): if type(value) is not int or not 1 <= value <= 90: return False diff --git a/docs/wiki/Configuration-Reference.md b/docs/wiki/Configuration-Reference.md index 629ba857..3fe58623 100644 --- a/docs/wiki/Configuration-Reference.md +++ b/docs/wiki/Configuration-Reference.md @@ -305,6 +305,11 @@ or bypass provider, HTTPS, origin, RP-ID or recovery checks. Networking, identit provider trust, secrets, resource ceilings, mounts and backup recovery paths remain deployment configuration. Audit retention is already managed in Administration. +Signed-in users see links to support WebSSH development in the account menu +and the Settings side panel. **Project support links** in Administration → +Integrations hides them for all users; the links load nothing from external +sites until a user follows one. + TOTP capability is available by default in 3.0; Admin activation remains off until explicitly enabled. `TOTP_ENABLED=false` is the optional operator kill switch. Existing enrolled users and saved activation choices are retained. diff --git a/static/css/admin.css b/static/css/admin.css index 08c0cb62..a953e9cf 100644 --- a/static/css/admin.css +++ b/static/css/admin.css @@ -580,6 +580,31 @@ font-size: 17px; } +.settings-support-hint { + margin: 0 0 8px; + color: var(--text-muted); + font-size: 12px; + line-height: 1.5; +} + +.settings-support-links { + display: grid; + gap: 2px; + margin: 0; + padding: 0; + list-style: none; +} + +.settings-support-links .settings-quick-link { + font-size: 13px; + text-decoration: none; +} + +.settings-support-links .settings-quick-link:focus-visible { + outline: 2px solid var(--accent-primary); + outline-offset: 1px; +} + .settings-context-note { display: flex; align-items: flex-start; diff --git a/static/css/session-workspace.css b/static/css/session-workspace.css index 66ed4381..56bce0ba 100644 --- a/static/css/session-workspace.css +++ b/static/css/session-workspace.css @@ -288,45 +288,6 @@ margin: 13px 0; } -.session-cpu-gauge { - display: grid; - width: 82px; - height: 82px; - padding: 7px; - border-radius: 50%; - background: conic-gradient(var(--gauge-color, var(--accent-primary)) calc(var(--value) * 1%), var(--bg-hover) 0); - place-items: center; -} - -.session-cpu-gauge::before { - content: ''; - grid-area: 1 / 1; - width: 100%; - height: 100%; - border-radius: 50%; - background: var(--bg-secondary); -} - -.session-cpu-gauge > div { - z-index: 1; - grid-area: 1 / 1; - text-align: center; -} - -.session-cpu-gauge strong { - display: block; - color: var(--text-primary); - font-family: var(--font-mono); - font-size: 17px; -} - -.session-cpu-gauge span { - color: var(--text-muted); - font-size: 9px; - font-weight: 700; - letter-spacing: 0.07em; -} - .session-cpu-chart { width: 100%; height: 72px; @@ -370,8 +331,7 @@ transition: width 0.35s ease; } -.session-resource.warning .session-resource-track span, -.session-cpu-gauge.warning { +.session-resource.warning .session-resource-track span { --gauge-color: var(--warning-color, #d29922); } @@ -379,8 +339,7 @@ background: var(--warning-color, #d29922); } -.session-resource.critical .session-resource-track span, -.session-cpu-gauge.critical { +.session-resource.critical .session-resource-track span { --gauge-color: var(--danger-color, #f85149); } diff --git a/static/css/sftp-file-manager.css b/static/css/sftp-file-manager.css index ab3f55ac..ed6055bf 100644 --- a/static/css/sftp-file-manager.css +++ b/static/css/sftp-file-manager.css @@ -3163,7 +3163,7 @@ } .smb-source-security-note .material-icons { - color: var(--primary-color); + color: var(--accent-primary); font-size: 18px; } @@ -3175,11 +3175,11 @@ } .smb-source-status[data-state="error"] { - color: var(--danger-color); + color: var(--error-color); } .smb-source-status[data-state="pending"] { - color: var(--primary-color); + color: var(--accent-primary); } .smb-source-status[data-state="success"] { diff --git a/static/css/style.css b/static/css/style.css index 2159e3f9..e2c7470f 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -4157,6 +4157,12 @@ body.keyboard-open.notepad-focused .notepad-panel { display: none; } +.account-item .account-item-external { + margin-left: auto; + color: var(--text-secondary); + font-size: 15px; +} + .account-version { margin: 6px 4px 0; padding: 8px 10px 4px; diff --git a/static/js/admin.js b/static/js/admin.js index b611748c..af7827ae 100644 --- a/static/js/admin.js +++ b/static/js/admin.js @@ -1378,7 +1378,7 @@ gateway.checked = data.ssh_gateway_enabled === true; gateway.disabled = false; } - for (const [id, key] of [['settingCredentials', 'credentials_enabled'], ['settingAutomation', 'automation_enabled']]) { + for (const [id, key] of [['settingCredentials', 'credentials_enabled'], ['settingAutomation', 'automation_enabled'], ['settingSupportLinks', 'support_links_enabled']]) { const input = document.getElementById(id); if (input && !settingsWrites.has(key)) { input.checked = data[key] === true; @@ -1551,7 +1551,7 @@ target.disabled = false; } }); - for (const [id, key] of [['settingCredentials', 'credentials_enabled'], ['settingAutomation', 'automation_enabled']]) { + for (const [id, key] of [['settingCredentials', 'credentials_enabled'], ['settingAutomation', 'automation_enabled'], ['settingSupportLinks', 'support_links_enabled']]) { document.getElementById(id)?.addEventListener('change', async (event) => { const input = event.target; const enabled = input.checked; diff --git a/static/js/drag-drop-manager.js b/static/js/drag-drop-manager.js index 6cc8dbc0..4f623630 100644 --- a/static/js/drag-drop-manager.js +++ b/static/js/drag-drop-manager.js @@ -331,7 +331,7 @@ class DragDropManager { if (session) { hint.textContent = `${session.username}@${session.host} — ${this.t('dropUpload.chooseFolder', 'Choose a destination folder after dropping.')}`; } else { - hint.textContent = 'Connect to a server first'; + hint.textContent = this.t('dropUpload.noSession', 'Connect to a server first'); } } } diff --git a/static/js/i18n.js b/static/js/i18n.js index 4544ce31..0538db4f 100644 --- a/static/js/i18n.js +++ b/static/js/i18n.js @@ -955,6 +955,8 @@ const translations = { 'gateway.target': "Waiting for target access. Review gateway prompts below.", 'admin.integrations': "Integrations", 'admin.sshGateway': "Warpgate / SSH gateway", + 'admin.supportLinks': "Project support links", + 'admin.supportLinksHint': "Show links to support WebSSH development in the account menu and the Settings side panel for all users.", 'admin.sshGatewayHint': "Allow interactive gateway connections using user:target for all users. Disabled by default. Disabling blocks new connections and unfinished gateway authentication; established sessions remain connected. Reload open workspaces after changing this setting.", 'gateway.cancel': "Cancel connection", 'gateway.passwordHint': "Leave the password empty for interactive gateway authentication.", @@ -962,6 +964,7 @@ const translations = { 'workspace.directorySync.setup': "Set up Bash prompt detection", 'workspace.directorySync.setupHint': "Run the copied block yourself at an empty Bash prompt in this pane. No configuration files are changed. Wait for detection, then press Enter. The helper stays until this shell exits, even with sync off.", 'workspace.directorySync.copy': "Copy activation command", + 'workspace.directorySync.commandLabel': 'Bash activation command', 'workspace.directorySync.copyFailed': "Could not copy the command. Select the displayed text or try again.", 'workspace.directorySync.ready': "Folder sync ready.", 'workspace.directorySync.waiting': "Folder sync paused. Finish or cancel your input. At an empty prompt, press Enter; if still paused, switch sync off and on, then press Enter.", @@ -1109,6 +1112,7 @@ const translations = { 'dropUpload.folderHint': "Relative paths start in the SFTP home directory, not the terminal working directory.", 'dropUpload.timeout': "Creating the destination folder timed out.", 'dropUpload.chooseFolder': "Choose a destination folder after dropping.", + 'dropUpload.noSession': 'Connect to a server first', 'app.title': 'Web SSH Terminal', 'app.subtitle': 'Modern Terminal Access', @@ -2019,6 +2023,8 @@ const translations = { 'settings.primarySignIn': 'Primary sign-in', 'settings.scope': 'Settings scope', 'settings.webSshVersion': 'WebSSH version', + 'settings.supportProject': 'Support WebSSH', + 'settings.supportProjectHint': 'WebSSH is free and open source. Support ongoing development:', 'settings.personal': 'Personal', 'settings.personalAndAdministration': 'Personal + administration', 'settings.quickAccess': 'Quick access', @@ -2176,6 +2182,7 @@ const translations = { 'fm.qc.username': 'Username', 'fm.qc.authentication': 'Authentication', 'fm.qc.password': 'Password', + 'fm.qc.passwordPlaceholder': 'Enter password', 'fm.qc.sshKey': 'SSH Key', 'fm.qc.selectKey': '-- Select Key --', 'fm.qc.connect': 'Connect', @@ -2295,6 +2302,9 @@ const translations = { 'preview.loading': 'Loading preview...', 'preview.binaryUnavailable': 'Binary file - cannot display preview', 'preview.downloadFile': 'Download File', + 'preview.truncatedPrefix': 'File truncated - showing first', + 'preview.truncatedOf': 'of', + 'preview.imageAlt': 'Image preview', 'terminal.saveTranscript': 'Save Transcript', 'terminal.copySelection': 'Copy Selection', 'terminal.pasteClipboard': 'Paste Clipboard', @@ -3377,6 +3387,8 @@ const translations = { 'gateway.target': "Đang chờ quyền truy cập đích. Kiểm tra yêu cầu bên dưới.", 'admin.integrations': "Tích hợp", 'admin.sshGateway': "Warpgate / cổng SSH", + 'admin.supportLinks': "Liên kết ủng hộ dự án", + 'admin.supportLinksHint': "Hiển thị liên kết ủng hộ việc phát triển WebSSH trong menu tài khoản và bảng bên của Cài đặt cho mọi người dùng.", 'admin.sshGatewayHint': "Cho phép mọi người dùng kết nối cổng tương tác bằng user:target. Mặc định tắt. Khi tắt, kết nối mới và xác thực chưa hoàn tất bị chặn; phiên đã kết nối được giữ lại. Tải lại không gian làm việc sau khi thay đổi.", 'gateway.cancel': "Hủy kết nối", 'gateway.passwordHint': "Để trống mật khẩu để xác thực tương tác.", @@ -3384,6 +3396,7 @@ const translations = { 'workspace.directorySync.setup': "Thiết lập nhận diện dấu nhắc Bash", 'workspace.directorySync.setupHint': "Tự chạy khối lệnh đã sao chép tại dấu nhắc Bash trống trong ô này. Không thay đổi tệp cấu hình. Chờ nhận diện rồi nhấn Enter. Tiện ích tồn tại đến khi thoát shell, kể cả khi tắt đồng bộ.", 'workspace.directorySync.copy': "Sao chép lệnh kích hoạt", + 'workspace.directorySync.commandLabel': 'Lệnh kích hoạt Bash', 'workspace.directorySync.copyFailed': "Không sao chép được. Hãy chọn văn bản hiển thị hoặc thử lại.", 'workspace.directorySync.ready': "Đồng bộ thư mục sẵn sàng.", 'workspace.directorySync.waiting': "Đồng bộ tạm dừng. Hoàn tất hoặc hủy nhập lệnh. Nhấn Enter tại dấu nhắc trống; nếu vẫn tạm dừng, tắt rồi bật đồng bộ và nhấn Enter.", @@ -3531,6 +3544,7 @@ const translations = { 'dropUpload.folderHint': "Đường dẫn tương đối bắt đầu tại thư mục nhà SFTP, không phải thư mục làm việc của terminal.", 'dropUpload.timeout': "Hết thời gian tạo thư mục đích.", 'dropUpload.chooseFolder': "Chọn thư mục đích sau khi thả.", + 'dropUpload.noSession': 'Hãy kết nối tới máy chủ trước', 'app.title': 'Web SSH Terminal', 'app.subtitle': 'Truy cập terminal hiện đại', @@ -4441,6 +4455,8 @@ const translations = { 'settings.primarySignIn': 'Đăng nhập chính', 'settings.scope': 'Phạm vi cài đặt', 'settings.webSshVersion': 'Phiên bản WebSSH', + 'settings.supportProject': 'Ủng hộ WebSSH', + 'settings.supportProjectHint': 'WebSSH miễn phí và mã nguồn mở. Hãy ủng hộ việc phát triển tiếp theo:', 'settings.personal': 'Cá nhân', 'settings.personalAndAdministration': 'Cá nhân + quản trị', 'settings.quickAccess': 'Truy cập nhanh', @@ -4598,6 +4614,7 @@ const translations = { 'fm.qc.username': 'Tên đăng nhập', 'fm.qc.authentication': 'Xác thực', 'fm.qc.password': 'Mật khẩu', + 'fm.qc.passwordPlaceholder': 'Nhập mật khẩu', 'fm.qc.sshKey': 'Khóa SSH', 'fm.qc.selectKey': '-- Chọn khóa --', 'fm.qc.connect': 'Kết nối', @@ -4717,6 +4734,9 @@ const translations = { 'preview.loading': 'Đang tải bản xem trước...', 'preview.binaryUnavailable': 'Tệp nhị phân - không thể hiển thị bản xem trước', 'preview.downloadFile': 'Tải tệp xuống', + 'preview.truncatedPrefix': 'Tệp đã bị cắt bớt - chỉ hiển thị', + 'preview.truncatedOf': 'trên tổng', + 'preview.imageAlt': 'Xem trước hình ảnh', 'terminal.saveTranscript': 'Lưu bản ghi', 'terminal.copySelection': 'Sao chép vùng chọn', 'terminal.pasteClipboard': 'Dán từ bộ nhớ tạm', @@ -5803,6 +5823,8 @@ const translations = { 'gateway.target': "Warten auf den Zielzugriff. Bitte die Gateway-Abfragen unten prüfen.", 'admin.integrations': "Integrationen", 'admin.sshGateway': "Warpgate / SSH-Gateway", + 'admin.supportLinks': "Links zur Projektunterstützung", + 'admin.supportLinksHint': "Zeigt allen Benutzern im Kontomenü und in der Seitenleiste der Einstellungen Links, mit denen sie die Entwicklung von WebSSH unterstützen können.", 'admin.sshGatewayHint': "Interaktive Gateway-Verbindungen mit user:target für alle Benutzer erlauben. Standardmäßig deaktiviert. Abschalten sperrt neue Verbindungen und laufende Gateway-Anmeldungen; aufgebaute Sitzungen bleiben verbunden. Offene Arbeitsbereiche nach einer Änderung neu laden.", 'gateway.cancel': "Verbindung abbrechen", 'gateway.passwordHint': "Passwort für die interaktive Gateway-Anmeldung leer lassen.", @@ -5810,6 +5832,7 @@ const translations = { 'workspace.directorySync.setup': "Bash-Prompt-Erkennung einrichten", 'workspace.directorySync.setupHint': "Führe den kopierten Block selbst am leeren Bash-Prompt dieses Bereichs aus. Konfigurationsdateien bleiben unverändert. Warte auf die Erkennung und drücke dann Enter. Die Ergänzung bleibt bis zum Ende dieser Shell aktiv, auch bei ausgeschaltetem Sync.", 'workspace.directorySync.copy': "Aktivierungsbefehl kopieren", + 'workspace.directorySync.commandLabel': 'Bash-Aktivierungsbefehl', 'workspace.directorySync.copyFailed': "Kopieren fehlgeschlagen. Markiere den angezeigten Text oder versuche es erneut.", 'workspace.directorySync.ready': "Ordner-Sync bereit.", 'workspace.directorySync.waiting': "Ordner-Sync pausiert. Beende oder verwirf deine Eingabe. Drücke am leeren Prompt Enter; bleibt der Sync pausiert, schalte ihn aus und wieder an und drücke Enter.", @@ -5957,6 +5980,7 @@ const translations = { 'dropUpload.folderHint': "Relative Pfade beginnen im SFTP-Heimatverzeichnis, nicht im Arbeitsverzeichnis des Terminals.", 'dropUpload.timeout': "Zeitüberschreitung beim Erstellen des Zielordners.", 'dropUpload.chooseFolder': "Nach dem Ablegen einen Zielordner auswählen.", + 'dropUpload.noSession': 'Zuerst mit einem Server verbinden', 'app.title': 'Web SSH Terminal', 'app.subtitle': 'Moderner Terminal-Zugang', @@ -6885,6 +6909,8 @@ const translations = { 'settings.primarySignIn': 'Primäre Anmeldung', 'settings.scope': 'Einstellungsumfang', 'settings.webSshVersion': 'WebSSH-Version', + 'settings.supportProject': 'WebSSH unterstützen', + 'settings.supportProjectHint': 'WebSSH ist kostenlos und Open Source. Unterstütze die Weiterentwicklung:', 'settings.personal': 'Persönlich', 'settings.personalAndAdministration': 'Persönlich + Administration', 'settings.quickAccess': 'Schnellzugriff', @@ -7042,6 +7068,7 @@ const translations = { 'fm.qc.username': 'Benutzername', 'fm.qc.authentication': 'Authentifizierung', 'fm.qc.password': 'Passwort', + 'fm.qc.passwordPlaceholder': 'Passwort eingeben', 'fm.qc.sshKey': 'SSH-Schlüssel', 'fm.qc.selectKey': '-- Schlüssel auswählen --', 'fm.qc.connect': 'Verbinden', @@ -7156,6 +7183,9 @@ const translations = { 'preview.loading': 'Vorschau wird geladen...', 'preview.binaryUnavailable': 'Binärdatei - Vorschau nicht möglich', 'preview.downloadFile': 'Datei herunterladen', + 'preview.truncatedPrefix': 'Datei gekürzt – angezeigt werden die ersten', + 'preview.truncatedOf': 'von', + 'preview.imageAlt': 'Bildvorschau', 'terminal.saveTranscript': 'Terminalprotokoll speichern', 'terminal.copySelection': 'Auswahl kopieren', 'terminal.pasteClipboard': 'Zwischenablage einfügen', @@ -8224,6 +8254,8 @@ const translations = { 'gateway.target': "En attente de l'accès à la cible. Vérifiez les demandes ci-dessous.", 'admin.integrations': "Intégrations", 'admin.sshGateway': "Warpgate / passerelle SSH", + 'admin.supportLinks': "Liens de soutien au projet", + 'admin.supportLinksHint': "Affiche à tous les utilisateurs, dans le menu du compte et le panneau latéral des paramètres, des liens pour soutenir le développement de WebSSH.", 'admin.sshGatewayHint': "Autoriser les connexions interactives via user:target pour tous les utilisateurs. Désactivé par défaut. La désactivation bloque les nouvelles connexions et les authentifications en cours ; les sessions établies restent connectées. Rechargez les espaces de travail après modification.", 'gateway.cancel': "Annuler la connexion", 'gateway.passwordHint': "Laissez le mot de passe vide pour l'authentification interactive.", @@ -8231,6 +8263,7 @@ const translations = { 'workspace.directorySync.setup': "Configurer la détection Bash", 'workspace.directorySync.setupHint': "Exécutez vous-même le bloc copié à une invite Bash vide dans ce panneau. Aucun fichier de configuration ne change. Attendez la détection, puis appuyez sur Entrée. Le module reste actif jusqu’à la fermeture du shell, même sans synchronisation.", 'workspace.directorySync.copy': "Copier la commande d’activation", + 'workspace.directorySync.commandLabel': 'Commande d’activation Bash', 'workspace.directorySync.copyFailed': "Copie impossible. Sélectionnez le texte affiché ou réessayez.", 'workspace.directorySync.ready': "Synchronisation prête.", 'workspace.directorySync.waiting': "Synchronisation en pause. Terminez ou annulez la saisie, puis appuyez sur Entrée à une invite vide. Si nécessaire, désactivez et réactivez la synchronisation, puis appuyez sur Entrée.", @@ -8378,6 +8411,7 @@ const translations = { 'dropUpload.folderHint': "Les chemins relatifs partent du dossier personnel SFTP, pas du répertoire de travail du terminal.", 'dropUpload.timeout': "Délai dépassé pour la création du dossier de destination.", 'dropUpload.chooseFolder': "Choisissez un dossier de destination après le dépôt.", + 'dropUpload.noSession': 'Connectez-vous d’abord à un serveur', 'app.title': 'Terminal SSH Web', 'app.subtitle': 'Accès Terminal Moderne', @@ -9317,6 +9351,8 @@ const translations = { 'settings.primarySignIn': 'Connexion principale', 'settings.scope': 'Portée des paramètres', 'settings.webSshVersion': 'Version de WebSSH', + 'settings.supportProject': 'Soutenir WebSSH', + 'settings.supportProjectHint': 'WebSSH est gratuit et open source. Soutenez son développement :', 'settings.personal': 'Personnel', 'settings.personalAndAdministration': 'Personnel + administration', 'settings.quickAccess': 'Accès rapide', @@ -9474,6 +9510,7 @@ const translations = { 'fm.qc.username': "Nom d'utilisateur", 'fm.qc.authentication': 'Authentification', 'fm.qc.password': 'Mot de passe', + 'fm.qc.passwordPlaceholder': 'Saisir le mot de passe', 'fm.qc.sshKey': 'Clé SSH', 'fm.qc.selectKey': '-- Sélectionner une clé --', 'fm.qc.connect': 'Connecter', @@ -9588,6 +9625,9 @@ const translations = { 'preview.loading': 'Chargement de l’aperçu...', 'preview.binaryUnavailable': 'Fichier binaire - aperçu indisponible', 'preview.downloadFile': 'Télécharger le fichier', + 'preview.truncatedPrefix': 'Fichier tronqué – affichage des premiers', + 'preview.truncatedOf': 'sur', + 'preview.imageAlt': 'Aperçu de l’image', 'terminal.saveTranscript': 'Enregistrer la transcription', 'terminal.copySelection': 'Copier la sélection', 'terminal.pasteClipboard': 'Coller le presse-papiers', @@ -10645,6 +10685,8 @@ const translations = { 'gateway.target': "Esperando acceso al destino. Revisa las solicitudes siguientes.", 'admin.integrations': "Integraciones", 'admin.sshGateway': "Warpgate / puerta de enlace SSH", + 'admin.supportLinks': "Enlaces de apoyo al proyecto", + 'admin.supportLinksHint': "Muestra a todos los usuarios, en el menú de la cuenta y el panel lateral de ajustes, enlaces para apoyar el desarrollo de WebSSH.", 'admin.sshGatewayHint': "Permitir conexiones interactivas con user:target para todos los usuarios. Desactivado por defecto. Al desactivar se bloquean nuevas conexiones y autenticaciones pendientes; las sesiones establecidas siguen conectadas. Recarga los espacios de trabajo tras el cambio.", 'gateway.cancel': "Cancelar conexión", 'gateway.passwordHint': "Deja la contraseña vacía para la autenticación interactiva.", @@ -10652,6 +10694,7 @@ const translations = { 'workspace.directorySync.setup': "Configurar detección de Bash", 'workspace.directorySync.setupHint': "Ejecuta tú mismo el bloque copiado en un prompt Bash vacío de este panel. No se modifican archivos de configuración. Espera la detección y pulsa Intro. La integración permanece hasta cerrar la shell, incluso con la sincronización desactivada.", 'workspace.directorySync.copy': "Copiar comando de activación", + 'workspace.directorySync.commandLabel': 'Comando de activación de Bash', 'workspace.directorySync.copyFailed': "No se pudo copiar. Selecciona el texto mostrado o inténtalo de nuevo.", 'workspace.directorySync.ready': "Sincronización lista.", 'workspace.directorySync.waiting': "Sincronización pausada. Termina o cancela la entrada y pulsa Intro en un prompt vacío. Si sigue pausada, desactiva y activa la sincronización y pulsa Intro.", @@ -10799,6 +10842,7 @@ const translations = { 'dropUpload.folderHint': "Las rutas relativas parten del directorio personal SFTP, no del directorio de trabajo del terminal.", 'dropUpload.timeout': "Se agotó el tiempo para crear la carpeta de destino.", 'dropUpload.chooseFolder': "Elige una carpeta de destino después de soltar.", + 'dropUpload.noSession': 'Conéctate primero a un servidor', 'app.title': 'Terminal SSH Web', 'app.subtitle': 'Acceso Terminal Moderno', @@ -11738,6 +11782,8 @@ const translations = { 'settings.primarySignIn': 'Inicio de sesión principal', 'settings.scope': 'Ámbito de los ajustes', 'settings.webSshVersion': 'Versión de WebSSH', + 'settings.supportProject': 'Apoyar WebSSH', + 'settings.supportProjectHint': 'WebSSH es gratuito y de código abierto. Apoya su desarrollo:', 'settings.personal': 'Personal', 'settings.personalAndAdministration': 'Personal + administración', 'settings.quickAccess': 'Acceso rápido', @@ -11895,6 +11941,7 @@ const translations = { 'fm.qc.username': 'Usuario', 'fm.qc.authentication': 'Autenticación', 'fm.qc.password': 'Contraseña', + 'fm.qc.passwordPlaceholder': 'Introduce la contraseña', 'fm.qc.sshKey': 'Clave SSH', 'fm.qc.selectKey': '-- Seleccionar clave --', 'fm.qc.connect': 'Conectar', @@ -12009,6 +12056,9 @@ const translations = { 'preview.loading': 'Cargando vista previa...', 'preview.binaryUnavailable': 'Archivo binario - no se puede mostrar la vista previa', 'preview.downloadFile': 'Descargar archivo', + 'preview.truncatedPrefix': 'Archivo truncado: se muestran los primeros', + 'preview.truncatedOf': 'de', + 'preview.imageAlt': 'Vista previa de la imagen', 'terminal.saveTranscript': 'Guardar transcripción', 'terminal.copySelection': 'Copiar selección', 'terminal.pasteClipboard': 'Pegar portapapeles', @@ -13065,6 +13115,8 @@ const translations = { 'gateway.target': "正在等待目标访问。请检查下方的网关提示。", 'admin.integrations': "集成", 'admin.sshGateway': "Warpgate / SSH 网关", + 'admin.supportLinks': "项目支持链接", + 'admin.supportLinksHint': "在账户菜单和设置侧栏中向所有用户显示支持 WebSSH 开发的链接。", 'admin.sshGatewayHint': "允许所有用户使用 user:target 建立交互式网关连接。默认禁用。禁用后会阻止新连接和未完成的网关认证;已建立的会话保持连接。更改后请重新加载工作区。", 'gateway.cancel': "取消连接", 'gateway.passwordHint': "将密码留空以使用交互式网关身份验证。", @@ -13072,6 +13124,7 @@ const translations = { 'workspace.directorySync.setup': "设置 Bash 提示符检测", 'workspace.directorySync.setupHint': "请自行在此窗格的空白 Bash 提示符处运行复制的代码。不会修改配置文件。等待检测完成后按 Enter。即使关闭同步,辅助功能也会保留到此 shell 退出。", 'workspace.directorySync.copy': "复制激活命令", + 'workspace.directorySync.commandLabel': 'Bash 激活命令', 'workspace.directorySync.copyFailed': "无法复制。请选择显示的文本或重试。", 'workspace.directorySync.ready': "文件夹同步已就绪。", 'workspace.directorySync.waiting': "同步已暂停。完成或取消输入后,在空白提示符处按 Enter。如仍暂停,请关闭再开启同步,然后按 Enter。", @@ -13219,6 +13272,7 @@ const translations = { 'dropUpload.folderHint': "相对路径从 SFTP 主目录开始,而不是终端的工作目录。", 'dropUpload.timeout': "创建目标文件夹超时。", 'dropUpload.chooseFolder': "拖放后选择目标文件夹。", + 'dropUpload.noSession': '请先连接到服务器', 'app.title': 'Web SSH 终端', 'app.subtitle': '现代化终端访问', @@ -14147,6 +14201,8 @@ const translations = { 'settings.primarySignIn': '主要登录方式', 'settings.scope': '设置范围', 'settings.webSshVersion': 'WebSSH 版本', + 'settings.supportProject': '支持 WebSSH', + 'settings.supportProjectHint': 'WebSSH 免费且开源。支持后续开发:', 'settings.personal': '个人', 'settings.personalAndAdministration': '个人 + 管理', 'settings.quickAccess': '快速访问', @@ -14304,6 +14360,7 @@ const translations = { 'fm.qc.username': '用户名', 'fm.qc.authentication': '认证方式', 'fm.qc.password': '密码', + 'fm.qc.passwordPlaceholder': '输入密码', 'fm.qc.sshKey': 'SSH 密钥', 'fm.qc.selectKey': '-- 选择密钥 --', 'fm.qc.connect': '连接', @@ -14418,6 +14475,9 @@ const translations = { 'preview.loading': '正在加载预览…', 'preview.binaryUnavailable': '二进制文件 - 无法显示预览', 'preview.downloadFile': '下载文件', + 'preview.truncatedPrefix': '文件已截断 - 仅显示前', + 'preview.truncatedOf': ',总大小', + 'preview.imageAlt': '图片预览', 'terminal.saveTranscript': '保存终端记录', 'terminal.copySelection': '复制选中内容', 'terminal.pasteClipboard': '粘贴剪贴板', diff --git a/static/js/profile-manager.js b/static/js/profile-manager.js index c24dea02..ffbaa686 100644 --- a/static/js/profile-manager.js +++ b/static/js/profile-manager.js @@ -1879,7 +1879,7 @@ const ProfileManager = { const keyContent = contentInput?.value || ''; if (!name || !keyContent) { if (status) { - status.textContent = 'Key name and content are required'; + status.textContent = this.t('toast.keyNameContentRequired', 'Key name and content are required'); status.classList.add('error'); } return; diff --git a/static/js/session-manager.js b/static/js/session-manager.js index 454a0b64..0f9f01a0 100644 --- a/static/js/session-manager.js +++ b/static/js/session-manager.js @@ -329,7 +329,7 @@ const SessionManager = { const tmuxBadge = document.createElement('span'); tmuxBadge.className = 'tab-tmux-badge material-icons'; tmuxBadge.textContent = 'push_pin'; - tmuxBadge.title = 'Persistent tmux session' + (sess.tmuxSessionName ? ': ' + sess.tmuxSessionName : ''); + tmuxBadge.title = (window.i18n ? i18n.t('session.persistentTmux') : 'Persistent session (tmux)') + (sess.tmuxSessionName ? ': ' + sess.tmuxSessionName : ''); tab.appendChild(tmuxBadge); } const tabReconnect = document.createElement('span'); @@ -676,8 +676,11 @@ const SessionManager = { tabClose.dataset.pendingId = requestId; tabClose.classList.add('material-icons'); tabClose.textContent = 'close'; - tabClose.setAttribute('aria-label', 'Cancel connection'); - tabClose.setAttribute('title', 'Cancel connection'); + const cancelLabel = window.i18n ? i18n.t('connection.authBannerCancel') : 'Cancel connection'; + tabClose.setAttribute('aria-label', cancelLabel); + tabClose.setAttribute('title', cancelLabel); + tabClose.dataset.i18nAriaLabel = 'connection.authBannerCancel'; + tabClose.dataset.i18nTitle = 'connection.authBannerCancel'; tab.appendChild(statusDot); tab.appendChild(tabLabel); diff --git a/static/js/sftp-file-manager.js b/static/js/sftp-file-manager.js index 6f44315a..e4f7d21d 100644 --- a/static/js/sftp-file-manager.js +++ b/static/js/sftp-file-manager.js @@ -1528,9 +1528,9 @@ class SFTPFileManager extends window.FileDirectoryRequests {
Allow interactive gateway connections using user:target for all users. Disabled by default. Disabling blocks new connections and unfinished gateway authentication; established sessions remain connected. Reload open workspaces after changing this setting.
Show links to support WebSSH development in the account menu and the Settings side panel for all users.
Run the copied block yourself at an empty Bash prompt in this pane. No configuration files are changed. Wait for detection, then press Enter. The helper stays until this shell exits, even with sync off.