fix: improve UI and login functionality
Build and Push Multi-Arch Docker Image / build (push) Has been cancelled
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:
1 parent
21e03aa990
commit
ae023525ec
4 files changed
+88
-44
No files matched your search
@@ -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 \
|
||||||
|
|||||||
+19
-12
@@ -19,18 +19,20 @@
|
|||||||
<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>
|
||||||
<div class="form-group">
|
<form onsubmit="event.preventDefault(); login()">
|
||||||
<label>Username</label>
|
<div class="form-group">
|
||||||
<input id="loginUsername" placeholder="admin">
|
<label>Username</label>
|
||||||
</div>
|
<input id="loginUsername" placeholder="admin">
|
||||||
<div class="form-group">
|
</div>
|
||||||
<label>Password</label>
|
<div class="form-group">
|
||||||
<input type="password" id="loginPassword" placeholder="Password">
|
<label>Password</label>
|
||||||
</div>
|
<input type="password" id="loginPassword" placeholder="Password">
|
||||||
<div id="loginError" style="color: #ef4444; font-size: 13px; margin-bottom: 10px;"></div>
|
</div>
|
||||||
<div class="modal-actions">
|
<div id="loginError" style="color: #ef4444; font-size: 13px; margin-bottom: 10px;"></div>
|
||||||
<button onclick="login()">Login</button>
|
<div class="modal-actions">
|
||||||
</div>
|
<button type="submit">Login</button>
|
||||||
|
</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>
|
||||||
|
|||||||
@@ -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
@@ -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() {
|
||||||
|
|||||||
Reference in new issue
Block a user