Redesign SFTP workspace and fix server monitoring

Replace Bootstrap dependencies with custom blue-accented light and dark themes and native dialogs.

Share the sidebar, top bar and responsive layout across file browsing, account settings, user management, server statistics and file previews. Add folder search, type filters, upload progress and asset cache versioning.

Fix SSH configuration usage, filesystem discovery, missing statistics and RAID health parsing. Adapt PDF sizing and fullscreen controls to the shared workspace.

Validation: PHP workspace and server statistics regression tests, JavaScript syntax checks, responsive browser checks and live XAMPP navigation.
This commit is contained in:
Michal Sedlák
2026-10-16 10:20:55 +02:00
parent d15fb27fd1
commit 565fd18379
26 changed files with 1058 additions and 1964 deletions
+11 -47
View File
@@ -1,47 +1,11 @@
html, body {
height: 100%;
margin: 0;
}
body {
display: flex;
flex-direction: column;
}
main {
flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
padding-top: 7%;
}
.fixed {
-ms-flex: 0 0 300px;
flex: 0 0 300px;
min-width: 300px;
}
.hover-effect {
transition: opacity 0.3s ease;
}
.hover-effect:hover {
opacity: 0.8;
}
.theme-toggle {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
}
.theme-light .dark-logo {
display: none;
}
.theme-dark .light-logo {
display: none;
}
.login-page { display:flex; min-height:100dvh; flex-direction:column; }
.login-top,.login-footer { display:flex; align-items:center; justify-content:space-between; padding:28px 5vw; gap:16px; }
.login-brand { display:flex; align-items:center; gap:12px; font-size:19px; color:var(--text); font-weight:750; letter-spacing:-.03em; } .login-brand > span { display:grid; place-items:center; background:#2563eb; color:white; width:36px; height:36px; border-radius:9px; font-size:25px; }
.login-layout { flex:1; width:min(1060px,90%); margin:48px auto; display:grid; grid-template-columns:1.1fr 1fr; align-items:center; gap:100px; }
.login-story h1 { font-size:clamp(44px,5vw,64px); margin:16px 0 20px; line-height:1.08; letter-spacing:-.055em; } .login-story h1 > span { color:var(--blue); } .login-story > p { max-width:370px; color:var(--muted); font-size:15px; line-height:1.8; }
.login-card { background:var(--surface); padding:40px; border:1px solid var(--line); border-radius:18px; box-shadow:var(--shadow); } .login-card h2 { margin:12px 0 8px; font-size:24px; } .login-card form { margin-top:30px; } .login-card label { font-size:12px; } .login-card .btn { width:100%; justify-content:space-between; padding:13px 16px; } .login-note { margin-top:28px; padding-top:22px; border-top:1px solid var(--line); font-size:11px; color:var(--muted); text-align:center; }
.storage-illustration { max-width:310px; margin-top:35px; padding:18px; border:1px solid var(--line); border-radius:14px; background:var(--surface); transform:rotate(-3deg); box-shadow:12px 14px 0 var(--blue-soft); }
.storage-top { display:flex; justify-content:space-between; font-size:10px; font-weight:700; letter-spacing:.12em; margin-bottom:14px; } .storage-top span { color:var(--blue); } .storage-drive { display:flex; align-items:center; gap:16px; height:38px; padding:0 12px; border:1px solid var(--line); border-radius:6px; margin:6px 0; background:var(--bg); } .storage-drive b { font:10px ui-monospace,monospace; color:var(--muted); } .storage-drive > span { flex:1; height:5px; border-radius:4px; background:var(--line); } .storage-drive i { width:5px; height:5px; border-radius:50%; background:var(--blue); } .storage-bottom { color:var(--muted); font-size:7px; letter-spacing:.14em; margin-top:14px; }
.login-footer { font-size:11px; } .login-footer a { color:var(--muted); }
@media(max-width:800px) { .login-layout { grid-template-columns:1fr 1fr; gap:30px; } .login-card { padding:26px; } }
@media(max-width:620px) { .login-layout { display:block; max-width:430px; margin:24px auto; } .login-story { margin-bottom:30px; } .login-story h1 { font-size:38px; } .login-story h1 br { display:none; } .storage-illustration { display:none; } .login-story > p { font-size:13px; } .login-top,.login-footer { padding:20px; } .login-footer { flex-wrap:wrap; } }
+71 -181
View File
@@ -1,181 +1,71 @@
/* Modern dark theme with blue accents */
:root {
--bg-dark: #171b22;
--bg-dark2: #1a202c;
--primary-blue: #3b82f6;
--text-light: #e5e7eb;
--bg-darkblue: #101722;
--container-bg-start: #1a2029;
--container-bg-end: #151d29;
--sftp-bg-start: #151b25;
--sftp-bg-end: #101722;
--container-border: rgba(77, 145, 255, 0.7);
--panel-text: #e5e7eb;
--panel-muted: #aab4c3;
--control-bg: #101722;
--control-border: #39485d;
}
body {
background: linear-gradient(135deg, var(--bg-dark), var(--bg-darkblue));
color: var(--text-light);
min-height: 100vh;
font-family: system-ui, sans-serif;
margin: 0;
padding: 0;
}
a, .nav-link {
color: var(--primary-blue) !important;
transition: opacity .2s ease-in-out;
}
body, button, input, select, textarea {
font-family: system-ui, sans-serif;
}
/* Keep text, fields and tables readable on every page that uses the shared theme. */
.surface-panel,
.dark-box {
color: var(--panel-text);
}
.surface-panel .text-muted,
.dark-box .text-muted {
color: var(--panel-muted) !important;
}
.surface-panel .form-control,
.surface-panel .form-select,
.dark-box .form-control,
.dark-box .form-select {
color: var(--panel-text);
background-color: var(--control-bg);
border-color: var(--control-border);
}
.surface-panel .form-control::placeholder,
.dark-box .form-control::placeholder {
color: var(--panel-muted);
}
.surface-panel .form-control:focus,
.surface-panel .form-select:focus,
.dark-box .form-control:focus,
.dark-box .form-select:focus {
border-color: var(--primary-blue);
box-shadow: 0 0 0 .2rem rgba(59, 130, 246, .2);
}
.surface-panel .table,
.surface-panel .table th,
.surface-panel .table td {
color: inherit;
}
a:hover, .btn:hover, .nav-link:hover {
opacity: 0.85;
}
/* Ensure action buttons have white text for visibility */
.btn-primary,
.btn-danger,
.btn-warning,
.btn-success,
.btn-info,
.btn-secondary {
color: var(--text-light) !important;
}
/* Override specific actions (update password, upload directory, rename) to have black text */
.btn-primary,
.btn-warning,
.btn-info {
color: #212529 !important;
}
.dark-box {
background: rgba(0,0,0,0.6);
border-radius: var(--border-radius);
padding: 1rem;
backdrop-filter: blur(8px);
}
.surface-panel {
background: linear-gradient(145deg, var(--sftp-bg-start), var(--sftp-bg-end));
color: var(--text-light);
border: 1px solid rgba(77, 145, 255, 0.3);
border-radius: 1rem;
box-shadow: 0 12px 32px rgba(3, 8, 18, 0.2);
}
.modal-content {
background: rgba(20, 20, 30, 0.9) !important;
color: var(--text-light) !important;
border: none;
}
.table {
color: var(--text-light) !important;
}
.table th, .table td {
border-color: rgba(255,255,255,0.1) !important;
}
/* Main window styling (window within window) */
.custom-container.dark-box {
box-sizing: border-box;
margin: 2rem auto;
background: linear-gradient(135deg, var(--container-bg-start), var(--container-bg-end));
border: 2px solid var(--container-border);
border-radius: var(--border-radius);
padding: 1rem;
backdrop-filter: blur(6px);
}
/* Light mode overrides */
html[data-bs-theme="light"] {
--bg-light: #f8f9fa;
--bg-light2: #ffffff;
--container-bg: rgba(255,255,255,0.85);
--container-border: var(--primary-blue);
--text-dark: #212529;
--panel-text: #212529;
--panel-muted: #596579;
--control-bg: #ffffff;
--control-border: #c8d2df;
}
html[data-bs-theme="light"] body {
background: var(--bg-light);
color: var(--text-dark);
}
html[data-bs-theme="light"] .dark-box {
background: rgba(255, 255, 255, 0.8);
color: var(--text-dark);
}
html[data-bs-theme="light"] .surface-panel .form-control,
html[data-bs-theme="light"] .surface-panel .form-select,
html[data-bs-theme="light"] .dark-box .form-control,
html[data-bs-theme="light"] .dark-box .form-select {
color: var(--panel-text);
background-color: var(--control-bg);
border-color: var(--control-border);
}
html[data-bs-theme="light"] .form-control::placeholder {
color: var(--panel-muted);
}
html[data-bs-theme="light"] .surface-panel {
background: linear-gradient(145deg, #ffffff, #edf2f8);
color: var(--text-dark);
border-color: rgba(59, 130, 246, 0.3);
box-shadow: 0 12px 32px rgba(31, 58, 92, 0.08);
}
html[data-bs-theme="light"] .modal-content {
background: #fff !important;
color: var(--text-dark) !important;
}
html[data-bs-theme="light"] .table {
color: var(--text-dark) !important;
}
/* Shared UI primitives, independent of a CSS framework. */
:root { color-scheme: light; --bg:#f5f7fb; --surface:#fff; --surface-alt:#f0f4fa; --text:#17243b; --muted:#64748b; --line:#e0e7f0; --blue:#2563eb; --blue-soft:#eaf1ff; --danger:#c83b50; --success:#168364; --shadow:0 16px 48px #16294a09; --body-bg-rgb:245,247,251; }
:root[data-theme="dark"] { color-scheme:dark; --bg:#0c1220; --surface:#121c2d; --surface-alt:#19253a; --text:#e4ecfa; --muted:#96a6bf; --line:#26334a; --blue:#79a7ff; --blue-soft:#1c3053; --danger:#ff8797; --success:#64d3ae; --shadow:0 16px 48px #0002; --body-bg-rgb:12,18,32; }
* { box-sizing:border-box; }
body { margin:0; min-height:100vh; background:var(--bg); color:var(--text); font:14px/1.55 'Segoe UI',system-ui,sans-serif; }
button,input,select,textarea { font:inherit; }
a { color:var(--blue); text-decoration:none; } a:hover { text-decoration:underline; }
:focus-visible { outline:3px solid var(--blue); outline-offset:3px; }
[hidden] { display:none !important; }
h1,h2,h3,h4,h5,p { margin:0 0 1rem; }
h1 { font-size:clamp(26px,3vw,36px); letter-spacing:-.045em; line-height:1.2; }
h2 { font-size:23px; letter-spacing:-.03em; } h3 { font-size:18px; } h4,h5 { font-size:16px; }
small,.small,.form-text { font-size:12px; }
.text-muted,.form-text { color:var(--muted); } .text-danger { color:var(--danger); } .text-success { color:var(--success); }
.text-center { text-align:center; } .text-end { text-align:right; } .text-break,.text-wrap { overflow-wrap:anywhere; } .fw-bold { font-weight:650; }
.btn { display:inline-flex; justify-content:center; align-items:center; gap:8px; min-height:38px; padding:8px 14px; border:1px solid var(--line); border-radius:8px; background:var(--surface); color:var(--text); cursor:pointer; font-weight:600; line-height:1.4; text-decoration:none; transition:background .15s,border-color .15s; }
.btn:hover { background:var(--surface-alt); border-color:var(--muted); text-decoration:none; }
.btn-primary { background:#2563eb; border-color:#2563eb; color:#fff; } .btn-primary:hover { background:#1d4ed8; border-color:#1d4ed8; }
.btn-danger { color:var(--danger); } .btn-success,.btn-info { color:var(--blue); }
.btn-sm { min-height:32px; padding:6px 10px; font-size:12px; } .btn:disabled,.btn.disabled { opacity:.5; cursor:default; }
.btn-group,.input-group { display:flex; gap:8px; }
.form-control,.form-select { display:block; width:100%; min-width:0; border:1px solid var(--line); border-radius:8px; background:var(--surface); color:var(--text); padding:11px 13px; }
.form-control::placeholder { color:var(--muted); } .form-control:focus,.form-select:focus { border-color:var(--blue); outline:3px solid var(--blue-soft); }
label,.form-label { display:inline-block; margin-bottom:6px; font-weight:600; } input[type="checkbox"] { accent-color:var(--blue); width:16px; height:16px; }
.form-check { display:flex; align-items:center; gap:8px; } .form-check label { margin:0; }
.surface-panel,.card { background:var(--surface); border:1px solid var(--line); border-radius:12px; padding:24px; }
.card-header { border-bottom:1px solid var(--line); padding:12px; } .card-body { padding:20px; }
.custom-container.dark-box { width:calc(100% - 48px); max-width:1280px; min-height:calc(100vh - 100px); margin:0 auto 24px; padding:24px; background:var(--surface); border:1px solid var(--line); border-radius:16px; box-shadow:var(--shadow); }
.custom-container > header { padding-bottom:20px; margin:8px 0 24px; }
table { width:100%; border-collapse:collapse; text-align:left; } th { color:var(--muted); font-size:11px; font-weight:650; letter-spacing:.06em; text-transform:uppercase; background:var(--surface-alt); }
th,td { padding:14px 16px; border-bottom:1px solid var(--line); overflow-wrap:anywhere; } tbody tr:hover { background:var(--surface-alt); }
.alert { padding:14px 16px; margin:16px 0; border:1px solid var(--line); border-radius:8px; background:var(--blue-soft); text-align:left; } .alert-danger { color:var(--danger); } .alert-success { color:var(--success); }
.badge { display:inline-block; padding:2px 7px; border-radius:5px; background:var(--surface-alt); color:var(--muted); font-size:10px; font-weight:600; }
.progress { height:8px; background:var(--surface-alt); border-radius:8px; overflow:hidden; } .progress-bar { height:100%; background:var(--blue); }
.bg-success { background:var(--success); } .bg-warning { background:#d99b32; } .bg-danger { background:var(--danger); }
.badge.bg-danger,.badge.bg-success,.badge.bg-warning { background:var(--surface-alt); color:var(--muted); }
.img-fluid { max-width:100%; height:auto; }
html:not([data-theme="dark"]) .dark-logo,[data-theme="dark"] .light-logo { display:none; }
footer { color:var(--muted); font-size:12px; } footer img { opacity:.7; max-height:22px; object-fit:contain; }
.theme-toggle { white-space:nowrap; }
.d-flex { display:flex; } .flex-column { flex-direction:column; } .flex-wrap { flex-wrap:wrap; } .justify-content-end { justify-content:flex-end; } .justify-content-center { justify-content:center; } .justify-content-between { justify-content:space-between; } .align-items-center { align-items:center; } .gap-3 { gap:16px; }
.row { display:flex; flex-wrap:wrap; gap:20px; } .col { flex:1; min-width:0; } .col-12 { width:100%; } .col-4 { flex:1; }
.container,.container-fluid { width:100%; margin-inline:auto; } .container { max-width:1280px; }
.border { border:1px solid var(--line); } .border-top { border-top:1px solid var(--line); } .border-bottom { border-bottom:1px solid var(--line); } .rounded { border-radius:10px; }
.position-relative { position:relative; } .w-100 { width:100%; } .h-100 { height:100%; } .w-auto { width:auto; } .mx-auto { margin-inline:auto; }
.p-2 { padding:12px; } .p-3 { padding:20px; } .m-3 { margin:20px; } .m-5 { margin:32px; }
.mb-1,.mt-1 { margin-block:4px; } .mb-2 { margin-bottom:8px; } .mb-3 { margin-bottom:16px; } .mb-4 { margin-bottom:24px; } .mt-2 { margin-top:8px; } .mt-3 { margin-top:16px; } .mt-4 { margin-top:24px; } .me-2 { margin-right:8px; }
dialog.modal { width:min(480px,calc(100% - 32px)); padding:0; border:1px solid var(--line); border-radius:16px; background:var(--surface); color:var(--text); box-shadow:0 24px 100px #0005; max-height:calc(100dvh - 40px); }
dialog::backdrop { background:#080f2199; backdrop-filter:blur(4px); }
.modal-header,.modal-footer { padding:20px 24px; display:flex; align-items:center; justify-content:space-between; gap:12px; } .modal-header { border-bottom:1px solid var(--line); } .modal-title { margin:0; } .modal-body { padding:24px; } .modal-footer { justify-content:flex-end; border-top:1px solid var(--line); }
.btn-close { border:0; background:transparent; color:var(--muted); cursor:pointer; font-size:24px; } .btn-close::before { content:'×'; } body:has(dialog[open]) { overflow:hidden; }
.eyebrow { color:var(--muted); font-size:10px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; }
@media(min-width:900px) { .col-md-6 { width:calc(50% - 10px); } .col-lg-4 { width:calc(33.333% - 14px); } }
@media(max-width:700px) { .custom-container.dark-box { width:calc(100% - 24px); padding:16px; } .surface-panel { overflow-x:auto; } .row { gap:12px; } .col-md-6,.col-lg-4 { width:100%; } th,td { padding:10px; } .m-5 { margin:16px; } }
@media(prefers-reduced-motion:reduce) { *,*::before,*::after { transition:none !important; animation:none !important; } }
/* Small local icon vocabulary used by secondary screens. */
.ui-icon { font-style:normal; display:inline-block; }
.icon-chevron-left::before { content:'‹'; } .icon-chevron-right::before { content:'›'; }
.icon-fullscreen::before { content:'⛶'; } .icon-fullscreen-exit::before { content:'⊡'; }
.icon-share::before { content:'↗'; } .icon-clipboard::before { content:'▤'; }
.icon-check::before,.icon-check-circle-fill::before { content:'✓'; }
.icon-x-circle-fill::before { content:'×'; } .icon-exclamation-triangle::before { content:'!'; }
.icon-server::before,.icon-hdd::before,.icon-hdd-stack::before { content:'▤'; }
.icon-clock::before { content:'◷'; } .icon-cpu::before { content:'▦'; } .icon-memory::before { content:'▥'; }
.text-primary { color:var(--blue); } .text-warning { color:#c98d22; }
.spinner-border { display:inline-block; width:14px; height:14px; border:2px solid var(--line); border-top-color:var(--blue); border-radius:50%; animation:spin .8s linear infinite; }
@keyframes spin { to { transform:rotate(360deg); } }
/* Secondary screens share the same surfaces and spacing. */
.col-lg-10 { width:100%; } .py-4 { padding-block:24px; } .mb-0 { margin-bottom:0; } .h4 { font-size:22px; } .h5 { font-size:16px; }
.card-header.bg-success,.card-header.bg-warning,.card-header.bg-danger,.card-header.bg-primary,.card-header.bg-info { background:var(--surface-alt); }
.custom-container.dark-box.fullscreen-mode { max-width:none; width:100%; height:100dvh; margin:0; border:0; border-radius:0; padding:0; }
+62
View File
@@ -0,0 +1,62 @@
/* Persistent workspace frame shared by every signed-in page and shared previews. */
.file-app { display:grid; grid-template-columns:236px minmax(0,1fr); text-align:left; }
.skip-link { position:fixed; left:250px; top:-60px; z-index:10000; padding:10px 16px; background:var(--surface); border:1px solid var(--blue); border-radius:8px; }
.skip-link:focus { top:12px; }
.sidebar { position:sticky; top:0; height:100dvh; padding:30px 20px 16px; display:flex; flex-direction:column; background:var(--surface); border-right:1px solid var(--line); overflow-y:auto; }
.brand { display:flex; align-items:center; gap:12px; color:var(--text); font-size:19px; font-weight:750; letter-spacing:-.04em; text-decoration:none; }
.brand-mark { display:grid; place-items:center; width:40px; height:40px; border-radius:11px; background:#2563eb; color:white; font-size:27px; }
.brand-caption { display:block; font-size:9px; font-weight:600; letter-spacing:.16em; color:var(--muted); margin-top:1px; }
.nav-heading { margin:48px 12px 12px; }
.nav-item { display:flex; align-items:center; gap:12px; padding:11px 12px; margin:4px 0; color:var(--muted); border-radius:8px; font-weight:550; }
.nav-item:hover { text-decoration:none; background:var(--surface-alt); color:var(--text); }
.nav-item.active { background:var(--blue-soft); color:var(--blue); }
.nav-item > span { font-size:20px; width:21px; text-align:center; }
.sidebar-bottom { margin-top:auto; padding-top:40px; }
.connection-card { border:1px solid var(--line); border-radius:10px; padding:14px 12px; font-size:12px; font-weight:600; margin-bottom:18px; }
.status-dot { display:inline-block; width:6px; height:6px; background:var(--success); border-radius:50%; margin-right:7px; }
.connection-card small { display:block; margin-top:5px; color:var(--muted); font-weight:400; font-size:10px; }
.user-card { display:flex; gap:10px; align-items:center; padding:16px 4px 0; margin-top:14px; border-top:1px solid var(--line); }
.avatar { display:grid; place-items:center; background:var(--blue-soft); color:var(--blue); width:34px; height:34px; border-radius:50%; font-weight:700; flex-shrink:0; }
.user-name { flex:1; overflow-wrap:anywhere; font-weight:600; font-size:12px; }
.user-name small { display:block; font-size:10px; font-weight:400; color:var(--muted); }
.workspace { min-width:0; display:flex; flex-direction:column; min-height:100dvh; }
.topbar { min-height:76px; display:flex; justify-content:space-between; align-items:center; gap:12px; padding:16px 40px; border-bottom:1px solid var(--line); background:var(--surface); color:var(--muted); font-size:12px; }
.topbar strong { color:var(--text); font-weight:500; }
.topbar-divider { padding:0 14px; opacity:.5; }
.file-main { flex:1; width:100%; max-width:1560px; margin:0 auto; padding:42px 40px 0; display:flex; flex-direction:column; min-width:0; }
.workspace-content { flex:1; min-width:0; }
.page-heading { display:flex; align-items:center; justify-content:space-between; gap:20px; margin-bottom:30px; }
.page-heading .eyebrow { margin-bottom:9px; }
.page-heading h1 { margin-bottom:10px; font-size:36px; overflow-wrap:anywhere; }
.heading-dot { color:var(--blue); }
.page-heading p { color:var(--muted); margin:0; font-size:13px; }
.heading-back { white-space:nowrap; font-size:12px; flex-shrink:0; }
.protocol-badge { border:1px solid var(--line); border-radius:6px; padding:5px 10px; font:11px ui-monospace,monospace; background:var(--surface); white-space:nowrap; }
.workspace-footer { display:flex; align-items:center; justify-content:space-between; gap:16px; padding:22px 0; font-size:10px; margin:0; border:0; }
.workspace-footer a { color:var(--muted); }
.content-panel { padding:24px; border:1px solid var(--line); border-radius:12px; background:var(--surface); box-shadow:var(--shadow); min-width:0; }
.panel-heading { margin-bottom:22px; } .panel-heading h2 { margin-bottom:6px; font-size:18px; } .panel-heading p { margin:0; color:var(--muted); font-size:12px; }
.content-toolbar { display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:12px; margin-bottom:20px; font-size:12px; color:var(--muted); }
.content-toolbar p { margin:0; }
.table-scroll { overflow-x:auto; max-width:100%; }
@media(min-width:1500px) { .file-main { padding-top:56px; } }
@media(max-width:1100px) { .file-app { grid-template-columns:200px minmax(0,1fr); } .sidebar { padding-inline:14px; } .nav-item { font-size:13px; } .file-main { padding:28px 24px 0; } .topbar { padding-inline:24px; } }
@media(max-width:760px) {
.file-app { display:block; }
.sidebar { position:static; height:auto; padding:16px; border-right:0; border-bottom:1px solid var(--line); overflow:visible; }
.brand-caption,.nav-heading,.connection-card { display:none; }
.sidebar nav { display:flex; flex-wrap:wrap; gap:6px; margin-top:14px; }
.nav-item { font-size:12px; padding:7px 10px; margin:0; }
.sidebar-bottom { margin:8px 0 0; padding:0; display:flex; }
.sidebar-bottom .nav-item { padding:4px 10px; }
.sidebar-bottom .user-card { display:flex; margin:0 0 0 auto; padding:0 10px; border:0; }
.sidebar-bottom .user-name,.sidebar-bottom .avatar { display:none; }
.topbar { min-height:58px; padding:12px 16px; }
.file-main { padding:26px 16px 0; }
.page-heading { align-items:flex-start; flex-wrap:wrap; }
.page-heading h1 { font-size:30px; }
.protocol-badge { display:none; }
.workspace-footer { flex-wrap:wrap; }
.content-panel { padding:18px; }
.skip-link { left:16px; }
}