fix: improve UI and login functionality
Build and Push Multi-Arch Docker Image / build (push) Has been cancelled

* update UI components and layout
* fix login form functionality
* pin Docker base image to python:3.11-slim-bookworm
This commit is contained in:
dido committed 2026-09-11 10:41:43 +03:00
1 parent 21e03aa990
commit ae023525ec
4 files changed
+77 -33

No files matched your search

+2 -2
View File
@@ -1,6 +1,6 @@
# Multi-stage Dockerfile for WebSSH - Optimized for small image size # Multi-stage Dockerfile for WebSSH - Optimized for small image size
# Stage 1: Builder - compiles Python packages with build dependencies # 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 # Install build dependencies needed for compiling cryptography/pynacl on arm/v7
RUN apt-get update && apt-get install -y --no-install-recommends \ 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 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 # Stage 2: Final - minimal runtime image
FROM python:3.11-slim FROM python:3.11-slim-bookworm
# Install only runtime dependencies (no build tools) # Install only runtime dependencies (no build tools)
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
+8 -1
View File
@@ -19,6 +19,7 @@
<div class="modal-overlay active" id="loginModal"> <div class="modal-overlay active" id="loginModal">
<div class="modal"> <div class="modal">
<h3>StrixWebSSH Login</h3> <h3>StrixWebSSH Login</h3>
<form onsubmit="event.preventDefault(); login()">
<div class="form-group"> <div class="form-group">
<label>Username</label> <label>Username</label>
<input id="loginUsername" placeholder="admin"> <input id="loginUsername" placeholder="admin">
@@ -29,8 +30,9 @@
</div> </div>
<div id="loginError" style="color: #ef4444; font-size: 13px; margin-bottom: 10px;"></div> <div id="loginError" style="color: #ef4444; font-size: 13px; margin-bottom: 10px;"></div>
<div class="modal-actions"> <div class="modal-actions">
<button onclick="login()">Login</button> <button type="submit">Login</button>
</div> </div>
</form>
</div> </div>
</div> </div>
@@ -42,6 +44,11 @@
<span id="loggedInUser" style="font-size: 12px; color: #10b981;"></span> <span id="loggedInUser" style="font-size: 12px; color: #10b981;"></span>
<button class="btn-small" onclick="logout()" style="background: #374151; color: #9ca3af;">Logout</button> <button class="btn-small" onclick="logout()" style="background: #374151; color: #9ca3af;">Logout</button>
</div> </div>
<div class="sidebar-actions">
<button id="terminalToggleBtn" class="btn-sidebar-action btn-terminal-action active" onclick="hideSftpPanel()" title="Switch to Terminal">💻 Terminal</button>
<button id="sftpToggleBtn" class="btn-sidebar-action btn-sftp-action" onclick="toggleSftpView()" title="Switch to File Manager">📁 SFTP</button>
<button class="btn-sidebar-action" onclick="showHelp()" title="Open Help">❔ Help</button>
</div>
<button class="btn-add" onclick="showNewConnectionModal()">+ New Connection</button> <button class="btn-add" onclick="showNewConnectionModal()">+ New Connection</button>
<button class="btn-recordings" onclick="showRecordings()" title="Session Recordings">🎥 Recordings</button> <button class="btn-recordings" onclick="showRecordings()" title="Session Recordings">🎥 Recordings</button>
</div> </div>
+27
View File
@@ -45,6 +45,33 @@ body {
.btn-add:hover { background: #059669; } .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 { .connection-list {
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
+40 -30
View File
@@ -5,19 +5,16 @@ let currentConnectionId = null;
let authToken = localStorage.getItem('strixwebssh_token'); let authToken = localStorage.getItem('strixwebssh_token');
let currentUser = localStorage.getItem('strixwebssh_user'); let currentUser = localStorage.getItem('strixwebssh_user');
let storedUsers = {}; // Will be loaded from /api/config 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) // Dynamically determine API URL based on current host (same origin since everything is in container)
let apiBaseUrl = window.location.origin; let apiBaseUrl = window.location.origin;
let backendWsPort = null; let backendWsPort = null;
let backendWsHost = 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() { function getWsUrl() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const host = backendWsHost || window.location.hostname || 'localhost'; return `${protocol}//${window.location.host}/ws`;
if (backendWsPort) {
return `${protocol}//${host}:${backendWsPort}`;
}
return `${protocol}//${host}:8081`;
} }
const CONNECTIONS_KEY = 'strixwebssh_connections'; const CONNECTIONS_KEY = 'strixwebssh_connections';
@@ -380,10 +377,15 @@ function toggleEditModalAuth() {
} }
function toggleAuthFields() { function toggleAuthFields() {
const authType = document.getElementById('authType').value; const authTypeField = document.getElementById('authType');
document.getElementById('password').style.display = authType === 'key' ? 'none' : 'block'; 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 // 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() { function toggleKeyField() {
@@ -441,6 +443,7 @@ function renderConnectionList() {
: (conn.socket && conn.socket.readyState === WebSocket.CONNECTING ? 'Loading...' : '—'); : (conn.socket && conn.socket.readyState === WebSocket.CONNECTING ? 'Loading...' : '—');
const item = document.createElement('div'); const item = document.createElement('div');
item.className = `connection-item ${conn.id === currentConnectionId ? 'active' : ''} ${conn.connected ? 'connected' : 'disconnected'}`; item.className = `connection-item ${conn.id === currentConnectionId ? 'active' : ''} ${conn.connected ? 'connected' : 'disconnected'}`;
item.dataset.connectionId = conn.id;
item.onclick = () => selectConnection(conn.id); item.onclick = () => selectConnection(conn.id);
item.innerHTML = ` item.innerHTML = `
<div class="connection-heading"> <div class="connection-heading">
@@ -456,7 +459,7 @@ function renderConnectionList() {
? `<button class="btn-small btn-disconnect" onclick="disconnectConnection('${conn.id}')">Disconnect</button>` ? `<button class="btn-small btn-disconnect" onclick="disconnectConnection('${conn.id}')">Disconnect</button>`
: `<button class="btn-small btn-connect" onclick="connectConnection('${conn.id}')">Connect</button>` : `<button class="btn-small btn-connect" onclick="connectConnection('${conn.id}')">Connect</button>`
} }
<button class="btn-small btn-settings" onclick="showEditConnectionModal('${conn.id}')">Settings</button> <button class="btn-small btn-edit" onclick="showEditConnectionModal('${conn.id}')">Edit</button>
<button class="btn-small btn-delete" onclick="deleteConnection('${conn.id}')">Delete</button> <button class="btn-small btn-delete" onclick="deleteConnection('${conn.id}')">Delete</button>
</div> </div>
`; `;
@@ -468,16 +471,22 @@ function selectConnection(id) {
currentConnectionId = id; currentConnectionId = id;
const conn = getCurrentConnection(); const conn = getCurrentConnection();
if (conn) { if (conn) {
document.getElementById('connName').value = conn.name; const quickConnectFields = {
document.getElementById('host').value = conn.host; connName: conn.name,
document.getElementById('port').value = conn.port; host: conn.host,
document.getElementById('user').value = conn.username; port: conn.port,
document.getElementById('authType').value = conn.authType || 'password'; user: conn.username,
document.getElementById('password').value = conn.password || ''; authType: conn.authType || 'password',
document.getElementById('sshKey').value = conn.key || ''; password: conn.password || '',
document.getElementById('lockTimeout').value = conn.lockTimeout || 15; sshKey: conn.key || '',
document.getElementById('disconnectTimeout').value = conn.disconnectTimeout || 60; lockTimeout: conn.lockTimeout || 15,
toggleAuthFields(); disconnectTimeout: conn.disconnectTimeout || 60
};
Object.entries(quickConnectFields).forEach(([fieldId, value]) => {
const field = document.getElementById(fieldId);
if (field) field.value = value;
});
if (document.getElementById('authType')) toggleAuthFields();
document.getElementById('terminalTitle').textContent = `${conn.name} - ${conn.username}@${conn.host}:${conn.port}`; document.getElementById('terminalTitle').textContent = `${conn.name} - ${conn.username}@${conn.host}:${conn.port}`;
updateTerminalStatus(conn.connected ? 'connected' : 'disconnected'); updateTerminalStatus(conn.connected ? 'connected' : 'disconnected');
@@ -776,8 +785,6 @@ let sftpSelectedFile = null;
let sftpSelectedFileIsDir = false; let sftpSelectedFileIsDir = false;
let sftpUploadQueue = []; let sftpUploadQueue = [];
let currentSftpConnection = null; let currentSftpConnection = null;
let maxUploadSize = localStorage.getItem('maxUploadSize') ? parseInt(localStorage.getItem('maxUploadSize')) : 100 * 1024 * 1024; // Try to get from localStorage, default 100MB
// Load configuration from API // Load configuration from API
function loadConfig() { function loadConfig() {
console.log('loadConfig() called, token exists:', !!localStorage.getItem('strixwebssh_token')); console.log('loadConfig() called, token exists:', !!localStorage.getItem('strixwebssh_token'));
@@ -1325,20 +1332,25 @@ function toggleSftpView() {
const sftpView = document.getElementById('sftpView'); const sftpView = document.getElementById('sftpView');
const sftpToggleBtn = document.getElementById('sftpToggleBtn'); const sftpToggleBtn = document.getElementById('sftpToggleBtn');
if (sftpView.style.display === 'none') { if (window.getComputedStyle(sftpView).display === 'none') {
// Show SFTP, hide terminal // Show SFTP, hide terminal
const conn = getCurrentConnection(); const conn = getCurrentConnection() || connections.find(connection =>
connection.id === document.querySelector('.connection-item.active')?.dataset.connectionId
);
if (!conn) { if (!conn) {
alert('Please select a connection first'); alert('Please select a connection first');
return; return;
} }
if (currentConnectionId !== conn.id) {
selectConnection(conn.id);
}
currentSftpConnection = conn; currentSftpConnection = conn;
terminalView.style.display = 'none'; terminalView.style.display = 'none';
sftpView.style.display = 'flex'; sftpView.style.display = 'flex';
sftpToggleBtn.classList.add('active'); sftpToggleBtn.classList.add('active');
sftpToggleBtn.textContent = '💻 Terminal'; document.getElementById('terminalToggleBtn').classList.remove('active');
sftpToggleBtn.title = 'Switch to Terminal';
// Connect SFTP // Connect SFTP
sftpConnect(conn); sftpConnect(conn);
@@ -1347,8 +1359,7 @@ function toggleSftpView() {
terminalView.style.display = 'flex'; terminalView.style.display = 'flex';
sftpView.style.display = 'none'; sftpView.style.display = 'none';
sftpToggleBtn.classList.remove('active'); sftpToggleBtn.classList.remove('active');
sftpToggleBtn.textContent = '📁 SFTP'; document.getElementById('terminalToggleBtn').classList.add('active');
sftpToggleBtn.title = 'Switch to File Manager';
} }
} }
@@ -1361,8 +1372,7 @@ function hideSftpPanel() {
terminalView.style.display = 'flex'; terminalView.style.display = 'flex';
sftpView.style.display = 'none'; sftpView.style.display = 'none';
sftpToggleBtn.classList.remove('active'); sftpToggleBtn.classList.remove('active');
sftpToggleBtn.textContent = '📁 SFTP'; document.getElementById('terminalToggleBtn').classList.add('active');
sftpToggleBtn.title = 'Switch to File Manager';
} }
function showSftpPanel() { function showSftpPanel() {