From 813d7d5099d3b1ac4c2f4547a3a63f4726442658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Sun, 19 Oct 2025 15:07:15 +0200 Subject: [PATCH] visual refactor --- static/js/ui.js | 63 ++++++++++ static/styles.css | 270 ++++++++++++++++--------------------------- templates/index.html | 199 ++++++++++++++++++++----------- 3 files changed, 296 insertions(+), 236 deletions(-) create mode 100644 static/js/ui.js diff --git a/static/js/ui.js b/static/js/ui.js new file mode 100644 index 0000000..3d06dae --- /dev/null +++ b/static/js/ui.js @@ -0,0 +1,63 @@ +// static/js/ui.js +(() => { + const docEl = document.documentElement; + + // ---- THEME ---- + const THEME_KEY = "pve-ui-theme"; + const storedTheme = localStorage.getItem(THEME_KEY); + if (storedTheme === "light" || storedTheme === "dark") { + docEl.setAttribute("data-bs-theme", storedTheme); + } + + const btnTheme = document.getElementById("btnTheme"); + if (btnTheme) { + btnTheme.addEventListener("click", () => { + const current = docEl.getAttribute("data-bs-theme") === "light" ? "dark" : "light"; + docEl.setAttribute("data-bs-theme", current); + localStorage.setItem(THEME_KEY, current); + btnTheme.innerHTML = current === "dark" + ? ' Theme' + : ' Theme'; + }); + } + + // ---- DENSITY ---- + const DENSITY_KEY = "pve-ui-density"; + const storedDensity = localStorage.getItem(DENSITY_KEY); + if (storedDensity === "compact") { + docEl.setAttribute("data-density", "compact"); + } + + const btnDensity = document.getElementById("btnDensity"); + if (btnDensity) { + btnDensity.addEventListener("click", () => { + const isCompact = docEl.getAttribute("data-density") === "compact"; + if (isCompact) { + docEl.removeAttribute("data-density"); + localStorage.setItem(DENSITY_KEY, "normal"); + } else { + docEl.setAttribute("data-density", "compact"); + localStorage.setItem(DENSITY_KEY, "compact"); + } + }); + } + + // ---- PRE blocks: keep nicely scrollable ---- + const pres = document.querySelectorAll("pre.pre-scrollable"); + pres.forEach((p) => { + p.style.maxHeight = "40vh"; + p.style.overflow = "auto"; + }); + + // Optional: subtle overflow shadows for horizontally scrollable areas + const scrollables = document.querySelectorAll(".table-responsive, .overflow-auto"); + scrollables.forEach((el) => { + el.addEventListener("scroll", () => { + // Add classes for left/right shadow indicators + const atStart = el.scrollLeft <= 0; + const atEnd = el.scrollLeft + el.clientWidth >= el.scrollWidth - 1; + el.classList.toggle("is-scrolled-start", !atStart); + el.classList.toggle("is-scrolled-end", !atEnd); + }); + }); +})(); diff --git a/static/styles.css b/static/styles.css index de1107b..5d879f8 100644 --- a/static/styles.css +++ b/static/styles.css @@ -1,198 +1,130 @@ -/* Dark theme */ -body { - background-color: #0f1115; +/* ========== Root, theme & density ========== */ +:root { + --radius: 14px; + --shadow-sm: 0 4px 12px rgba(0, 0, 0, .08); } -.card.health-card { - background: #101520; +html[data-density="compact"] .table-sm> :not(caption)>*>* { + padding: .3rem .4rem; } -.health-dot { +html[data-density="compact"] .input-group-sm>.form-control, +html[data-density="compact"] .form-select, +html[data-density="compact"] .btn-sm { + padding-top: .2rem; + padding-bottom: .2rem; +} + +/* Rounded cards / sections */ +.card { + border-radius: var(--radius); + box-shadow: var(--shadow-sm); +} + +.accordion .accordion-item { + border: none; +} + +.accordion-button { + border-radius: 0 !important; +} + +/* Navbar polish */ +.navbar .navbar-brand i { + font-size: 1.15rem; +} + +/* Health dot — controlled by JS via .ok / .bad */ +.health-card .health-dot { width: 12px; height: 12px; border-radius: 50%; - background: #dc3545; + background: var(--bs-secondary-color); + /* neutral by default */ + box-shadow: 0 0 0 4px rgba(0, 0, 0, .12) inset, 0 0 10px rgba(0, 0, 0, .1); } -.health-dot.ok { - background: #28a745; +/* green when OK, red when problem */ +.health-card .health-dot.ok { + background: var(--bs-success); } -.health-dot.bad { - background: #dc3545; +.health-card .health-dot.bad { + background: var(--bs-danger); } -/* Tables */ -.table td, -.table th { + +/* Tables: sticky head & compact borders */ +.table thead th { + position: sticky; + top: 0; + z-index: 2; + background: var(--bs-body-bg); +} + +.table thead { + border-bottom: 1px solid var(--bs-border-color); +} + +.table> :not(caption)>*>* { vertical-align: middle; } -/* Dividers */ -.vr { - width: 1px; - min-height: 1rem; - background: rgba(255, 255, 255, .15); -} - -/* --- horizontal scroll & nowrap for wide tables --- */ +/* Overflow shadows for scrollable containers (JS toggles classes) */ .table-responsive { - overflow-x: auto; + position: relative; + mask-image: linear-gradient(to right, transparent 0, black 12px, black calc(100% - 12px), transparent 100%); } -.table-nowrap { +.table-responsive.is-scrolled-start { + mask-image: linear-gradient(to right, black 0, black calc(100% - 12px), transparent 100%); +} + +.table-responsive.is-scrolled-end { + mask-image: linear-gradient(to right, transparent 0, black 12px, black 100%); +} + +/* Pre blocks */ +pre { + background: var(--bs-tertiary-bg); + border-radius: 10px; + padding: .75rem; + border: 1px solid var(--bs-border-color); +} + +/* Footer */ +.site-footer { + background: var(--bs-body-bg); +} + +/* Buttons & utilities */ +.btn .bi { + margin-right: .35rem; +} + +/* Make first column narrow-friendly */ +.w-1 { + width: 1%; white-space: nowrap; } -@media (min-width: 992px) { - .table-nowrap-lg-normal { - white-space: normal; - } +/* Slightly denser tabs bar and nicer corners */ +.nav-tabs .nav-link { + border: 0; + border-bottom: 2px solid transparent; + padding: .6rem .8rem; } -/* sticky first column (for wide tables) */ -.sticky-col { - position: sticky; - left: 0; - z-index: 2; - background: var(--bs-body-bg); - box-shadow: 1px 0 0 rgba(255, 255, 255, .08); +.nav-tabs .nav-link.active { + border-bottom-color: var(--bs-primary); + font-weight: 600; } -footer.site-footer { - border-top: 1px solid rgba(255, 255, 255, .1); +/* Global loading spacing */ +#global-loading { + display: none; } -footer.site-footer a { - text-decoration: none; -} - -footer.site-footer a:hover { - text-decoration: underline; -} - -/* Toast container constraints */ -#toast-container .toast { - max-width: min(420px, 90vw); - word-wrap: break-word; -} - -#toast-container { - max-width: 92vw; -} - - -#toast-container { - width: min(480px, 96vw); - max-width: min(480px, 96vw); -} - -#toast-container .toast { - max-width: 100%; - overflow-wrap: anywhere; - word-break: break-word; - white-space: normal; -} - -.position-fixed.bottom-0.end-0.p-3 { - right: max(env(safe-area-inset-right), 1rem); - bottom: max(env(safe-area-inset-bottom), 1rem); -} - -/* Row chevron (expandable rows) */ -.table .chev { - width: 1.25rem; - text-align: center; - user-select: none; -} - -.table tr.expandable { - cursor: pointer; -} - -.table tr.expandable .chev::before { - content: "▸"; - display: inline-block; - transition: transform .15s ease; -} - -.table tr.expanded .chev::before { - transform: rotate(90deg); - content: "▾"; -} - -/* Small utility widths */ -.w-1 { - width: 1.25rem; -} - -/* Subtle skeleton */ -.skel { - position: relative; - background: linear-gradient(90deg, rgba(255, 255, 255, .05) 25%, rgba(255, 255, 255, .10) 37%, rgba(255, 255, 255, .05) 63%); - background-size: 400% 100%; - animation: skel 1.4s ease-in-out infinite; -} - -@keyframes skel { - 0% { - background-position: 100% 0; - } - - 100% { - background-position: 0 0; - } -} - - -#vm-admin, -#vm-admin .table-responsive, -#vm-admin table, -#vm-admin tbody, -#vm-admin tr, -#vm-admin td { - overflow: visible !important; -} - -#vm-admin td { - position: relative; - z-index: 1; -} - -#vm-admin .target-node { - position: relative; - z-index: 1001; -} - -/* Toasts: hard-pinned to bottom-right corner */ -#toast-container { - position: fixed !important; - right: 0 !important; - bottom: 0 !important; - left: auto !important; - top: auto !important; - - margin: 0 !important; - padding: 1rem !important; - - width: auto; - /* allow toast's own width (e.g., 350px in Bootstrap) */ - max-width: 100vw; - /* safety on tiny screens */ - pointer-events: none; - z-index: 3000; -} - -#toast-container .toast { - pointer-events: auto; - margin: 0.25rem 0 0 0; - /* stack vertically */ -} - -@supports (inset: 0) { - - /* If the browser supports logical inset, keep it exact as well */ - #toast-container { - inset: auto 0 0 auto !important; - } +#global-loading.show { + display: block; } \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 451ce3a..ae478b0 100644 --- a/templates/index.html +++ b/templates/index.html @@ -2,75 +2,134 @@ - - + + PVE HA Panel - - + + + + + + + -
+ + - -
+ +
+
- Loading data… + Loading data…
+
+ +
-
+
- -
-
Loading…
+ +
+
Loading…
-
-
-
+ + + + @@ -80,7 +139,7 @@
-
+

- -
+ +

-
Loading…
+
+ Loading… +
-
+

-
+

-
+