feat(ui): refresh shell, auth, and node visuals

This commit is contained in:
2026-06-20 18:08:10 -05:00
parent a58b7682f3
commit 2d46a9289c
10 changed files with 497 additions and 121 deletions

View File

@@ -24,8 +24,7 @@
}
body.theme-dark,
body.theme-light,
body.theme-blue {
body.theme-light {
--color-primary-50: 239 246 255;
--color-primary-100: 219 234 254;
--color-primary-200: 191 219 254;
@@ -39,37 +38,6 @@ body.theme-blue {
--color-primary-950: 23 37 84;
}
body.theme-green {
--color-primary-50: 236 253 245;
--color-primary-100: 209 250 229;
--color-primary-200: 167 243 208;
--color-primary-300: 110 231 183;
--color-primary-400: 52 211 153;
--color-primary-500: 16 185 129;
--color-primary-600: 5 150 105;
--color-primary-700: 4 120 87;
--color-primary-800: 6 95 70;
--color-primary-900: 6 78 59;
--color-primary-950: 2 44 34;
}
body.theme-red {
--color-primary-50: 254 242 242;
--color-primary-100: 254 226 226;
--color-primary-200: 254 202 202;
--color-primary-300: 252 165 165;
--color-primary-400: 248 113 113;
--color-primary-500: 239 68 68;
--color-primary-600: 220 38 38;
--color-primary-700: 185 28 28;
--color-primary-800: 153 27 27;
--color-primary-900: 127 29 29;
--color-primary-950: 69 10 10;
}
body.theme-blue,
body.theme-green,
body.theme-red,
body.theme-dark,
body.theme-light {
--bs-primary: rgb(var(--color-primary-600));
@@ -133,12 +101,47 @@ a {
.auth-card {
background: color-mix(in srgb, var(--ma-surface-overlay) 92%, transparent);
backdrop-filter: blur(14px);
border: 1px solid var(--ma-border);
}
body[data-bs-theme="light"] .auth-card {
background: color-mix(in srgb, white 58%, var(--ma-surface-base));
}
.auth-shell {
background:
radial-gradient(circle at top center, rgba(var(--color-primary-500), 0.16), transparent 24%),
linear-gradient(180deg, #181c22 0%, #14181e 100%);
}
body[data-bs-theme="light"] .auth-shell {
background:
radial-gradient(circle at top center, rgba(var(--color-primary-300), 0.28), transparent 22%),
linear-gradient(180deg, #f4f7fb 0%, #eceff4 100%);
}
.container-tight {
max-width: 26rem;
}
.auth-logo {
width: 4rem;
height: 4rem;
object-fit: cover;
border-radius: 1rem;
box-shadow: 0 0.75rem 2rem rgba(0, 0, 0, 0.22);
}
.auth-card-tabler {
border-radius: 1rem;
box-shadow: 0 1.5rem 3.5rem rgba(15, 23, 42, 0.28);
}
.auth-input {
min-height: 2.75rem;
border-radius: 0.85rem;
}
.auth-brand {
background:
radial-gradient(circle at top, rgba(var(--color-primary-600), 0.32), transparent 35%),
@@ -199,6 +202,28 @@ body[data-bs-theme="light"] .auth-feature {
font-size: 1.4rem;
}
.chip-icon-plain {
background: transparent;
box-shadow: none;
color: var(--bs-emphasis-color);
}
.distro-icon {
color: var(--bs-emphasis-color);
}
.distro-icon.fl-ubuntu {
color: #e95420;
}
.distro-icon.fl-debian {
color: #d70a53;
}
.distro-icon.fl-archlinux {
color: #1793d1;
}
.content {
min-width: 0;
min-height: 0;
@@ -228,12 +253,6 @@ body[data-bs-theme="light"] .app-sidebar {
background: color-mix(in srgb, white 22%, var(--ma-surface-sidebar));
}
.app-sidebar-nav .btn {
justify-content: flex-start;
align-items: center;
gap: 0.4rem;
}
.sidebar-logo {
width: 3rem;
height: 3rem;
@@ -242,6 +261,75 @@ body[data-bs-theme="light"] .app-sidebar {
box-shadow: 0 0.75rem 2rem rgba(var(--color-primary-700), 0.18);
}
.app-sidebar-inner {
gap: 0.9rem;
}
.sidebar-brand {
padding: 0.35rem 0.2rem 0.55rem;
}
.sidebar-brand-title {
font-size: 1rem;
font-weight: 700;
letter-spacing: -0.02em;
}
.sidebar-section-label {
padding: 0 0.65rem;
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--bs-secondary-color);
}
.app-sidebar-nav {
gap: 0.35rem;
}
.app-nav-link {
display: flex;
align-items: center;
gap: 0.8rem;
min-height: 2.9rem;
padding: 0.75rem 0.85rem;
border-radius: 0.95rem;
color: var(--bs-secondary-color);
font-weight: 600;
transition: background-color 0.18s ease, color 0.18s ease, transform 0.18s ease, box-shadow 0.18s ease;
}
.app-nav-link i {
width: 1.1rem;
font-size: 1.05rem;
text-align: center;
}
.app-nav-link:hover {
color: var(--bs-emphasis-color);
background: color-mix(in srgb, var(--ma-surface-2) 92%, transparent);
transform: translateX(2px);
}
.app-nav-link.is-active {
color: #fff;
background: linear-gradient(135deg, rgba(var(--color-primary-600), 0.96), rgba(var(--color-primary-700), 0.96));
box-shadow: 0 0.9rem 2rem rgba(var(--color-primary-900), 0.2);
}
body[data-bs-theme="light"] .app-nav-link.is-active {
color: #fff;
}
.app-header {
box-shadow: inset 0 -1px 0 var(--ma-border);
}
.app-footer {
box-shadow: inset 0 1px 0 var(--ma-border);
}
.min-w-0 {
min-width: 0;
}
@@ -285,6 +373,7 @@ body[data-bs-theme="light"] .app-sidebar {
align-items: center;
justify-content: center;
padding: 0;
border-radius: 0.85rem;
}
.node-chip {
@@ -311,6 +400,90 @@ body[data-bs-theme="light"] .app-sidebar {
position: relative;
}
.kpi-card-featured .card-body {
min-height: 12rem;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.kpi-card-label {
font-size: 0.76rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--bs-secondary-color);
}
.kpi-card-subtle {
margin-top: 0.35rem;
font-size: 0.9rem;
color: var(--bs-secondary-color);
}
.kpi-card-chip {
display: inline-flex;
align-items: center;
min-height: 2rem;
padding: 0.35rem 0.7rem;
border-radius: 999px;
border: 1px solid var(--ma-border);
background: color-mix(in srgb, var(--ma-surface-base) 78%, var(--ma-surface-2));
color: var(--bs-secondary-color);
font-size: 0.76rem;
font-weight: 600;
white-space: nowrap;
}
.kpi-card-featured-value {
position: relative;
z-index: 1;
font-size: clamp(2.3rem, 4vw, 3.15rem);
font-weight: 800;
line-height: 1;
letter-spacing: -0.03em;
color: var(--bs-emphasis-color);
}
.kpi-card-featured-trend {
position: relative;
z-index: 1;
display: flex;
align-items: center;
gap: 0.7rem;
flex-wrap: wrap;
}
.kpi-card-trend {
display: inline-flex;
align-items: center;
min-height: 2rem;
padding: 0.3rem 0.65rem;
border-radius: 999px;
font-size: 0.82rem;
font-weight: 700;
}
.kpi-card-trend.is-up {
color: #86efac;
background: rgba(20, 83, 45, 0.58);
}
.kpi-card-trend.is-down {
color: #fca5a5;
background: rgba(127, 29, 29, 0.58);
}
.kpi-card-trend.is-flat {
color: #cbd5e1;
background: rgba(51, 65, 85, 0.6);
}
.kpi-card-trend-copy {
font-size: 0.88rem;
color: var(--bs-secondary-color);
}
.kpi-graph {
position: absolute;
inset: 0;
@@ -323,6 +496,13 @@ body[data-bs-theme="light"] .app-sidebar {
height: 100%;
}
.kpi-card-featured .kpi-graph {
inset: auto 0 0 0;
height: 68%;
opacity: 0.92;
mask-image: linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, 0.45) 26%, rgba(0, 0, 0, 1) 100%);
}
.node-tile {
display: grid;
grid-template-columns: 64px minmax(0, 1fr);
@@ -382,6 +562,12 @@ body[data-bs-theme="light"] .app-sidebar {
font-size: 1rem;
}
.node-brand-icon {
width: 3rem;
height: 3rem;
font-size: 2.1rem;
}
.node-loading-icon {
animation: node-icon-spin 1s linear infinite;
}
@@ -455,6 +641,16 @@ body[data-bs-theme="light"] .app-sidebar {
font-size: 1.2rem;
}
.system-summary-icon-plain {
width: 3.5rem;
height: 3.5rem;
font-size: 2.25rem;
}
.distro-icon-lg {
font-size: 2.4rem;
}
.system-summary-label {
font-size: 0.72rem;
font-weight: 700;
@@ -515,6 +711,23 @@ body[data-bs-theme="light"] .app-sidebar {
background: var(--ma-surface-1);
}
.form-control,
.form-select,
.btn,
.modal-content {
border-radius: 0.9rem;
}
.form-control,
.form-select {
background: color-mix(in srgb, var(--ma-surface-base) 80%, var(--ma-surface-2));
}
body[data-bs-theme="light"] .form-control,
body[data-bs-theme="light"] .form-select {
background: color-mix(in srgb, white 72%, var(--ma-surface-1));
}
.stat-card,
.node-chip,
.dashboard-loader > .card,
@@ -739,23 +952,159 @@ body[data-bs-theme="light"] .theme-card {
background: linear-gradient(135deg, #ffffff, #e2e8f0);
}
.swatch-green {
background: linear-gradient(135deg, rgb(16 185 129), rgb(4 120 87));
}
.swatch-red {
background: linear-gradient(135deg, rgb(239 68 68), rgb(185 28 28));
}
.swatch-blue {
background: linear-gradient(135deg, rgb(59 130 246), rgb(29 78 216));
}
.btn-check:checked + .theme-card {
border-color: rgb(var(--color-primary-500));
box-shadow: 0 0 0 0.2rem rgba(var(--color-primary-500), 0.2);
}
.uptime-summary-card {
background: linear-gradient(180deg, color-mix(in srgb, var(--ma-surface-2) 92%, transparent), var(--ma-surface-1));
}
.uptime-summary-label {
font-size: 0.76rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--bs-secondary-color);
margin-bottom: 0.6rem;
}
.uptime-summary-value {
font-size: clamp(1.8rem, 3vw, 2.5rem);
font-weight: 800;
line-height: 1;
color: var(--bs-emphasis-color);
}
.uptime-monitor-card {
display: grid;
gap: 1rem;
height: 100%;
padding: 1.1rem;
border: 1px solid var(--ma-border);
border-radius: 1.15rem;
background: linear-gradient(180deg, color-mix(in srgb, var(--ma-surface-2) 90%, transparent), var(--ma-surface-1));
}
.uptime-monitor-card.is-up {
box-shadow: inset 0 0 0 1px rgba(34, 197, 94, 0.12);
}
.uptime-monitor-card.is-down {
box-shadow: inset 0 0 0 1px rgba(239, 68, 68, 0.14);
}
.uptime-monitor-head,
.uptime-monitor-foot,
.uptime-monitor-stat {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.uptime-monitor-badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 4.5rem;
min-height: 2rem;
padding: 0.35rem 0.75rem;
border-radius: 999px;
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.uptime-monitor-badge.is-up {
color: #86efac;
background: rgba(20, 83, 45, 0.55);
}
.uptime-monitor-badge.is-down {
color: #fca5a5;
background: rgba(127, 29, 29, 0.55);
}
.uptime-monitor-badge.is-pending {
color: #cbd5e1;
background: rgba(51, 65, 85, 0.65);
}
.uptime-monitor-stats {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.8rem 1rem;
}
.uptime-monitor-stat {
padding: 0.75rem 0.85rem;
border-radius: 0.9rem;
background: color-mix(in srgb, var(--ma-surface-base) 74%, var(--ma-surface-2));
font-size: 0.86rem;
}
.uptime-monitor-stat span,
.uptime-monitor-foot {
color: var(--bs-secondary-color);
}
.uptime-monitor-stat strong {
color: var(--bs-emphasis-color);
font-weight: 700;
}
.uptime-check-strip {
display: flex;
align-items: flex-end;
gap: 0.28rem;
min-height: 2.25rem;
padding: 0.2rem 0;
}
.uptime-check-pill {
flex: 1 1 0;
min-width: 0;
height: 2rem;
border-radius: 999px;
background: rgba(100, 116, 139, 0.35);
}
.uptime-check-pill.is-up {
background: linear-gradient(180deg, rgba(34, 197, 94, 0.95), rgba(22, 163, 74, 0.45));
}
.uptime-check-pill.is-down {
background: linear-gradient(180deg, rgba(248, 113, 113, 0.95), rgba(185, 28, 28, 0.5));
}
.uptime-check-pill.is-pending {
background: rgba(100, 116, 139, 0.35);
}
.uptime-monitor-foot {
font-size: 0.82rem;
}
.uptime-table th {
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--bs-secondary-color);
}
body[data-bs-theme="light"] .uptime-monitor-card,
body[data-bs-theme="light"] .uptime-summary-card {
background: linear-gradient(180deg, color-mix(in srgb, white 76%, var(--ma-surface-1)), var(--ma-surface-1));
}
body[data-bs-theme="light"] .uptime-monitor-stat {
background: color-mix(in srgb, white 82%, var(--ma-surface-1));
}
@media (max-width: 991.98px) {
.app-shell {
flex-direction: column;

View File

@@ -8,6 +8,8 @@ document.addEventListener("DOMContentLoaded", () => {
const dashboardNodes = document.getElementById("dashboard-nodes");
const dashboardSearch = document.getElementById("dashboard-search");
const authToggle = document.querySelector("[data-auth-toggle]");
const themeRadios = document.querySelectorAll('input[name="theme"]');
const themeModeInput = document.querySelector('input[name="mode"]');
const attachXtermConsole = (consoleOutput) => {
const wsPath = consoleOutput.dataset.ws;
@@ -143,6 +145,7 @@ document.addEventListener("DOMContentLoaded", () => {
disk: [],
uptime: [],
};
let previousCpu = null;
const uptimeLabel = (seconds) => {
const days = Math.floor(seconds / 86400);
@@ -200,11 +203,21 @@ document.addEventListener("DOMContentLoaded", () => {
const ramValue = nodeLive.querySelector('[data-kpi-value="ram"]');
const diskValue = nodeLive.querySelector('[data-kpi-value="disk"]');
const uptimeValue = nodeLive.querySelector('[data-kpi-value="uptime"]');
const cpuTrend = nodeLive.querySelector('[data-kpi-trend="cpu"]');
if (cpuValue) cpuValue.textContent = `${cpu.toFixed(1)}%`;
if (ramValue) ramValue.textContent = `${ram.toFixed(1)}%`;
if (diskValue) diskValue.textContent = `${disk.toFixed(1)}%`;
if (uptimeValue) uptimeValue.textContent = uptimeLabel(uptime);
if (cpuTrend) {
const delta = previousCpu === null ? 0 : cpu - previousCpu;
const trendClass = delta > 0.15 ? "is-up" : delta < -0.15 ? "is-down" : "is-flat";
const trendLabel = `${delta > 0 ? "+" : ""}${delta.toFixed(1)}%`;
cpuTrend.classList.remove("is-up", "is-down", "is-flat");
cpuTrend.classList.add(trendClass);
cpuTrend.textContent = trendLabel;
}
previousCpu = cpu;
pushValue("cpu", cpu);
pushValue("ram", ram);
@@ -294,6 +307,16 @@ document.addEventListener("DOMContentLoaded", () => {
syncAuthMode();
}
if (themeModeInput instanceof HTMLInputElement) {
themeRadios.forEach((radio) => {
radio.addEventListener("change", () => {
if (radio instanceof HTMLInputElement && radio.checked) {
themeModeInput.value = radio.value;
}
});
});
}
if (typeof bootstrap !== "undefined") {
document.querySelectorAll('[data-bs-toggle-tooltip="tooltip"]').forEach((element) => {
new bootstrap.Tooltip(element);