diff --git a/deployment/Dockerfile b/deployment/Dockerfile index c5dd072..2bc1e16 100644 --- a/deployment/Dockerfile +++ b/deployment/Dockerfile @@ -1,6 +1,6 @@ # Multi-stage Dockerfile for WebSSH - Optimized for small image size # Stage 1: Builder - compiles Python packages with build dependencies -FROM python:3.11-slim AS builder +FROM python:3.11-slim-bookworm AS builder # Install build dependencies needed for compiling cryptography/pynacl on arm/v7 RUN apt-get update && apt-get install -y --no-install-recommends \ @@ -95,7 +95,7 @@ RUN set -eux; \ if command -v strip >/dev/null 2>&1; then strip --strip-unneeded /tmp/caddy_bin_dir/caddy || true; fi # Stage 2: Final - minimal runtime image -FROM python:3.11-slim +FROM python:3.11-slim-bookworm # Install only runtime dependencies (no build tools) RUN apt-get update && apt-get install -y --no-install-recommends \ diff --git a/src/index.html b/src/index.html index 713e62f..05776aa 100644 --- a/src/index.html +++ b/src/index.html @@ -19,18 +19,20 @@
@@ -42,6 +44,11 @@ + diff --git a/src/static/webssh.css b/src/static/webssh.css index 5f66d96..fbfe263 100644 --- a/src/static/webssh.css +++ b/src/static/webssh.css @@ -45,6 +45,33 @@ body { .btn-add:hover { background: #059669; } +.sidebar-actions { + display: flex; + gap: 5px; + margin-bottom: 14px; + flex-wrap: nowrap; +} + +.btn-sidebar-action { + flex: 1; + min-width: 0; + padding: 7px 6px; + color: #e5e7eb; + font-size: 12px; + white-space: nowrap; +} + +.btn-terminal-action { background: #2563eb; } +.btn-terminal-action:hover, +.btn-terminal-action.active { background: #1d4ed8; } + +.btn-sftp-action { background: #d97706; } +.btn-sftp-action:hover, +.btn-sftp-action.active { background: #b45309; } + +.btn-sidebar-action:not(.btn-terminal-action):not(.btn-sftp-action) { background: #7c3aed; } +.btn-sidebar-action:not(.btn-terminal-action):not(.btn-sftp-action):hover { background: #6d28d9; } + .connection-list { flex: 1; overflow-y: auto; diff --git a/src/static/webssh.js b/src/static/webssh.js index 6f5a82a..a8b42ff 100644 --- a/src/static/webssh.js +++ b/src/static/webssh.js @@ -5,19 +5,16 @@ let currentConnectionId = null; let authToken = localStorage.getItem('strixwebssh_token'); let currentUser = localStorage.getItem('strixwebssh_user'); let storedUsers = {}; // Will be loaded from /api/config +let maxUploadSize = localStorage.getItem('maxUploadSize') ? parseInt(localStorage.getItem('maxUploadSize')) : 100 * 1024 * 1024; // Dynamically determine API URL based on current host (same origin since everything is in container) let apiBaseUrl = window.location.origin; let backendWsPort = null; let backendWsHost = null; -// Use the backend WebSocket port reported by the API config. +// Use the frontend proxy so the browser does not need direct access to the backend port. function getWsUrl() { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; - const host = backendWsHost || window.location.hostname || 'localhost'; - if (backendWsPort) { - return `${protocol}//${host}:${backendWsPort}`; - } - return `${protocol}//${host}:8081`; + return `${protocol}//${window.location.host}/ws`; } const CONNECTIONS_KEY = 'strixwebssh_connections'; @@ -380,10 +377,15 @@ function toggleEditModalAuth() { } function toggleAuthFields() { - const authType = document.getElementById('authType').value; - document.getElementById('password').style.display = authType === 'key' ? 'none' : 'block'; + const authTypeField = document.getElementById('authType'); + const passwordField = document.getElementById('password'); + if (!authTypeField || !passwordField) return; + + const authType = authTypeField.value; + passwordField.style.display = authType === 'key' ? 'none' : 'block'; // Don't auto-show key section - let user control it with toggle button - document.getElementById('keySection').classList.remove('active'); + const keySection = document.getElementById('keySection'); + if (keySection) keySection.classList.remove('active'); } function toggleKeyField() { @@ -441,6 +443,7 @@ function renderConnectionList() { : (conn.socket && conn.socket.readyState === WebSocket.CONNECTING ? 'Loading...' : '—'); const item = document.createElement('div'); item.className = `connection-item ${conn.id === currentConnectionId ? 'active' : ''} ${conn.connected ? 'connected' : 'disconnected'}`; + item.dataset.connectionId = conn.id; item.onclick = () => selectConnection(conn.id); item.innerHTML = `