Redesign The Hub web UI: softer dark theme, sidebar nav, status widgets
Apply the approved "The Hub" redesign to the web admin while keeping every existing mechanic intact (polling, service-detail modals, Matrix and system password management, NWC wallet manager, update/rebuild/backup/security/ reboot flows, feature manager, onboarding, role handling). Layout (templates/index.html): - Old header bar + IP bar replaced by a sidebar + topbar app shell. Sidebar carries the brand (The Hub / Sovran_SystemsOS version), category navigation with live counts, the System actions (Update System, Tech Support, Manual Backup, Security, node-only Upgrade), Feature Manager / Preferences, and the role badge. - Topbar carries the page title, a service search box, the LAN | WAN network chip (external IP always visible, one line), Reboot and Sign Out. - New widgets row: Systems Operational summary (opens the new Systems Operational modal) and Bitcoin Core sync progress with block/ETA. - New Systems Operational modal: service counts, router port-forwarding steps (80/443 to this machine's LAN IP), the live domain diagnostics checklist, and which services use those ports. New static/js/dashboard.js (namespaced IIFE, no new globals) renders the nav, search filtering, widgets, and the Systems Operational modal; it is driven by the existing /api/services payloads via a window.dashboardServicesUpdated() hook called from buildTiles/updateTiles. Visual design (static/css/*): - New token set (softer dark surfaces, lifted contrast, Sovran green reserved for status and actions) with legacy variable names aliased so every secondary sheet re-skins automatically. - Tiles, dialogs, buttons, inputs, toggles, tables, forms, overlays and the login page restyled to the GNOME/libadwaita-flavored surfaces: 20px cards, 26px dialogs, pill buttons, libadwaita switches, mono value pills with Copy buttons, consistent modal anatomy. - Inline SVG symbol set for chrome/nav icons (monochrome, currentColor); service icons still load from /static/icons/*.svg as before. - Sidebar system buttons now use vector glyphs instead of emoji. Behavioral details: - Service detail modal header gains a status pill next to the version chip; credentials render with pre-wrap for multiline values. - First-login security banner now renders as a card inside the content area instead of a full-width strip above the app. - Search + category filtering hide/show sections and tiles without touching the polling or update logic. Onboarding and login pages rebranded to "The Hub" with aligned palette.
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
/* Sovran_SystemsOS Hub — Web UI Stylesheet
|
||||
Dark theme — near-black with green accents matching the Sovran Hub icon
|
||||
v8 — Black-forward, green used for accents/borders/highlights only */
|
||||
The Hub redesign — softer dark theme, GNOME 50 / libadwaita surfaces,
|
||||
Sovran green reserved for status and actions.
|
||||
|
||||
Design tokens are defined once here. Legacy variable names from the
|
||||
previous theme are aliased to the new values so every stylesheet
|
||||
(support, security, domain-setup, onboarding, …) re-skins without
|
||||
needing per-rule edits. */
|
||||
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
@@ -9,22 +14,48 @@
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg-color: #080a09;
|
||||
--surface-color: rgba(14, 16, 15, 0.7);
|
||||
--card-color: rgba(20, 22, 21, 0.6);
|
||||
--border-color: rgba(255, 255, 255, 0.06);
|
||||
--text-primary: #ecf3ef;
|
||||
--text-secondary: #8aaa9a;
|
||||
--text-dim: #4a6658;
|
||||
--accent-color: #5EAD8A;
|
||||
--green: #6DBF8B;
|
||||
--yellow: #e5a50a;
|
||||
--red: #e01b24;
|
||||
--grey: #5E7A6A;
|
||||
--radius-card: 18px;
|
||||
--radius-btn: 8px;
|
||||
--shadow-card: 0 4px 16px rgba(0, 0, 0, 0.4);
|
||||
--shadow-hover: 0 8px 32px rgba(0, 0, 0, 0.5);
|
||||
color-scheme: dark;
|
||||
|
||||
/* ── The Hub palette ─────────────────────────────────────────── */
|
||||
--bg: #141a16;
|
||||
--surface: #181f1b;
|
||||
--card: #1e2621;
|
||||
--card-hover: #232d27;
|
||||
--elevated: #212925;
|
||||
--inset: #121814;
|
||||
--border: rgba(255, 255, 255, 0.07);
|
||||
--border-strong: rgba(255, 255, 255, 0.14);
|
||||
--text: #e3ede7;
|
||||
--text-2: #a8bcb0;
|
||||
--text-3: #6e8578;
|
||||
--accent: #3ecf8e;
|
||||
--accent-strong: #42f39a;
|
||||
--accent-deep: #1aa45d;
|
||||
--accent-dim: rgba(66, 243, 154, 0.12);
|
||||
--amber: #e9b64a;
|
||||
--red: #f66151;
|
||||
--blue: #78aeed;
|
||||
--mono: 'JetBrains Mono', 'Fira Code', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
|
||||
--radius-card: 20px;
|
||||
--radius-dialog: 26px;
|
||||
--shadow-card: 0 10px 30px rgba(0, 0, 0, 0.25);
|
||||
--shadow-hover: 0 16px 36px rgba(0, 0, 0, 0.38);
|
||||
--shadow-pop: 0 30px 80px rgba(0, 0, 0, 0.5);
|
||||
|
||||
/* ── Legacy aliases (previous theme) — keep every old sheet working */
|
||||
--bg-color: var(--bg);
|
||||
--surface-color: var(--surface);
|
||||
--card-color: var(--card);
|
||||
--border-color: var(--border);
|
||||
--text-primary: var(--text);
|
||||
--text-secondary: var(--text-2);
|
||||
--text-dim: var(--text-3);
|
||||
--accent-color: var(--accent);
|
||||
--green: var(--accent);
|
||||
--yellow: var(--amber);
|
||||
--red: var(--red);
|
||||
--grey: var(--text-3);
|
||||
--radius-btn: 12px;
|
||||
}
|
||||
|
||||
html, body {
|
||||
@@ -32,19 +63,39 @@ html, body {
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
background:
|
||||
radial-gradient(ellipse at top, rgba(94, 173, 138, 0.04) 0%, transparent 50%),
|
||||
var(--bg-color);
|
||||
color: var(--text-primary);
|
||||
font-family: Inter, Cantarell, 'Segoe UI', system-ui, -apple-system, 'Helvetica Neue', Arial, sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ── Login page ──────────────────────────────────────────────────── */
|
||||
/* Soft brand wash so the canvas never reads flat-black */
|
||||
body::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background:
|
||||
radial-gradient(1100px 520px at 16% -12%, rgba(66, 243, 154, 0.04), transparent 60%),
|
||||
radial-gradient(900px 600px at 110% 110%, rgba(26, 169, 110, 0.05), transparent 55%);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
button { font-family: inherit; }
|
||||
|
||||
::selection { background: rgba(66, 243, 154, 0.25); }
|
||||
|
||||
/* ── Scrollbars ─────────────────────────────────────────────────── */
|
||||
::-webkit-scrollbar { width: 10px; height: 10px; }
|
||||
::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.10); border-radius: 99px; border: 2px solid transparent; background-clip: padding-box; }
|
||||
::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.18); background-clip: padding-box; }
|
||||
::-webkit-scrollbar-track { background: transparent; }
|
||||
|
||||
/* ── Login page ─────────────────────────────────────────────────── */
|
||||
|
||||
.login-wrapper {
|
||||
display: flex;
|
||||
@@ -52,86 +103,103 @@ body {
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
padding: 24px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
background-color: rgba(14, 16, 15, 0.75);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 20px;
|
||||
padding: 48px 40px;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.5);
|
||||
max-width: 402px;
|
||||
text-align: center;
|
||||
background: var(--elevated);
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: 28px;
|
||||
padding: 46px 42px 38px;
|
||||
box-shadow: var(--shadow-pop);
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
margin-bottom: 26px;
|
||||
}
|
||||
|
||||
.login-logo {
|
||||
height: 64px;
|
||||
margin-bottom: 16px;
|
||||
width: 78px;
|
||||
height: 78px;
|
||||
margin-bottom: 18px;
|
||||
filter: drop-shadow(0 8px 24px rgba(28, 196, 120, 0.3));
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
font-size: 1.3rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.login-title em { font-style: normal; color: var(--accent); }
|
||||
|
||||
.login-sub {
|
||||
color: var(--text-2);
|
||||
font-size: 0.85rem;
|
||||
margin: 7px 0 0;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.8rem;
|
||||
font-weight: 650;
|
||||
color: var(--text-2);
|
||||
margin-bottom: 6px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-btn);
|
||||
background-color: var(--card-color);
|
||||
color: var(--text-primary);
|
||||
padding: 11px 16px;
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: 14px;
|
||||
background: var(--inset);
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
font-size: 0.92rem;
|
||||
outline: 0;
|
||||
transition: border-color 0.15s, box-shadow 0.15s;
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-color);
|
||||
border-color: rgba(66, 243, 154, 0.55);
|
||||
box-shadow: 0 0 0 3px rgba(66, 243, 154, 0.13);
|
||||
}
|
||||
|
||||
.btn-login {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border-radius: var(--radius-btn);
|
||||
background-color: var(--accent-color);
|
||||
color: #0A1A10;
|
||||
font-size: 0.95rem;
|
||||
border-radius: 99px;
|
||||
background: linear-gradient(180deg, #3fd68e, #1ea263);
|
||||
color: #04220f;
|
||||
font-size: 0.92rem;
|
||||
font-weight: 700;
|
||||
margin-top: 8px;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.28), 0 6px 18px rgba(35, 199, 124, 0.22);
|
||||
}
|
||||
|
||||
.btn-login:hover {
|
||||
opacity: 0.88;
|
||||
.btn-login:hover:not(:disabled) {
|
||||
filter: brightness(1.08);
|
||||
}
|
||||
|
||||
.login-error {
|
||||
background-color: rgba(224, 27, 36, 0.12);
|
||||
border: 1px solid var(--red);
|
||||
color: #f87171;
|
||||
background: rgba(246, 97, 81, 0.10);
|
||||
border: 1px solid rgba(246, 97, 81, 0.35);
|
||||
color: #f8a39b;
|
||||
padding: 10px 14px;
|
||||
border-radius: 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.85rem;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -5,34 +5,133 @@ button {
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
outline: none;
|
||||
transition: opacity 0.15s, box-shadow 0.15s, background-color 0.15s;
|
||||
transition: background 0.16s, border-color 0.16s, color 0.16s, transform 0.16s, filter 0.16s, opacity 0.15s;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.45;
|
||||
opacity: 0.55;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 7px 16px;
|
||||
border-radius: var(--radius-btn);
|
||||
font-size: 0.88rem;
|
||||
font-weight: 600;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
border-radius: 99px;
|
||||
padding: 8px 18px;
|
||||
font-size: 0.84rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.01em;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn svg { width: 15px; height: 15px; }
|
||||
.btn:active { transform: scale(0.97); }
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--accent-color);
|
||||
color: #0A1A10;
|
||||
background: linear-gradient(180deg, #3fd68e, #1ea263);
|
||||
color: #04220f;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.28), 0 6px 18px rgba(35, 199, 124, 0.22);
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
opacity: 0.88;
|
||||
filter: brightness(1.08);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* Update System button: uses accent green by default */
|
||||
.btn-primary:active:not(:disabled) { transform: scale(0.97); }
|
||||
|
||||
/* Ghost — the default secondary action (legacy: btn-close-modal / btn-save) */
|
||||
.btn-ghost,
|
||||
.btn-close-modal,
|
||||
.btn-save {
|
||||
background: transparent;
|
||||
border-color: var(--border-strong);
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
.btn-ghost:hover:not(:disabled),
|
||||
.btn-close-modal:hover:not(:disabled),
|
||||
.btn-save:hover:not(:disabled) {
|
||||
color: var(--text);
|
||||
border-color: rgba(255, 255, 255, 0.26);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
/* Amber — destructive-ish confirmations (restart, reboot, retry) */
|
||||
.btn-amber,
|
||||
.btn-reboot,
|
||||
.btn-restart-amber,
|
||||
.btn-warning {
|
||||
background: rgba(233, 182, 74, 0.10);
|
||||
border-color: rgba(233, 182, 74, 0.32);
|
||||
color: #f2c45e;
|
||||
}
|
||||
|
||||
.btn-amber:hover:not(:disabled),
|
||||
.btn-reboot:hover:not(:disabled),
|
||||
.btn-restart-amber:hover:not(:disabled),
|
||||
.btn-warning:hover:not(:disabled) {
|
||||
background: rgba(233, 182, 74, 0.18);
|
||||
border-color: rgba(233, 182, 74, 0.5);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: rgba(246, 97, 81, 0.10);
|
||||
border-color: rgba(246, 97, 81, 0.35);
|
||||
color: #f8a39b;
|
||||
}
|
||||
|
||||
.btn-danger:hover:not(:disabled) {
|
||||
background: rgba(246, 97, 81, 0.18);
|
||||
border-color: rgba(246, 97, 81, 0.5);
|
||||
}
|
||||
|
||||
.btn-sm { padding: 6px 14px; font-size: 0.77rem; }
|
||||
|
||||
/* ── Header / topbar buttons ────────────────────────────────────── */
|
||||
|
||||
.btn-header-reboot {
|
||||
background: rgba(233, 182, 74, 0.10);
|
||||
border: 1px solid rgba(233, 182, 74, 0.32);
|
||||
color: #f2c45e;
|
||||
font-size: 0.77rem;
|
||||
font-weight: 700;
|
||||
padding: 6px 14px;
|
||||
border-radius: 99px;
|
||||
}
|
||||
|
||||
.btn-header-reboot:hover:not(:disabled) {
|
||||
background: rgba(233, 182, 74, 0.18);
|
||||
border-color: rgba(233, 182, 74, 0.5);
|
||||
}
|
||||
|
||||
.btn-logout {
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-strong);
|
||||
color: var(--text-2);
|
||||
font-size: 0.77rem;
|
||||
font-weight: 700;
|
||||
padding: 6px 14px;
|
||||
border-radius: 99px;
|
||||
}
|
||||
|
||||
.btn-logout:hover {
|
||||
color: var(--text);
|
||||
border-color: rgba(255, 255, 255, 0.26);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
/* ── Update System button (sidebar) ─────────────────────────────── */
|
||||
|
||||
.btn-update {
|
||||
background-color: #4A9474;
|
||||
color: #E0F2EA;
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-strong);
|
||||
color: var(--text-2);
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -40,24 +139,22 @@ button:disabled {
|
||||
}
|
||||
|
||||
.btn-update:hover:not(:disabled) {
|
||||
opacity: 0.88;
|
||||
color: var(--text);
|
||||
border-color: rgba(255, 255, 255, 0.26);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
/* Update System button: brighter green when updates are available */
|
||||
.btn-update.has-updates {
|
||||
background-color: #5EAD8A;
|
||||
color: #0A1A10;
|
||||
}
|
||||
|
||||
.btn-update.has-updates:hover:not(:disabled) {
|
||||
background-color: #78C8A2;
|
||||
background: rgba(66, 243, 154, 0.12);
|
||||
border-color: rgba(66, 243, 154, 0.4);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.update-badge {
|
||||
display: none;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-color: var(--yellow);
|
||||
background-color: var(--amber);
|
||||
border-radius: 50%;
|
||||
animation: pulse-badge 1.4s ease-in-out infinite;
|
||||
}
|
||||
@@ -71,9 +168,11 @@ button:disabled {
|
||||
50% { opacity: 0.5; transform: scale(1.35); }
|
||||
}
|
||||
|
||||
/* ── Icon buttons ───────────────────────────────────────────────── */
|
||||
|
||||
.btn-icon {
|
||||
background: none;
|
||||
color: var(--text-secondary);
|
||||
color: var(--text-2);
|
||||
padding: 6px;
|
||||
border-radius: 50%;
|
||||
font-size: 1.1rem;
|
||||
@@ -81,6 +180,6 @@ button:disabled {
|
||||
}
|
||||
|
||||
.btn-icon:hover:not(:disabled) {
|
||||
background-color: var(--border-color);
|
||||
color: var(--text-primary);
|
||||
background-color: rgba(255, 255, 255, 0.06);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ domain-field-label {
|
||||
|
||||
domain-field-input {
|
||||
width: 100%;
|
||||
background-color: #0c0f0e;
|
||||
background-color: var(--inset);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
@@ -75,7 +75,7 @@ domain-field-actions {
|
||||
|
||||
.domain-field-input {
|
||||
width: 100%;
|
||||
background-color: #0c0f0e;
|
||||
background-color: var(--inset);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
@@ -198,21 +198,21 @@ domain-field-actions {
|
||||
}
|
||||
|
||||
.port-proto-badge--tcp {
|
||||
color: #6dbf8b;
|
||||
background: rgba(109, 191, 139, 0.12);
|
||||
border: 1px solid rgba(109, 191, 139, 0.4);
|
||||
color: var(--accent);
|
||||
background: rgba(66, 243, 154, 0.12);
|
||||
border: 1px solid rgba(66, 243, 154, 0.4);
|
||||
}
|
||||
|
||||
.port-proto-badge--udp {
|
||||
color: #6aa9e0;
|
||||
color: var(--blue);
|
||||
background: rgba(106, 169, 224, 0.12);
|
||||
border: 1px solid rgba(106, 169, 224, 0.4);
|
||||
}
|
||||
|
||||
.port-proto-badge--both {
|
||||
color: #e5a50a;
|
||||
background: rgba(229, 165, 10, 0.12);
|
||||
border: 1px solid rgba(229, 165, 10, 0.45);
|
||||
color: var(--amber);
|
||||
background: rgba(233, 182, 74, 0.12);
|
||||
border: 1px solid rgba(233, 182, 74, 0.45);
|
||||
}
|
||||
|
||||
.port-proto-note {
|
||||
@@ -230,7 +230,7 @@ domain-field-actions {
|
||||
margin-bottom: 14px;
|
||||
padding: 10px 14px;
|
||||
background: rgba(255, 180, 0, 0.10);
|
||||
border: 1px solid var(--warning-color, #f59e0b);
|
||||
border: 1px solid var(--warning-color, var(--amber));
|
||||
border-radius: 8px;
|
||||
font-size: 0.88rem;
|
||||
line-height: 1.6;
|
||||
|
||||
@@ -1,143 +1,196 @@
|
||||
/* ── Feature Manager styles ──────────────────────────────────────── */
|
||||
/* ── Sidebar: Feature Manager + Preferences ─────────────────────── */
|
||||
|
||||
.feature-manager-section {
|
||||
margin-bottom: 32px;
|
||||
.category-section.autolaunch-section,
|
||||
.category-section.feature-manager-section {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.feature-subcategory {
|
||||
margin-bottom: 16px;
|
||||
/* The old "Preferences" header + hr becomes a small sidebar label.
|
||||
features.js renders: .section-header + hr.section-divider + cards */
|
||||
.autolaunch-section .section-header,
|
||||
.feature-manager-section .section-header {
|
||||
font-size: 0.66rem;
|
||||
font-weight: 750;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-3);
|
||||
margin: 0;
|
||||
padding: 14px 10px 7px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.autolaunch-section .section-divider,
|
||||
.feature-manager-section .section-divider {
|
||||
background: none;
|
||||
height: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.feature-cards-wrap { display: flex; flex-direction: column; }
|
||||
|
||||
.feature-subcategory { display: flex; flex-direction: column; }
|
||||
|
||||
.feature-subcategory-header {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
font-size: 0.66rem;
|
||||
font-weight: 750;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--text-dim);
|
||||
margin-bottom: 8px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.feature-cards-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
color: var(--text-3);
|
||||
padding: 12px 10px 6px;
|
||||
}
|
||||
|
||||
/* Feature card — sidebar preference row with a switch */
|
||||
.feature-card {
|
||||
background-color: var(--card-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
padding: 14px 16px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: flex-start;
|
||||
padding: 9px 10px;
|
||||
border-radius: 10px;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.feature-card:hover { background: rgba(255, 255, 255, 0.05); }
|
||||
|
||||
.feature-card-top {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.feature-card-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.feature-card-info { min-width: 0; flex: 1; }
|
||||
|
||||
.feature-card-name {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 4px;
|
||||
font-size: 0.84rem;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.feature-card-desc {
|
||||
font-size: 0.78rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.5;
|
||||
margin-top: 2px;
|
||||
font-size: 0.68rem;
|
||||
color: var(--text-3);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.feature-card-status {
|
||||
font-size: 0.72rem;
|
||||
color: var(--text-dim);
|
||||
font-size: 0.66rem;
|
||||
font-weight: 700;
|
||||
padding: 2px 8px;
|
||||
border-radius: 99px;
|
||||
flex-shrink: 0;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
.feature-card-status.status-on {
|
||||
color: var(--accent);
|
||||
background: var(--accent-dim);
|
||||
border-color: rgba(66, 243, 154, 0.3);
|
||||
}
|
||||
|
||||
.feature-conflict-warning {
|
||||
margin-top: 8px;
|
||||
font-size: 0.68rem;
|
||||
color: var(--amber);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Domain badges inside feature cards */
|
||||
.feature-domain-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 0.64rem;
|
||||
font-weight: 700;
|
||||
padding: 2px 8px;
|
||||
border-radius: 99px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.feature-domain-badge.configured {
|
||||
color: var(--accent);
|
||||
background: var(--accent-dim);
|
||||
}
|
||||
|
||||
.feature-domain-badge.not-configured {
|
||||
color: var(--amber);
|
||||
background: rgba(233, 182, 74, 0.10);
|
||||
}
|
||||
|
||||
.feature-domain-icon { font-size: 0.8rem; }
|
||||
|
||||
.feature-domain-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
font-size: 0.66rem;
|
||||
font-weight: 650;
|
||||
margin-top: 5px;
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
.feature-domain-label--ok { color: var(--accent); }
|
||||
.feature-domain-label--warn { color: var(--amber); }
|
||||
.feature-domain-label--error { color: var(--red); }
|
||||
.feature-domain-label--checking { color: var(--blue); }
|
||||
|
||||
/* ── Toggle switch (libadwaita style) ───────────────────────────── */
|
||||
|
||||
.feature-toggle {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 44px;
|
||||
height: 24px;
|
||||
width: 46px;
|
||||
height: 26px;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
margin-top: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.feature-toggle-input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.feature-toggle-slider {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-color: var(--border-color);
|
||||
border-radius: 24px;
|
||||
transition: background-color 0.2s;
|
||||
border-radius: 99px;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
border: 1px solid var(--border-strong);
|
||||
transition: 0.2s;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.feature-toggle-slider::before {
|
||||
.feature-toggle-slider::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
left: 3px;
|
||||
top: 3px;
|
||||
background-color: #fff;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
transition: transform 0.2s;
|
||||
background: #cfd8d2;
|
||||
transition: 0.2s cubic-bezier(0.3, 0.8, 0.4, 1.2);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.feature-toggle.active .feature-toggle-slider {
|
||||
background-color: var(--green);
|
||||
.feature-toggle-input:checked + .feature-toggle-slider {
|
||||
background: var(--accent);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.feature-toggle.active .feature-toggle-slider::before {
|
||||
transform: translateX(20px);
|
||||
.feature-toggle-input:checked + .feature-toggle-slider::after {
|
||||
left: 23px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.feature-domain-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-top: 6px;
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.feature-domain-icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.feature-domain-label {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.feature-domain-label--checking {
|
||||
color: var(--text-dim);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.feature-domain-label--ok {
|
||||
color: var(--green);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.feature-domain-label--warn {
|
||||
color: var(--yellow);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.feature-domain-label--error {
|
||||
color: var(--red);
|
||||
font-weight: 600;
|
||||
.feature-toggle-input:focus-visible + .feature-toggle-slider {
|
||||
box-shadow: 0 0 0 3px rgba(66, 243, 154, 0.3);
|
||||
}
|
||||
|
||||
@@ -1,69 +1,147 @@
|
||||
/* ── Header bar ─────────────────────────────────────────────────── */
|
||||
/* ── Topbar (replaces the old header-bar + ip-bar) ──────────────── */
|
||||
|
||||
.header-bar {
|
||||
backdrop-filter: blur(14px);
|
||||
-webkit-backdrop-filter: blur(14px);
|
||||
background-color: rgba(10, 12, 11, 0.82);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
padding: 8px 24px;
|
||||
.topbar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
height: 80px;
|
||||
width: auto;
|
||||
display: block;
|
||||
gap: 14px;
|
||||
padding: 13px 26px;
|
||||
background: var(--surface);
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.header-bar .title {
|
||||
font-size: 1.15rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
.page-title {
|
||||
font-size: 1.02rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.01em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.title-group {
|
||||
/* ── Search ─────────────────────────────────────────────────────── */
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px 10px;
|
||||
gap: 9px;
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 99px;
|
||||
padding: 8px 16px;
|
||||
flex: 1;
|
||||
max-width: 400px;
|
||||
min-width: 140px;
|
||||
margin-left: 18px;
|
||||
color: var(--text-3);
|
||||
transition: border-color 0.15s, box-shadow 0.15s;
|
||||
}
|
||||
|
||||
.header-buttons {
|
||||
.search-box:focus-within {
|
||||
border-color: rgba(66, 243, 154, 0.35);
|
||||
box-shadow: 0 0 0 3px rgba(66, 243, 154, 0.09);
|
||||
}
|
||||
|
||||
.search-box svg { width: 15px; height: 15px; flex-shrink: 0; }
|
||||
|
||||
.search-box input {
|
||||
background: none;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
color: var(--text);
|
||||
width: 100%;
|
||||
font: inherit;
|
||||
font-size: 0.87rem;
|
||||
}
|
||||
|
||||
.search-box input::placeholder { color: var(--text-3); }
|
||||
|
||||
/* ── Network chip — LAN + WAN always on one line ────────────────── */
|
||||
|
||||
.net-chip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 17px;
|
||||
border-radius: 99px;
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
font-family: var(--mono);
|
||||
font-size: 0.73rem;
|
||||
color: var(--text-3);
|
||||
white-space: nowrap;
|
||||
order: 4;
|
||||
}
|
||||
|
||||
.net-chip svg { width: 15px; height: 15px; color: var(--text-3); flex-shrink: 0; }
|
||||
.net-chip .int-wrap, .net-chip .ext-wrap { display: inline-flex; align-items: center; gap: 7px; }
|
||||
|
||||
.net-chip .ip-label {
|
||||
font-size: 0.6rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--text-3);
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
.net-chip .ip-value {
|
||||
color: var(--text);
|
||||
font-weight: 600;
|
||||
font-family: var(--mono);
|
||||
}
|
||||
|
||||
.net-separator {
|
||||
width: 1px;
|
||||
height: 15px;
|
||||
background: var(--border-strong);
|
||||
margin: 0 3px;
|
||||
flex-shrink: 0;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.top-actions {
|
||||
display: flex;
|
||||
gap: 9px;
|
||||
margin-left: auto;
|
||||
order: 5;
|
||||
}
|
||||
|
||||
/* Below 1160px the chip wraps to its own full-width topbar row —
|
||||
it never stacks LAN over WAN and never hides the external IP. */
|
||||
@media (max-width: 1160px) {
|
||||
.topbar { flex-wrap: wrap; row-gap: 10px; }
|
||||
.net-chip {
|
||||
order: 6;
|
||||
flex-basis: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Legacy badges (still used inside dialogs) ──────────────────── */
|
||||
|
||||
.role-badge {
|
||||
background-color: var(--accent-color);
|
||||
color: #0A1A10;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 7px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid var(--border-strong);
|
||||
color: var(--text-2);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
padding: 3px 10px;
|
||||
border-radius: 20px;
|
||||
letter-spacing: 0.03em;
|
||||
font-weight: 750;
|
||||
padding: 6px 12px;
|
||||
border-radius: 99px;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* ── OS Version Badge — identical styling to the version badge ────
|
||||
── shown next to titles in the service modal windows ──────────── */
|
||||
.os-version-badge {
|
||||
background-color: rgba(255, 255, 255, 0.06);
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.72rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: var(--text-2);
|
||||
font-size: 0.66rem;
|
||||
font-weight: 600;
|
||||
padding: 2px 10px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
padding: 2px 9px;
|
||||
border-radius: 99px;
|
||||
border: 1px solid var(--border);
|
||||
letter-spacing: 0.02em;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -71,76 +149,9 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ── IP bar ─────────────────────────────────────────────────────── */
|
||||
|
||||
.ip-bar {
|
||||
background-color: rgba(10, 12, 11, 0.7);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
padding: 8px 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 32px;
|
||||
font-size: 0.82rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.ip-bar .ip-label {
|
||||
color: var(--text-dim);
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.ip-bar .ip-value {
|
||||
font-family: 'JetBrains Mono', 'Fira Code', 'Source Code Pro', monospace;
|
||||
color: var(--accent-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ip-separator {
|
||||
color: var(--border-color);
|
||||
}
|
||||
.btn-logout {
|
||||
background: transparent;
|
||||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
padding: 4px 12px;
|
||||
border-radius: var(--radius-btn);
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s, color 0.15s;
|
||||
}
|
||||
|
||||
.btn-logout:hover {
|
||||
border-color: var(--accent-color);
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
/* ── Header reboot button ───────────────────────────────────────── */
|
||||
|
||||
.btn-header-reboot {
|
||||
background: transparent;
|
||||
border: 1px solid rgba(184, 125, 0, 0.35);
|
||||
color: #c98d08;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
padding: 4px 12px;
|
||||
border-radius: var(--radius-btn);
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s, color 0.15s, background-color 0.15s;
|
||||
}
|
||||
|
||||
.btn-header-reboot:hover {
|
||||
border-color: #b87d00;
|
||||
color: #e0a010;
|
||||
background-color: rgba(184, 125, 0, 0.1);
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.btn-header-reboot {
|
||||
padding: 4px 8px;
|
||||
padding: 5px 10px;
|
||||
font-size: 0.72rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,138 +1,367 @@
|
||||
/* ── Main content ───────────────────────────────────────────────── */
|
||||
/* ── App shell: sidebar + main column ───────────────────────────── */
|
||||
|
||||
.main-content {
|
||||
.app {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
max-width: 1400px;
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 26px 30px 70px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* ── Sidebar ────────────────────────────────────────────────────── */
|
||||
|
||||
.sidebar {
|
||||
width: 270px;
|
||||
width: 250px;
|
||||
flex-shrink: 0;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.06);
|
||||
background-color: rgba(12, 14, 13, 0.65);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
padding: 20px 14px;
|
||||
background: var(--surface);
|
||||
border-right: 1px solid var(--border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
padding: 20px 14px 14px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* ── Sidebar: Tech Support button ───────────────────────────────── */
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 2px 8px 20px;
|
||||
}
|
||||
|
||||
.brand-logo {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
flex-shrink: 0;
|
||||
filter: drop-shadow(0 4px 14px rgba(28, 196, 120, 0.25));
|
||||
}
|
||||
|
||||
.brand-text { display: flex; flex-direction: column; min-width: 0; }
|
||||
|
||||
.brand-title {
|
||||
font-size: 1.02rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.01em;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.brand-title em { font-style: normal; color: var(--accent); }
|
||||
|
||||
.brand-sub {
|
||||
font-size: 0.7rem;
|
||||
color: var(--text-3);
|
||||
font-family: var(--mono);
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.nav-label {
|
||||
font-size: 0.66rem;
|
||||
font-weight: 750;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-3);
|
||||
padding: 14px 10px 7px;
|
||||
}
|
||||
|
||||
/* Category nav (dashboard.js) */
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 11px;
|
||||
width: 100%;
|
||||
padding: 8px 10px;
|
||||
border-radius: 10px;
|
||||
border: 0;
|
||||
background: none;
|
||||
color: var(--text-2);
|
||||
font-size: 0.87rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
}
|
||||
|
||||
.nav-item svg { width: 17px; height: 17px; flex-shrink: 0; }
|
||||
|
||||
.nav-item:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
color: var(--text);
|
||||
box-shadow: inset 2.5px 0 0 var(--accent);
|
||||
}
|
||||
|
||||
.nav-text {
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.nav-count {
|
||||
margin-left: auto;
|
||||
font-size: 0.67rem;
|
||||
font-weight: 750;
|
||||
flex-shrink: 0;
|
||||
color: var(--text-3);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 2px 8px;
|
||||
border-radius: 99px;
|
||||
}
|
||||
|
||||
.nav-item.active .nav-count {
|
||||
background: rgba(255, 255, 255, 0.10);
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
/* System items (tiles.js renders these as .sidebar-support-btn) */
|
||||
.sidebar-support-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gap: 11px;
|
||||
width: 100%;
|
||||
background-color: var(--card-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
padding: 12px 14px;
|
||||
color: var(--text-primary);
|
||||
padding: 8px 10px;
|
||||
border-radius: 10px;
|
||||
border: 0;
|
||||
background: none;
|
||||
color: var(--text-2);
|
||||
font-size: 0.87rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: border-style 0.15s, border-color 0.15s, background-color 0.15s;
|
||||
text-align: left;
|
||||
transition: background 0.15s, color 0.15s, border-color 0.15s;
|
||||
}
|
||||
|
||||
.sidebar-support-btn:hover {
|
||||
border-color: var(--accent-color);
|
||||
border-style: solid;
|
||||
background-color: #162320;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.sidebar-support-btn + .sidebar-support-btn {
|
||||
margin-top: 8px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.sidebar-support-icon {
|
||||
font-size: 1.5rem;
|
||||
font-size: 1rem;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-support-icon svg { width: 17px; height: 17px; }
|
||||
|
||||
.sidebar-support-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
gap: 1px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.sidebar-support-title {
|
||||
font-size: 0.88rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
font-size: 0.87rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
color: var(--text);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.sidebar-support-hint {
|
||||
font-size: 0.72rem;
|
||||
color: var(--accent-color);
|
||||
font-weight: 600;
|
||||
font-size: 0.68rem;
|
||||
font-weight: 650;
|
||||
color: var(--text-3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.sidebar-divider {
|
||||
border: none;
|
||||
border-top: 1px solid var(--border-color);
|
||||
margin: 16px 0;
|
||||
border-top: 1px solid var(--border);
|
||||
margin: 14px 0 0;
|
||||
}
|
||||
|
||||
/* ── Upgrade modal ──────────────────────────────────────────────── */
|
||||
.sidebar-spacer { flex: 1; min-height: 18px; }
|
||||
|
||||
.upgrade-dialog {
|
||||
max-width: 480px;
|
||||
.sidebar-footer {
|
||||
border-top: 1px solid var(--border);
|
||||
padding-top: 14px;
|
||||
}
|
||||
|
||||
.upgrade-info-box {
|
||||
background-color: var(--card-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
padding: 14px 18px;
|
||||
margin-bottom: 14px;
|
||||
.host-note {
|
||||
text-align: center;
|
||||
font-size: 0.66rem;
|
||||
color: var(--text-3);
|
||||
font-family: var(--mono);
|
||||
margin-top: 9px;
|
||||
}
|
||||
|
||||
.upgrade-info-title {
|
||||
font-size: 0.88rem;
|
||||
/* ── Widgets row (dashboard.js) ─────────────────────────────────── */
|
||||
|
||||
.widgets {
|
||||
display: grid;
|
||||
grid-template-columns: 1.1fr 1.45fr;
|
||||
gap: 16px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.widgets { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.widget {
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 20px;
|
||||
padding: 17px 19px;
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
align-items: center;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03), var(--shadow-card);
|
||||
transition: border-color 0.18s;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.widget:hover { border-color: var(--border-strong); }
|
||||
|
||||
.widget.clickable { cursor: pointer; }
|
||||
.widget.clickable:focus-visible {
|
||||
outline: 2px solid rgba(66, 243, 154, 0.45);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.widget-chip {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
border-radius: 14px;
|
||||
flex-shrink: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: #fff;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 6px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.widget-chip svg { width: 22px; height: 22px; }
|
||||
.widget-chip img { width: 46px; height: 46px; display: block; object-fit: contain; }
|
||||
|
||||
.widget-chip.chip-green {
|
||||
background: linear-gradient(160deg, #2f9f6b, #17683f);
|
||||
}
|
||||
|
||||
.widget-chip.chip-btc {
|
||||
background: linear-gradient(160deg, #f7931a, #b96a0a);
|
||||
}
|
||||
|
||||
.widget-chip.chip-sovran {
|
||||
background: linear-gradient(160deg, #42f39a, #1aa45d);
|
||||
}
|
||||
|
||||
.w-body { min-width: 0; }
|
||||
|
||||
.widget h3 {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: -0.005em;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.upgrade-info-list {
|
||||
padding-left: 20px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.7;
|
||||
margin: 0;
|
||||
.widget .sub {
|
||||
margin-top: 4px;
|
||||
font-size: 0.78rem;
|
||||
color: var(--text-2);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.upgrade-info-list a {
|
||||
color: var(--accent-color);
|
||||
.widget .sub b { color: var(--text); font-weight: 650; }
|
||||
.widget .sub .good { color: var(--accent); font-weight: 650; }
|
||||
.widget .sub .warn { color: var(--amber); font-weight: 650; }
|
||||
|
||||
.w-bar {
|
||||
height: 7px;
|
||||
border-radius: 99px;
|
||||
background: rgba(255, 255, 255, 0.07);
|
||||
overflow: hidden;
|
||||
margin: 8px 0 7px;
|
||||
}
|
||||
|
||||
.upgrade-rebuild-note {
|
||||
font-style: italic;
|
||||
color: var(--text-dim);
|
||||
font-size: 0.82rem;
|
||||
.w-bar-fill {
|
||||
height: 100%;
|
||||
border-radius: 99px;
|
||||
background: linear-gradient(90deg, #42f39a, #1aa45d);
|
||||
transition: width 0.6s cubic-bezier(0.3, 0.7, 0.3, 1);
|
||||
}
|
||||
|
||||
.w-chev {
|
||||
margin-left: auto;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: var(--text-3);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.w-chev svg { width: 15px; height: 15px; }
|
||||
.widget.clickable:hover .w-chev {
|
||||
color: var(--text-2);
|
||||
background: rgba(255, 255, 255, 0.07);
|
||||
}
|
||||
|
||||
/* ── Tiles area ─────────────────────────────────────────────────── */
|
||||
|
||||
#tiles-area {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding: 24px 20px 48px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.dashboard-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
padding: 72px 24px;
|
||||
color: var(--text-3);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.dashboard-loading-spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
border: 2.5px solid rgba(255, 255, 255, 0.12);
|
||||
border-top-color: var(--accent);
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
|
||||
/* ── Category sections ──────────────────────────────────────────── */
|
||||
|
||||
.category-section {
|
||||
@@ -140,25 +369,30 @@
|
||||
}
|
||||
|
||||
.section-header {
|
||||
font-size: 0.82rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 15px;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 750;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 4px;
|
||||
padding-left: 4px;
|
||||
color: var(--text-2);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.section-divider {
|
||||
border: none;
|
||||
border-top: 1px solid var(--border-color);
|
||||
margin-bottom: 16px;
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, var(--border), transparent);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tiles-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 14px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(158px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
/* ── Empty state ────────────────────────────────────────────────── */
|
||||
@@ -166,10 +400,64 @@
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 64px 24px;
|
||||
color: var(--text-dim);
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* ── Upgrade modal (kept from previous layout) ──────────────────── */
|
||||
|
||||
.upgrade-dialog {
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.upgrade-info-box {
|
||||
background: rgba(0, 0, 0, 0.16);
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: 16px;
|
||||
padding: 14px 16px;
|
||||
margin: 14px 0;
|
||||
}
|
||||
|
||||
.upgrade-info-title {
|
||||
font-size: 0.88rem;
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.upgrade-info-list {
|
||||
padding-left: 20px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-2);
|
||||
line-height: 1.7;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.upgrade-info-list a {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.upgrade-rebuild-note {
|
||||
font-style: italic;
|
||||
color: var(--text-3);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
/* ── First-login security banner (inside .content) ──────────────── */
|
||||
|
||||
.security-first-login-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
background: rgba(233, 182, 74, 0.08);
|
||||
border: 1px solid rgba(233, 182, 74, 0.30);
|
||||
border-radius: 16px;
|
||||
padding: 12px 16px;
|
||||
color: var(--text-2);
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@
|
||||
justify-content: flex-start;
|
||||
min-height: 100vh;
|
||||
background:
|
||||
radial-gradient(ellipse at top, rgba(94, 173, 138, 0.04) 0%, transparent 50%),
|
||||
radial-gradient(ellipse at top, rgba(62, 207, 142, 0.04) 0%, transparent 50%),
|
||||
var(--bg-color);
|
||||
padding: 24px 16px 48px;
|
||||
overflow-y: auto;
|
||||
@@ -70,13 +70,13 @@
|
||||
.onboarding-step-dot.active {
|
||||
background-color: var(--accent-color);
|
||||
border-color: var(--accent-color);
|
||||
color: #0A1A10;
|
||||
color: #04220f;
|
||||
}
|
||||
|
||||
.onboarding-step-dot.completed {
|
||||
background-color: var(--green);
|
||||
border-color: var(--green);
|
||||
color: #0A1A10;
|
||||
color: #04220f;
|
||||
}
|
||||
|
||||
.onboarding-step-connector {
|
||||
@@ -139,7 +139,7 @@
|
||||
/* Cards */
|
||||
|
||||
.onboarding-card {
|
||||
background-color: rgba(14, 16, 15, 0.65);
|
||||
background-color: rgba(18, 24, 20, 0.65);
|
||||
backdrop-filter: blur(14px);
|
||||
-webkit-backdrop-filter: blur(14px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
@@ -184,10 +184,10 @@
|
||||
font-size: 0.82rem;
|
||||
font-weight: 700;
|
||||
color: var(--accent-color);
|
||||
background-color: rgba(94, 173, 138, 0.10);
|
||||
background-color: rgba(62, 207, 142, 0.10);
|
||||
padding: 3px 10px;
|
||||
border-radius: 20px;
|
||||
border: 1px solid rgba(94, 173, 138, 0.25);
|
||||
border: 1px solid rgba(62, 207, 142, 0.25);
|
||||
}
|
||||
|
||||
/* Step header */
|
||||
@@ -371,8 +371,8 @@
|
||||
|
||||
.onboarding-port-warn {
|
||||
padding: 10px 14px;
|
||||
background-color: rgba(229, 165, 10, 0.1);
|
||||
border: 1px solid rgba(229, 165, 10, 0.35);
|
||||
background-color: rgba(233, 182, 74, 0.1);
|
||||
border: 1px solid rgba(233, 182, 74, 0.35);
|
||||
border-radius: 8px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--yellow);
|
||||
@@ -409,8 +409,8 @@
|
||||
|
||||
.onboarding-creds-notice {
|
||||
padding: 12px 16px;
|
||||
background-color: rgba(94, 173, 138, 0.08);
|
||||
border: 1px solid rgba(94, 173, 138, 0.20);
|
||||
background-color: rgba(62, 207, 142, 0.08);
|
||||
border: 1px solid rgba(62, 207, 142, 0.20);
|
||||
border-radius: 8px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-secondary);
|
||||
@@ -503,8 +503,8 @@
|
||||
font-size: 0.72rem;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
background-color: rgba(94, 173, 138, 0.08);
|
||||
border: 1px solid rgba(94, 173, 138, 0.20);
|
||||
background-color: rgba(62, 207, 142, 0.08);
|
||||
border: 1px solid rgba(62, 207, 142, 0.20);
|
||||
color: var(--accent-color);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
@@ -512,7 +512,7 @@
|
||||
}
|
||||
|
||||
.onboarding-cred-reveal-btn:hover {
|
||||
background-color: rgba(94, 173, 138, 0.15);
|
||||
background-color: rgba(62, 207, 142, 0.15);
|
||||
}
|
||||
|
||||
/* Completion checklist (Step 5) */
|
||||
@@ -671,7 +671,7 @@
|
||||
}
|
||||
|
||||
.onboarding-password-toggle:hover {
|
||||
background-color: rgba(94, 173, 138, 0.10);
|
||||
background-color: rgba(62, 207, 142, 0.10);
|
||||
border-color: var(--accent-color);
|
||||
}
|
||||
|
||||
@@ -683,8 +683,8 @@
|
||||
|
||||
.onboarding-password-warning {
|
||||
padding: 10px 14px;
|
||||
background-color: rgba(229, 165, 10, 0.1);
|
||||
border: 1px solid rgba(229, 165, 10, 0.35);
|
||||
background-color: rgba(233, 182, 74, 0.1);
|
||||
border: 1px solid rgba(233, 182, 74, 0.35);
|
||||
border-radius: 8px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--yellow);
|
||||
@@ -694,8 +694,8 @@
|
||||
|
||||
.onboarding-password-success {
|
||||
padding: 12px 16px;
|
||||
background-color: rgba(94, 173, 138, 0.10);
|
||||
border: 1px solid rgba(94, 173, 138, 0.30);
|
||||
background-color: rgba(62, 207, 142, 0.10);
|
||||
border: 1px solid rgba(62, 207, 142, 0.30);
|
||||
border-radius: 8px;
|
||||
font-size: 0.92rem;
|
||||
color: var(--green);
|
||||
@@ -748,7 +748,7 @@
|
||||
}
|
||||
|
||||
.reboot-card {
|
||||
background-color: rgba(14, 16, 15, 0.8);
|
||||
background-color: rgba(18, 24, 20, 0.8);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
.security-warning-box {
|
||||
background-color: rgba(180, 40, 40, 0.10);
|
||||
border-left: 3px solid #c94040;
|
||||
border-left: 3px solid var(--red);
|
||||
border-radius: 6px;
|
||||
padding: 12px 14px;
|
||||
margin-bottom: 14px;
|
||||
@@ -78,7 +78,7 @@
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-color: #c94040;
|
||||
background-color: var(--red);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
@@ -90,7 +90,7 @@
|
||||
}
|
||||
|
||||
.btn-danger:hover:not(:disabled) {
|
||||
background-color: #a83030;
|
||||
background-color: var(--red);
|
||||
}
|
||||
|
||||
.btn-danger:disabled {
|
||||
@@ -105,8 +105,8 @@
|
||||
}
|
||||
|
||||
.security-status-info { color: var(--text-secondary); }
|
||||
.security-status-ok { color: #6DBF8B; }
|
||||
.security-status-error { color: #e05252; }
|
||||
.security-status-ok { color: var(--accent); }
|
||||
.security-status-error { color: var(--red); }
|
||||
|
||||
/* ── Verify System Integrity ─────────────────────────────────────── */
|
||||
|
||||
@@ -160,7 +160,7 @@
|
||||
|
||||
.security-verify-link {
|
||||
font-size: 0.78rem;
|
||||
color: var(--accent-color, #6DBF8B);
|
||||
color: var(--accent-color, var(--accent));
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@@ -174,13 +174,13 @@
|
||||
}
|
||||
|
||||
.security-verify-pass {
|
||||
background-color: rgba(109, 191, 139, 0.15);
|
||||
color: #6DBF8B;
|
||||
background-color: rgba(66, 243, 154, 0.15);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.security-verify-fail {
|
||||
background-color: rgba(224, 82, 82, 0.15);
|
||||
color: #e05252;
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.security-verify-errors {
|
||||
@@ -221,12 +221,11 @@
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-color: rgba(6, 8, 7, 0.94);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
background: radial-gradient(1000px 640px at 50% 18%, #17211c, #0b100d 72%);
|
||||
z-index: 1000;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 26px;
|
||||
animation: security-reset-fade-in 0.35s ease-out;
|
||||
}
|
||||
|
||||
@@ -253,13 +252,13 @@
|
||||
}
|
||||
|
||||
.security-reset-password-box {
|
||||
font-family: monospace;
|
||||
font-family: var(--mono);
|
||||
font-size: 1.35rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
background: rgba(109, 191, 139, 0.10);
|
||||
border: 1.5px solid rgba(109, 191, 139, 0.35);
|
||||
border-radius: 8px;
|
||||
background: var(--accent-dim);
|
||||
border: 1.5px solid rgba(66, 243, 154, 0.35);
|
||||
border-radius: 14px;
|
||||
padding: 14px 24px;
|
||||
letter-spacing: 0.04em;
|
||||
text-align: center;
|
||||
@@ -277,10 +276,11 @@
|
||||
}
|
||||
|
||||
.security-reset-reboot-btn {
|
||||
background-color: #6DBF8B;
|
||||
color: #0a0c0b;
|
||||
background: linear-gradient(180deg, #3fd68e, #1ea263);
|
||||
color: #04220f;
|
||||
border: none;
|
||||
border-radius: 7px;
|
||||
border-radius: 99px;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.28), 0 6px 18px rgba(35, 199, 124, 0.22);
|
||||
padding: 11px 22px;
|
||||
font-size: 0.88rem;
|
||||
font-weight: 700;
|
||||
@@ -290,7 +290,7 @@
|
||||
}
|
||||
|
||||
.security-reset-reboot-btn:hover:not(:disabled) {
|
||||
background-color: #5aab78;
|
||||
background-color: var(--accent);
|
||||
}
|
||||
|
||||
.security-reset-reboot-btn:disabled {
|
||||
@@ -303,11 +303,12 @@
|
||||
.security-first-login-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 12px 18px;
|
||||
background-color: rgba(94, 173, 138, 0.08);
|
||||
border-bottom: 2px solid rgba(94, 173, 138, 0.25);
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 24px;
|
||||
background: rgba(233, 182, 74, 0.08);
|
||||
border: 1px solid rgba(233, 182, 74, 0.30);
|
||||
border-radius: 16px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
@@ -333,19 +334,21 @@
|
||||
|
||||
.security-banner-dismiss {
|
||||
background: none;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: 99px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-secondary);
|
||||
padding: 2px 7px;
|
||||
padding: 4px 10px;
|
||||
flex-shrink: 0;
|
||||
line-height: 1.4;
|
||||
transition: background-color 0.15s;
|
||||
transition: 0.15s;
|
||||
}
|
||||
|
||||
.security-banner-dismiss:hover {
|
||||
background-color: rgba(0,0,0,0.08);
|
||||
color: var(--text);
|
||||
border-color: rgba(255, 255, 255, 0.26);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
/* ── Legacy security inline warning banner ───────────────────────── */
|
||||
@@ -357,14 +360,14 @@
|
||||
padding: 12px 14px;
|
||||
margin-bottom: 12px;
|
||||
background-color: rgba(180, 100, 0, 0.12);
|
||||
border-left: 3px solid #c97a00;
|
||||
border-left: 3px solid #c98d08;
|
||||
border-radius: 6px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.security-inline-icon {
|
||||
font-size: 1rem;
|
||||
color: #e69000;
|
||||
color: #e0a010;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -378,9 +381,9 @@
|
||||
display: inline-block;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
color: #e69000;
|
||||
color: #e0a010;
|
||||
text-decoration: none;
|
||||
border: 1px solid #c97a00;
|
||||
border: 1px solid #c98d08;
|
||||
border-radius: 4px;
|
||||
padding: 4px 10px;
|
||||
align-self: flex-start;
|
||||
@@ -447,9 +450,9 @@
|
||||
|
||||
.pw-credentials-note {
|
||||
font-size: 0.78rem;
|
||||
color: #c97a00;
|
||||
color: #c98d08;
|
||||
background-color: rgba(180, 100, 0, 0.10);
|
||||
border-left: 2px solid #c97a00;
|
||||
border-left: 2px solid #c98d08;
|
||||
border-radius: 4px;
|
||||
padding: 7px 10px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
}
|
||||
|
||||
.support-steps code {
|
||||
background-color: rgba(94, 173, 138, 0.10);
|
||||
background-color: rgba(62, 207, 142, 0.10);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.82rem;
|
||||
@@ -116,7 +116,7 @@
|
||||
padding: 12px;
|
||||
border-radius: var(--radius-btn);
|
||||
background-color: var(--accent-color);
|
||||
color: #0A1A10;
|
||||
color: #04220f;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 10px;
|
||||
@@ -146,7 +146,7 @@
|
||||
padding: 12px;
|
||||
border-radius: var(--radius-btn);
|
||||
background-color: var(--accent-color);
|
||||
color: #0A1A10;
|
||||
color: #04220f;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
margin-top: 16px;
|
||||
@@ -168,7 +168,7 @@
|
||||
}
|
||||
|
||||
.support-btn-auditlog:hover:not(:disabled) {
|
||||
background-color: #1c2a24;
|
||||
background-color: var(--card-hover);
|
||||
}
|
||||
|
||||
.support-fine-print {
|
||||
@@ -219,13 +219,13 @@
|
||||
}
|
||||
|
||||
.support-wallet-protected {
|
||||
background-color: rgba(109, 191, 139, 0.06);
|
||||
border-color: rgba(109, 191, 139, 0.3);
|
||||
background-color: rgba(66, 243, 154, 0.06);
|
||||
border-color: rgba(66, 243, 154, 0.3);
|
||||
}
|
||||
|
||||
.support-wallet-unlocked {
|
||||
background-color: rgba(229, 165, 10, 0.06);
|
||||
border-color: rgba(229, 165, 10, 0.3);
|
||||
background-color: rgba(233, 182, 74, 0.06);
|
||||
border-color: rgba(233, 182, 74, 0.3);
|
||||
}
|
||||
|
||||
.support-wallet-warning {
|
||||
@@ -290,7 +290,7 @@
|
||||
padding: 8px 16px;
|
||||
border-radius: var(--radius-btn);
|
||||
background-color: var(--yellow);
|
||||
color: #0A1A10;
|
||||
color: #04220f;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
@@ -310,7 +310,7 @@
|
||||
}
|
||||
|
||||
.support-btn-wallet-lock:hover:not(:disabled) {
|
||||
background-color: #529E7E;
|
||||
background-color: var(--accent);
|
||||
}
|
||||
|
||||
/* ── Audit log ───────────────────────────────────────────────────── */
|
||||
@@ -324,7 +324,7 @@
|
||||
.support-audit-log {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
background-color: #0c0f0e;
|
||||
background-color: var(--inset);
|
||||
border-radius: 8px;
|
||||
padding: 10px 14px;
|
||||
}
|
||||
|
||||
@@ -1,473 +1,178 @@
|
||||
/* ── Service tile card (status-only) ─────────────────────────────── */
|
||||
|
||||
.dashboard-loading {
|
||||
min-height: 180px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.dashboard-loading-spinner {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid var(--border-color);
|
||||
border-top-color: var(--accent-color);
|
||||
border-radius: 50%;
|
||||
animation: dashboard-loading-spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes dashboard-loading-spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
/* ── Service tiles ──────────────────────────────────────────────── */
|
||||
|
||||
.service-tile {
|
||||
width: 160px;
|
||||
min-height: 130px;
|
||||
background-color: var(--card-color);
|
||||
border: 1px solid var(--border-color);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
border-radius: var(--radius-card);
|
||||
box-shadow: var(--shadow-card);
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
padding: 21px 12px 17px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px 12px 18px;
|
||||
gap: 0;
|
||||
transition: box-shadow 0.2s, border-color 0.2s;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: transform 0.18s, border-color 0.18s, background 0.18s, box-shadow 0.18s, opacity 0.18s;
|
||||
animation: tileIn 0.45s cubic-bezier(0.2, 0.8, 0.3, 1) backwards;
|
||||
}
|
||||
|
||||
@keyframes tileIn {
|
||||
from { opacity: 0; transform: translateY(10px) scale(0.96); }
|
||||
}
|
||||
|
||||
.service-tile:hover {
|
||||
transform: translateY(-4px);
|
||||
border-color: var(--border-strong);
|
||||
background: var(--card-hover);
|
||||
box-shadow: var(--shadow-hover);
|
||||
border-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.service-tile.disabled {
|
||||
opacity: 0.45;
|
||||
.service-tile:active { transform: translateY(-1px) scale(0.985); }
|
||||
|
||||
.service-tile:focus-visible {
|
||||
outline: 2px solid rgba(66, 243, 154, 0.45);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.service-tile.disabled { opacity: 0.55; }
|
||||
|
||||
.tile-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
width: 62px;
|
||||
height: 62px;
|
||||
display: block;
|
||||
object-fit: contain;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.tile-icon-fallback {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
width: 62px;
|
||||
height: 62px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--border-color);
|
||||
border-radius: 12px;
|
||||
color: var(--text-dim);
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 10px;
|
||||
color: var(--text-3);
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.tile-name {
|
||||
font-size: 0.88rem;
|
||||
font-weight: 600;
|
||||
margin-top: 13px;
|
||||
font-size: 0.86rem;
|
||||
font-weight: 650;
|
||||
text-align: center;
|
||||
color: var(--text-primary);
|
||||
line-height: 1.3;
|
||||
max-width: 140px;
|
||||
word-break: break-word;
|
||||
hyphens: auto;
|
||||
min-height: 1.3em;
|
||||
line-height: 1.25;
|
||||
min-height: 2.5em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Status pill — dot + label inside a rounded pill */
|
||||
.tile-status {
|
||||
font-size: 0.75rem;
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
margin-top: 9px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
color: var(--text-secondary);
|
||||
gap: 6px;
|
||||
font-size: 0.69rem;
|
||||
font-weight: 700;
|
||||
padding: 3px 10px;
|
||||
border-radius: 99px;
|
||||
letter-spacing: 0.01em;
|
||||
white-space: nowrap;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
.tile-version {
|
||||
font-size: 0.7rem;
|
||||
color: var(--text-dim);
|
||||
margin-top: 2px;
|
||||
text-align: center;
|
||||
}
|
||||
.status-text { line-height: 1; }
|
||||
|
||||
.status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--text-3);
|
||||
flex-shrink: 0;
|
||||
background-color: var(--grey);
|
||||
}
|
||||
|
||||
.status-dot.active { background-color: var(--green); }
|
||||
.status-dot.inactive { background-color: var(--red); }
|
||||
.status-dot.loading { background-color: var(--yellow); animation: pulse-badge 1s infinite; }
|
||||
.status-dot.failed { background-color: var(--red); }
|
||||
.status-dot.disabled { background-color: var(--grey); }
|
||||
.status-dot.needs-attention { background-color: var(--yellow); }
|
||||
.status-dot.syncing { background-color: #f5a623; animation: pulse-badge 1.5s infinite; }
|
||||
.status-dot.checking-reachability { background-color: var(--accent-color); animation: pulse-badge 1s infinite; }
|
||||
.status-dot.active { background: var(--accent); }
|
||||
.status-dot.needs-attention { background: var(--amber); }
|
||||
.status-dot.failed { background: var(--red); }
|
||||
.status-dot.inactive,
|
||||
.status-dot.disabled { background: var(--text-3); }
|
||||
.status-dot.syncing,
|
||||
.status-dot.loading,
|
||||
.status-dot.checking-reachability { background: var(--blue); animation: tile-dot-pulse 1.4s ease-in-out infinite; }
|
||||
.status-dot.unknown { background: var(--text-3); }
|
||||
|
||||
/* ── Bitcoin IBD sync progress bar ──────────────────────────────── */
|
||||
@keyframes tile-dot-pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.35; }
|
||||
}
|
||||
|
||||
/* Progressive enhancement: tint the whole pill on modern browsers */
|
||||
.tile-status:has(.status-dot.active) { background: var(--accent-dim); color: var(--accent); }
|
||||
.tile-status:has(.status-dot.needs-attention) { background: rgba(233, 182, 74, 0.10); color: var(--amber); }
|
||||
.tile-status:has(.status-dot.failed) { background: rgba(246, 97, 81, 0.10); color: var(--red); }
|
||||
.tile-status:has(.status-dot.syncing),
|
||||
.tile-status:has(.status-dot.loading),
|
||||
.tile-status:has(.status-dot.checking-reachability) { background: rgba(120, 174, 237, 0.10); color: var(--blue); }
|
||||
|
||||
.support-status-label {
|
||||
font-size: 0.69rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
/* ── Bitcoin IBD sync tile ──────────────────────────────────────── */
|
||||
|
||||
.tile-sync-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin-top: 12px;
|
||||
width: 100%;
|
||||
margin-top: 6px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.tile-sync-label {
|
||||
font-size: 0.72rem;
|
||||
color: #f5a623;
|
||||
font-weight: 600;
|
||||
font-size: 0.66rem;
|
||||
font-weight: 750;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
color: var(--amber);
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tile-sync-bar-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
width: 100%;
|
||||
gap: 8px;
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.tile-sync-bar-track {
|
||||
flex: 1;
|
||||
height: 6px;
|
||||
background-color: var(--border-color);
|
||||
border-radius: 3px;
|
||||
height: 7px;
|
||||
border-radius: 99px;
|
||||
background: rgba(255, 255, 255, 0.07);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tile-sync-bar-fill {
|
||||
height: 100%;
|
||||
background-color: #f5a623;
|
||||
border-radius: 3px;
|
||||
transition: width 0.6s ease;
|
||||
min-width: 2px;
|
||||
border-radius: 99px;
|
||||
background: linear-gradient(90deg, #42f39a, #1aa45d);
|
||||
transition: width 0.6s cubic-bezier(0.3, 0.7, 0.3, 1);
|
||||
}
|
||||
|
||||
.tile-sync-percent {
|
||||
font-size: 0.72rem;
|
||||
font-family: var(--mono);
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
color: #f5a623;
|
||||
white-space: nowrap;
|
||||
min-width: 2.5em;
|
||||
text-align: right;
|
||||
color: var(--amber);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tile-sync-eta {
|
||||
font-size: 0.68rem;
|
||||
color: var(--text-dim);
|
||||
margin-top: 6px;
|
||||
font-family: var(--mono);
|
||||
font-size: 0.66rem;
|
||||
color: var(--text-3);
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
/* ── Service detail modal sections ───────────────────────────────── */
|
||||
|
||||
.svc-detail-section {
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.svc-detail-section:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.svc-detail-section-title {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--text-dim);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.svc-detail-desc {
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.svc-detail-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* ── Service detail: Domain ──────────────────────────────────────── */
|
||||
|
||||
.svc-detail-domain-value {
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tile-domain-label--ok {
|
||||
color: var(--green);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tile-domain-label--warn {
|
||||
color: var(--yellow);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tile-domain-label--error {
|
||||
color: var(--red);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ── Service detail: Port table ──────────────────────────────────── */
|
||||
|
||||
.svc-detail-port-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.82rem;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.svc-detail-port-table th {
|
||||
text-align: left;
|
||||
color: var(--text-dim);
|
||||
font-weight: 600;
|
||||
font-size: 0.72rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
padding: 6px 10px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.svc-detail-port-table td {
|
||||
padding: 8px 10px;
|
||||
border-bottom: 1px solid rgba(30, 45, 39, 0.6);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.svc-detail-port-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.svc-detail-port-table-port {
|
||||
font-family: 'JetBrains Mono', 'Fira Code', 'Source Code Pro', monospace;
|
||||
font-weight: 600;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.svc-detail-port-table-proto {
|
||||
text-transform: uppercase;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.svc-detail-port-table-desc {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.svc-detail-port-table-status {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.port-status-listening { color: var(--green); }
|
||||
.port-status-open { color: var(--yellow); }
|
||||
.port-status-closed { color: var(--red); }
|
||||
.port-status-unknown { color: var(--text-dim); }
|
||||
|
||||
/* ── Service detail: Troubleshoot box ────────────────────────────── */
|
||||
|
||||
.svc-detail-troubleshoot {
|
||||
margin-top: 12px;
|
||||
padding: 14px 16px;
|
||||
background-color: rgba(229, 165, 10, 0.08);
|
||||
border: 1px solid rgba(229, 165, 10, 0.3);
|
||||
border-radius: 10px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.svc-detail-troubleshoot strong {
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
.svc-detail-troubleshoot ol {
|
||||
margin-top: 8px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.svc-detail-troubleshoot li {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.svc-detail-troubleshoot code {
|
||||
background-color: rgba(94, 173, 138, 0.10);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.82rem;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.svc-detail-troubleshoot a {
|
||||
color: var(--accent-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.svc-detail-troubleshoot a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* ── Service detail: Domain configure button ─────────────────────── */
|
||||
|
||||
.svc-detail-domain-btn {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
/* ── Service detail: Addon feature toggle ────────────────────────── */
|
||||
|
||||
.svc-detail-addon-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.svc-detail-addon-status {
|
||||
font-size: 0.88rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.addon-status--on {
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.addon-status--off {
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.svc-detail-option-card {
|
||||
padding: 16px;
|
||||
background: rgba(94, 173, 138, 0.06);
|
||||
border: 1px solid rgba(94, 173, 138, 0.24);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.svc-detail-option-list {
|
||||
margin: 10px 0 0;
|
||||
padding-left: 20px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.84rem;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.svc-detail-option-list li {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.svc-detail-option-privacy {
|
||||
margin-top: 12px;
|
||||
padding: 10px 12px;
|
||||
border-left: 3px solid var(--accent-color);
|
||||
background: rgba(94, 173, 138, 0.08);
|
||||
border-radius: 6px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.82rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.svc-detail-option-privacy strong {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.svc-detail-related-feature-btn:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.feature-conflict-warning {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
padding: 10px 14px;
|
||||
background-color: rgba(229, 165, 10, 0.1);
|
||||
border: 1px solid rgba(229, 165, 10, 0.3);
|
||||
border-radius: 8px;
|
||||
font-size: 0.82rem;
|
||||
color: var(--yellow);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background-color: #d97706;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-warning:hover:not(:disabled) {
|
||||
background-color: #b45309;
|
||||
}
|
||||
|
||||
.svc-detail-restart-section {
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.svc-detail-restart-btn {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.svc-detail-restart-result {
|
||||
margin-top: 12px;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
font-size: 0.88rem;
|
||||
line-height: 1.5;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.svc-detail-restart-result.success {
|
||||
background-color: rgba(109, 191, 139, 0.12);
|
||||
border: 1px solid var(--green);
|
||||
color: var(--green);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.svc-detail-restart-result.error {
|
||||
background-color: rgba(239, 68, 68, 0.12);
|
||||
border: 1px solid #ef4444;
|
||||
color: #f87171;
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
/* ── Desktop launch buttons ──────────────────────────────────────── */
|
||||
|
||||
.svc-detail-launch-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.svc-detail-launch-btn {
|
||||
font-size: 0.85rem;
|
||||
padding: 8px 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
"use strict";
|
||||
|
||||
/* ── The Hub dashboard chrome ──────────────────────────────────────
|
||||
Category navigation, service search, status widgets, and the
|
||||
Systems Operational modal (router ports 80/443 + the live domain
|
||||
diagnostics checklist).
|
||||
|
||||
Everything lives in an IIFE so no globals leak into the other
|
||||
Hub scripts; tiles.js calls window.dashboardServicesUpdated()
|
||||
whenever the service list refreshes. */
|
||||
|
||||
(function () {
|
||||
|
||||
var $nav = document.getElementById("sidebar-nav");
|
||||
var $pageTitle = document.getElementById("page-title");
|
||||
var $widgets = document.getElementById("widgets");
|
||||
var $search = document.getElementById("search-input");
|
||||
var $sysModal = document.getElementById("systems-modal");
|
||||
var $sysBody = document.getElementById("systems-body");
|
||||
|
||||
var _cat = "all";
|
||||
var _query = "";
|
||||
|
||||
var CAT_ICONS = {
|
||||
"all": "g-grid",
|
||||
"infrastructure": "g-server",
|
||||
"bitcoin-base": "g-btc-sym",
|
||||
"bitcoin-apps": "g-btc-sym",
|
||||
"communication": "g-chat",
|
||||
"apps": "g-dots",
|
||||
"nostr": "g-antenna",
|
||||
"other": "g-dots"
|
||||
};
|
||||
|
||||
var CAT_FALLBACK_LABELS = {
|
||||
"infrastructure": "Infrastructure",
|
||||
"bitcoin-base": "Bitcoin",
|
||||
"bitcoin-apps": "Bitcoin Apps",
|
||||
"communication": "Communication",
|
||||
"apps": "Personal Apps",
|
||||
"nostr": "Nostr",
|
||||
"other": "Other"
|
||||
};
|
||||
|
||||
/* Units that serve a domain — used to find a live diagnostics
|
||||
checklist for the Systems Operational modal (order matters only
|
||||
for which service is polled first). */
|
||||
var DOMAIN_UNITS = [
|
||||
"matrix-synapse.service",
|
||||
"btcpayserver.service",
|
||||
"vaultwarden.service",
|
||||
"phpfpm-nextcloud.service",
|
||||
"phpfpm-wordpress.service",
|
||||
"haven-relay.service",
|
||||
"livekit.service",
|
||||
"albyhub.service"
|
||||
];
|
||||
|
||||
function icon(id) {
|
||||
return '<svg><use href="#' + id + '"/></svg>';
|
||||
}
|
||||
|
||||
function visibleServices() {
|
||||
var services = (typeof _servicesCache !== "undefined" && _servicesCache) ? _servicesCache : [];
|
||||
return services.filter(function (s) {
|
||||
return s.category !== "support" && s.type !== "support";
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Category navigation ─────────────────────────────────────── */
|
||||
|
||||
function renderNav() {
|
||||
if (!$nav) return;
|
||||
var counts = {};
|
||||
var order = [];
|
||||
visibleServices().forEach(function (s) {
|
||||
var cat = s.category || "other";
|
||||
if (!counts[cat]) { counts[cat] = 0; order.push(cat); }
|
||||
counts[cat]++;
|
||||
});
|
||||
var total = visibleServices().length;
|
||||
|
||||
var html = '<div class="nav-label">Services</div>';
|
||||
html += navItem("all", "All services", total);
|
||||
order.forEach(function (cat) {
|
||||
html += navItem(cat, catLabel(cat), counts[cat]);
|
||||
});
|
||||
$nav.innerHTML = html;
|
||||
|
||||
$nav.querySelectorAll(".nav-item").forEach(function (btn) {
|
||||
btn.addEventListener("click", function () {
|
||||
_cat = btn.dataset.cat;
|
||||
$nav.querySelectorAll(".nav-item").forEach(function (b) {
|
||||
b.classList.toggle("active", b === btn);
|
||||
});
|
||||
if ($pageTitle) {
|
||||
$pageTitle.textContent = _cat === "all" ? "Dashboard" : catLabel(_cat);
|
||||
}
|
||||
applyFilter();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function navItem(cat, label, count) {
|
||||
return '<button class="nav-item' + (cat === _cat ? " active" : "") + '" data-cat="' + escHtml(cat) + '" type="button">' +
|
||||
icon(CAT_ICONS[cat] || "g-dots") +
|
||||
'<span class="nav-text">' + escHtml(label) + '</span>' +
|
||||
'<span class="nav-count">' + count + '</span>' +
|
||||
'</button>';
|
||||
}
|
||||
|
||||
function catLabel(cat) {
|
||||
if (typeof _categoryLabels !== "undefined" && _categoryLabels[cat]) return _categoryLabels[cat];
|
||||
return CAT_FALLBACK_LABELS[cat] || cat;
|
||||
}
|
||||
|
||||
/* ── Filtering (category + search, applied together) ─────────── */
|
||||
|
||||
function applyFilter() {
|
||||
var area = document.getElementById("tiles-area");
|
||||
if (!area) return;
|
||||
var q = _query.trim().toLowerCase();
|
||||
area.querySelectorAll(".category-section").forEach(function (section) {
|
||||
var catMatch = (_cat === "all") || (section.dataset.category === _cat);
|
||||
var anyVisible = false;
|
||||
section.querySelectorAll(".service-tile").forEach(function (tile) {
|
||||
var nameEl = tile.querySelector(".tile-name");
|
||||
var name = nameEl ? nameEl.textContent.toLowerCase() : "";
|
||||
var match = catMatch && (!q || name.indexOf(q) !== -1);
|
||||
tile.style.display = match ? "" : "none";
|
||||
if (match) anyVisible = true;
|
||||
});
|
||||
section.style.display = (catMatch && (anyVisible || !q)) ? "" : "none";
|
||||
});
|
||||
}
|
||||
|
||||
if ($search) {
|
||||
$search.addEventListener("input", function () {
|
||||
_query = $search.value;
|
||||
applyFilter();
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Status widgets ──────────────────────────────────────────── */
|
||||
|
||||
function renderWidgets() {
|
||||
if (!$widgets) return;
|
||||
var services = visibleServices();
|
||||
if (!services.length) { $widgets.style.display = "none"; return; }
|
||||
|
||||
var running = 0, attention = 0, off = 0;
|
||||
services.forEach(function (s) {
|
||||
if (!s.enabled) { off++; return; }
|
||||
var h = s.health || s.status;
|
||||
if (h === "needs_attention" || h === "failed") attention++;
|
||||
else running++;
|
||||
});
|
||||
|
||||
var html = "";
|
||||
|
||||
/* Systems operational */
|
||||
var sub = '<b>' + running + '</b> running';
|
||||
if (attention) sub += ' · <span class="warn">' + attention + ' needs attention</span>';
|
||||
if (off) sub += ' · ' + off + ' off';
|
||||
var attentionTitle = attention ? "Systems need attention" : "Systems operational";
|
||||
html +=
|
||||
'<div class="widget clickable" id="w-systems" role="button" tabindex="0" title="System status and router setup">' +
|
||||
'<div class="widget-chip chip-green">' + icon("g-shield-check") + '</div>' +
|
||||
'<div class="w-body"><h3>' + attentionTitle + '</h3><div class="sub">' + sub + '</div></div>' +
|
||||
'<span class="w-chev">' + icon("g-chev") + '</span>' +
|
||||
'</div>';
|
||||
|
||||
/* Bitcoin Core sync */
|
||||
var btc = null;
|
||||
services.forEach(function (s) {
|
||||
if (s.sync_ibd && s.enabled) btc = s;
|
||||
});
|
||||
if (btc) {
|
||||
var pct = Math.round((btc.sync_progress || 0) * 100);
|
||||
var blocks = btc.sync_blocks ? btc.sync_blocks.toLocaleString() : "—";
|
||||
var eta = (typeof _calcBtcEta === "function") ? _calcBtcEta(btc.unit + "::" + btc.name, btc.sync_progress || 0) : "";
|
||||
html +=
|
||||
'<div class="widget clickable" id="w-btc" role="button" tabindex="0" title="' + escHtml(btc.name) + ' details">' +
|
||||
'<div class="widget-chip chip-btc"><img src="/static/icons/' + escHtml(btc.icon) + '.svg" alt="" style="width:46px;height:46px;display:block;object-fit:contain"/></div>' +
|
||||
'<div class="w-body"><h3>' + escHtml(btc.name) + ' — syncing timechain</h3>' +
|
||||
'<div class="w-bar"><div class="w-bar-fill" style="width:' + pct + '%"></div></div>' +
|
||||
'<div class="sub">Block <b>' + blocks + '</b> · ' + pct + '% · <span class="warn">' + escHtml(eta) + '</span></div></div>' +
|
||||
'<span class="w-chev">' + icon("g-chev") + '</span>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
$widgets.innerHTML = html;
|
||||
$widgets.style.display = "";
|
||||
|
||||
var wSys = document.getElementById("w-systems");
|
||||
if (wSys) {
|
||||
wSys.addEventListener("click", openSystemsModal);
|
||||
wSys.addEventListener("keydown", function (e) { if (e.key === "Enter") openSystemsModal(); });
|
||||
}
|
||||
var wBtc = document.getElementById("w-btc");
|
||||
if (wBtc && btc) {
|
||||
(function (svc) {
|
||||
wBtc.addEventListener("click", function () { openServiceDetailModal(svc.unit, svc.name, svc.icon); });
|
||||
wBtc.addEventListener("keydown", function (e) { if (e.key === "Enter") openServiceDetailModal(svc.unit, svc.name, svc.icon); });
|
||||
})(btc);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Systems Operational modal ───────────────────────────────── */
|
||||
|
||||
function internalIp() {
|
||||
var el = document.getElementById("ip-internal");
|
||||
var v = el ? el.textContent.trim() : "";
|
||||
return (v && v !== "…" && v !== "—") ? v : "";
|
||||
}
|
||||
|
||||
function step(n, title, sub, value) {
|
||||
return '<div class="sysstep"><div class="sysnum">' + n + '</div><div class="sysstep-x">' +
|
||||
'<div class="sysstep-t">' + title + (sub ? ' <span class="sysstep-sub">· ' + sub + '</span>' : '') + '</div>' +
|
||||
(value ? '<div class="sysval"><span class="sysval-text">' + value + '</span></div>' : '') +
|
||||
'</div></div>';
|
||||
}
|
||||
|
||||
function whoUsesPorts() {
|
||||
if (typeof _currentRole !== "undefined" && _currentRole === "node") {
|
||||
return 'On this <strong>Bitcoin Node</strong> install, BTCPay Server and Lightning Wallet Connections (LNURL) are the domain services that use these ports.';
|
||||
}
|
||||
return 'All your domain services share ports 80 and 443 — Matrix, BTCPay Server, VaultWarden, Nextcloud, WordPress, Haven Relay, Lightning Wallet Connections, and Element Calling.';
|
||||
}
|
||||
|
||||
function openSystemsModal() {
|
||||
if (!$sysModal || !$sysBody) return;
|
||||
var services = visibleServices();
|
||||
var running = 0, attention = 0, off = 0;
|
||||
var attentionNames = [], offNames = [];
|
||||
services.forEach(function (s) {
|
||||
if (!s.enabled) { off++; offNames.push(s.name); return; }
|
||||
var h = s.health || s.status;
|
||||
if (h === "needs_attention" || h === "failed") { attention++; attentionNames.push(s.name); }
|
||||
else running++;
|
||||
});
|
||||
|
||||
var ip = internalIp();
|
||||
var fwd = ip ? ('\u2192 ' + escHtml(ip)) : "forward to this computer";
|
||||
|
||||
var html = "";
|
||||
|
||||
/* System status */
|
||||
html += '<div class="sysmodal-card">' +
|
||||
'<div class="sysmodal-card-title">' + icon("g-shield-check") + 'System Status</div>' +
|
||||
step(1, "Services running", "", String(running)) +
|
||||
step(2, "Needs attention", "", attention ? escHtml(attentionNames.join(", ")) : "None") +
|
||||
step(3, "Turned off", "", off ? escHtml(offNames.join(", ")) : "None") +
|
||||
'</div>';
|
||||
|
||||
/* Router ports — repo wording (domain-prereqs.js / server.py) */
|
||||
html += '<div class="sysmodal-card">' +
|
||||
'<div class="sysmodal-card-title">' + icon("g-wifi") + 'Router — Ports to Forward</div>' +
|
||||
step(1, "Port 80", "TCP — HTTP (redirect to HTTPS)", fwd) +
|
||||
step(2, "Port 443", "TCP — HTTPS", fwd) +
|
||||
'<div class="sysnote"><div class="sysnote-title">' + icon("g-alert") + 'One router task</div>' +
|
||||
'<div class="sysnote-desc">Set the internal and external port to the <strong>same number</strong>. You only need to do this once — all your services share these two ports. Test from your phone on mobile data (your home network may not support hairpin NAT / loopback).</div></div>' +
|
||||
'</div>';
|
||||
|
||||
/* Live domain diagnostics (sequential checklist from the backend —
|
||||
same data the domain-service modals show) */
|
||||
html += '<div class="sysmodal-card" id="sys-check-card" style="display:none">' +
|
||||
'<div class="sysmodal-card-title">' + icon("g-check") + 'Ports 80 & 443 Check</div>' +
|
||||
'<div id="sys-check-steps"><div class="sysfineprint">Checking…</div></div>' +
|
||||
'</div>';
|
||||
|
||||
/* Who uses these ports */
|
||||
html += '<div class="sysnote" style="margin-top:14px">' +
|
||||
'<div class="sysnote-title">' + icon("g-antenna") + 'Who uses these ports</div>' +
|
||||
'<div class="sysnote-desc">' + whoUsesPorts() + '</div></div>';
|
||||
|
||||
$sysBody.innerHTML = html;
|
||||
$sysModal.classList.add("open");
|
||||
|
||||
/* Poll the first configured domain service for its live checklist */
|
||||
var checkCard = document.getElementById("sys-check-card");
|
||||
var stepsEl = document.getElementById("sys-check-steps");
|
||||
var units = DOMAIN_UNITS.filter(function (u) {
|
||||
return services.some(function (s) { return s.unit === u; });
|
||||
});
|
||||
if (!units.length || !checkCard || !stepsEl) return;
|
||||
checkCard.style.display = "";
|
||||
|
||||
apiFetch("/api/service-detail/" + encodeURIComponent(units[0]))
|
||||
.then(function (data) {
|
||||
var steps = (data && data.domain_check_steps) || [];
|
||||
if (!steps.length) { checkCard.style.display = "none"; return; }
|
||||
var mark = { ok: "\u2705", error: "\u274c", warning: "\u26a0\ufe0f", skipped: "\u23ed\ufe0f" };
|
||||
var html2 = "";
|
||||
steps.forEach(function (st) {
|
||||
var m = mark[st.status] || "\u2014";
|
||||
var detail = escHtml(st.detail || "").replace(/\n/g, "<br>");
|
||||
html2 += '<div class="sysstep"><div class="sysnum">' + st.step + '</div><div class="sysstep-x">' +
|
||||
'<div class="sysstep-t">' + m + " " + escHtml(st.label || "") + '</div>' +
|
||||
(detail ? '<div class="sysstep-t" style="font-weight:400;color:var(--text-2);font-size:0.77rem;margin-top:3px">' + detail + '</div>' : '') +
|
||||
'</div></div>';
|
||||
});
|
||||
stepsEl.innerHTML = html2;
|
||||
})
|
||||
.catch(function () {
|
||||
checkCard.style.display = "none";
|
||||
});
|
||||
}
|
||||
|
||||
function closeSystemsModal() {
|
||||
if ($sysModal) $sysModal.classList.remove("open");
|
||||
}
|
||||
|
||||
var sysClose = document.getElementById("systems-close-btn");
|
||||
if (sysClose) sysClose.addEventListener("click", closeSystemsModal);
|
||||
if ($sysModal) {
|
||||
$sysModal.addEventListener("click", function (e) {
|
||||
if (e.target === $sysModal) closeSystemsModal();
|
||||
});
|
||||
$sysModal.addEventListener("keydown", function (e) {
|
||||
if (e.key === "Escape") closeSystemsModal();
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Public hook for tiles.js ────────────────────────────────── */
|
||||
|
||||
window.dashboardServicesUpdated = function () {
|
||||
renderNav();
|
||||
renderWidgets();
|
||||
applyFilter();
|
||||
};
|
||||
|
||||
})();
|
||||
@@ -175,9 +175,9 @@ function showSecurityBanner() {
|
||||
'</div>' +
|
||||
'<button class="security-banner-dismiss" id="security-banner-dismiss-btn" title="Dismiss">\u2715</button>';
|
||||
|
||||
var mainContent = document.querySelector(".main-content");
|
||||
if (mainContent) {
|
||||
mainContent.insertAdjacentElement("beforebegin", banner);
|
||||
var contentArea = document.querySelector(".content");
|
||||
if (contentArea) {
|
||||
contentArea.insertAdjacentElement("afterbegin", banner);
|
||||
} else {
|
||||
document.body.insertAdjacentElement("afterbegin", banner);
|
||||
}
|
||||
|
||||
@@ -696,6 +696,17 @@ async function openServiceDetailModal(unit, name, icon) {
|
||||
: (data.health || data.status);
|
||||
var sc = statusClass(effectiveHealth);
|
||||
var st = statusText(effectiveHealth, effectiveEnabled);
|
||||
if ($credsTitle) {
|
||||
var existingPill = $credsTitle.querySelector(".creds-title-status-pill");
|
||||
if (!existingPill) {
|
||||
var pill = document.createElement("span");
|
||||
pill.className = "creds-title-status-pill";
|
||||
$credsTitle.appendChild(pill);
|
||||
existingPill = pill;
|
||||
}
|
||||
existingPill.className = "creds-title-status-pill st-" + sc;
|
||||
existingPill.textContent = st;
|
||||
}
|
||||
addSetup('<div class="svc-detail-section">' +
|
||||
'<div class="svc-detail-section-title">Status</div>' +
|
||||
'<div class="svc-detail-status">' +
|
||||
|
||||
@@ -52,6 +52,7 @@ function buildTiles(services, categoryLabels) {
|
||||
if ($tilesArea.children.length === 0) {
|
||||
$tilesArea.innerHTML = '<div class="empty-state"><p>No services configured.</p></div>';
|
||||
}
|
||||
if (typeof window.dashboardServicesUpdated === "function") window.dashboardServicesUpdated();
|
||||
}
|
||||
|
||||
function renderSidebarSupport(supportServices) {
|
||||
@@ -62,7 +63,7 @@ function renderSidebarSupport(supportServices) {
|
||||
sidebarUpdateBtn.className = "sidebar-support-btn";
|
||||
sidebarUpdateBtn.id = "sidebar-btn-update";
|
||||
sidebarUpdateBtn.innerHTML =
|
||||
'<img class="sidebar-support-icon" src="/static/icons/update.svg" alt="Update" style="width:1.5rem;height:1.5rem;">' +
|
||||
'<span class="sidebar-support-icon"><svg><use href="#g-refresh"/></svg></span>' +
|
||||
'<span class="sidebar-support-text">' +
|
||||
'<span class="sidebar-support-title">Update System</span>' +
|
||||
'<span class="sidebar-support-hint" id="sidebar-update-hint">Check for updates</span>' +
|
||||
@@ -75,7 +76,7 @@ function renderSidebarSupport(supportServices) {
|
||||
var btn = document.createElement("button");
|
||||
btn.className = "sidebar-support-btn";
|
||||
btn.innerHTML =
|
||||
'<span class="sidebar-support-icon">🛟</span>' +
|
||||
'<span class="sidebar-support-icon"><svg><use href="#g-lifebuoy"/></svg></span>' +
|
||||
'<span class="sidebar-support-text">' +
|
||||
'<span class="sidebar-support-title">' + escHtml(svc.name || "Tech Support") + '</span>' +
|
||||
'<span class="sidebar-support-hint">Click for help</span>' +
|
||||
@@ -88,7 +89,7 @@ function renderSidebarSupport(supportServices) {
|
||||
var backupBtn = document.createElement("button");
|
||||
backupBtn.className = "sidebar-support-btn";
|
||||
backupBtn.innerHTML =
|
||||
'<span class="sidebar-support-icon">💾</span>' +
|
||||
'<span class="sidebar-support-icon"><svg><use href="#g-box"/></svg></span>' +
|
||||
'<span class="sidebar-support-text">' +
|
||||
'<span class="sidebar-support-title">Manual Backup</span>' +
|
||||
'<span class="sidebar-support-hint">Back up to external drive</span>' +
|
||||
@@ -100,7 +101,7 @@ function renderSidebarSupport(supportServices) {
|
||||
var securityBtn = document.createElement("button");
|
||||
securityBtn.className = "sidebar-support-btn";
|
||||
securityBtn.innerHTML =
|
||||
'<span class="sidebar-support-icon">\uD83D\uDEE1</span>' +
|
||||
'<span class="sidebar-support-icon"><svg><use href="#g-shield-check"/></svg></span>' +
|
||||
'<span class="sidebar-support-text">' +
|
||||
'<span class="sidebar-support-title">Security</span>' +
|
||||
'<span class="sidebar-support-hint">Reset & verify system</span>' +
|
||||
@@ -113,7 +114,7 @@ function renderSidebarSupport(supportServices) {
|
||||
var upgradeBtn = document.createElement("button");
|
||||
upgradeBtn.className = "sidebar-support-btn";
|
||||
upgradeBtn.innerHTML =
|
||||
'<span class="sidebar-support-icon">🚀</span>' +
|
||||
'<span class="sidebar-support-icon"><svg><use href="#g-antenna"/></svg></span>' +
|
||||
'<span class="sidebar-support-text">' +
|
||||
'<span class="sidebar-support-title">Upgrade to Full Server</span>' +
|
||||
'<span class="sidebar-support-hint">Unlock all services</span>' +
|
||||
@@ -237,6 +238,7 @@ function updateTiles(services) {
|
||||
if (text) text.textContent = st;
|
||||
}
|
||||
}
|
||||
if (typeof window.dashboardServicesUpdated === "function") window.dashboardServicesUpdated();
|
||||
}
|
||||
|
||||
// ── Service polling ───────────────────────────────────────────────
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Sovran_SystemsOS Hub</title>
|
||||
<title>Sovran_SystemsOS — The Hub</title>
|
||||
<link rel="stylesheet" href="/static/css/base.css?v={{ asset_version }}" />
|
||||
<link rel="stylesheet" href="/static/css/buttons.css?v={{ asset_version }}" />
|
||||
<link rel="stylesheet" href="/static/css/header.css?v={{ asset_version }}" />
|
||||
@@ -18,50 +18,241 @@
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Header bar -->
|
||||
<header class="header-bar">
|
||||
<img src="/static/sovran-hub-icon.svg" alt="Sovran Hub" class="header-logo" />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
|
||||
|
||||
<div class="title-group">
|
||||
<span class="title">Sovran_SystemsOS Hub</span>
|
||||
<!-- The Hub logo (verbatim from app/sovran_systemsos_web/static/sovran-hub-icon.svg) -->
|
||||
<symbol id="g-sovran" viewBox="0 0 256 256">
|
||||
<linearGradient id="sv-bg" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#153126"/><stop offset="55%" stop-color="#0F241B"/><stop offset="100%" stop-color="#091C14"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="sv-outer" x1="70" y1="40" x2="190" y2="210" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0%" stop-color="#42F39A"/><stop offset="45%" stop-color="#28D978"/><stop offset="100%" stop-color="#1AA45D"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="sv-inner" x1="90" y1="60" x2="180" y2="190" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0%" stop-color="#27C86F"/><stop offset="100%" stop-color="#157E49"/>
|
||||
</linearGradient>
|
||||
<rect width="256" height="256" rx="48" ry="48" fill="url(#sv-bg)"/>
|
||||
<rect x="1.5" y="1.5" width="253" height="253" rx="46.5" ry="46.5" fill="none" stroke="rgba(255,255,255,0.08)"/>
|
||||
<path d="M128 32 A96 96 0 1 1 58 196" fill="none" stroke="url(#sv-outer)" stroke-width="12" stroke-linecap="round"/>
|
||||
<path d="M128 56 A72 72 0 1 1 76 178" fill="none" stroke="url(#sv-inner)" stroke-width="10" stroke-linecap="round"/>
|
||||
<circle cx="128" cy="128" r="8" fill="#F2FFF7"/>
|
||||
<circle cx="128" cy="128" r="18" fill="none" stroke="#7BFFC0" stroke-opacity="0.14" stroke-width="4"/>
|
||||
</symbol>
|
||||
|
||||
<!-- OS Version Badge — styled identically to the version badge in service modals -->
|
||||
<span class="os-version-badge" id="os-version-badge" title="Sovran_SystemsOS Version">v{{ sovran_version }}</span>
|
||||
<!-- service glyphs -->
|
||||
<!-- ═══ Real service logos — extracted verbatim from app/icons/*.svg ═══
|
||||
(internal ids namespaced per icon to avoid collisions) -->
|
||||
|
||||
<!-- symbolic helpers (nav + modal internals) -->
|
||||
<!-- Bitcoin nav icon: the OFFICIAL logo silhouette (paths taken verbatim
|
||||
from app/icons/bitcoin-core.svg) rendered monochrome in currentColor
|
||||
so it matches the symbolic sidebar set -->
|
||||
<symbol id="g-btc-sym" viewBox="-34 -34 580 580">
|
||||
<path fill="currentColor" d="M317.871 7.656c-137.12-34.192-276.024 49.28-310.2 186.44-34.208 137.136 49.256 276.048 186.36 310.24 137.16 34.199 276.063-49.265 310.256-186.408 34.192-137.152-49.264-276.08-186.416-310.272m50.936 211.872c-3.688 24.936-17.512 37.008-35.864 41.24 25.2 13.12 38.024 33.239 25.809 68.12-15.16 43.319-51.176 46.976-99.072 37.912l-11.624 46.584-28.088-7 11.472-45.96a1076 1076 0 0 1-22.384-5.809l-11.512 46.177-28.056-7 11.624-46.673c-6.561-1.68-13.225-3.464-20.024-5.168l-36.552-9.111 13.943-32.152s20.696 5.504 20.416 5.096c7.952 1.969 11.48-3.216 12.872-6.672l18.368-73.64.048-.2 13.104-52.568c.344-5.968-1.712-13.496-13.088-16.336.439-.296-20.4-5.072-20.4-5.072l7.472-30 38.736 9.673-.032.144c5.824 1.448 11.824 2.824 17.937 4.216L245.423 89.2l28.072 7-11.28 45.224c7.536 1.721 15.12 3.456 22.504 5.297l11.2-44.929 28.088 7-11.504 46.145c35.464 12.215 61.401 30.527 56.304 64.591"/>
|
||||
<path fill="currentColor" d="m254.647 174.6-13.983 56.08c15.855 3.951 64.735 20.071 72.656-11.656 8.248-33.096-42.817-40.472-58.673-44.424"/>
|
||||
<path fill="currentColor" d="m233.608 258.984-15.425 61.832c19.04 4.729 77.769 23.584 86.448-11.296 9.072-36.376-51.984-45.784-71.023-50.536"/>
|
||||
</symbol>
|
||||
|
||||
<symbol id="g-chat" viewBox="0 0 24 24">
|
||||
<g fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M20 5.5v7a2.5 2.5 0 0 1-2.5 2.5H10l-4 3.2c-.6.5-1 .3-1-.5V5.5A2.5 2.5 0 0 1 7.5 3h10A2.5 2.5 0 0 1 20 5.5z"/>
|
||||
<circle cx="8.7" cy="9.2" r="0.5" fill="currentColor"/><circle cx="12" cy="9.2" r="0.5" fill="currentColor"/><circle cx="15.3" cy="9.2" r="0.5" fill="currentColor"/>
|
||||
</g>
|
||||
</symbol>
|
||||
|
||||
<symbol id="g-antenna" viewBox="0 0 24 24">
|
||||
<g fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round">
|
||||
<path d="M5.4 9.9a9.3 9.3 0 0 1 13.2 0"/>
|
||||
<path d="M8.1 12.9a5.6 5.6 0 0 1 7.8 0"/>
|
||||
<circle cx="12" cy="16.6" r="1.5" fill="currentColor" stroke="none"/>
|
||||
<path d="M12 16.6V21"/>
|
||||
</g>
|
||||
</symbol>
|
||||
|
||||
<symbol id="g-lock" viewBox="0 0 24 24">
|
||||
<g fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="5.5" y="10.5" width="13" height="9.5" rx="2.6"/>
|
||||
<path d="M8.5 10.5V8a3.5 3.5 0 0 1 7 0v2.5"/>
|
||||
<circle cx="12" cy="15" r="1.3" fill="currentColor" stroke="none"/>
|
||||
</g>
|
||||
</symbol>
|
||||
<!-- nav + ui symbolic icons -->
|
||||
<symbol id="g-grid" viewBox="0 0 24 24">
|
||||
<g fill="none" stroke="currentColor" stroke-width="1.9" stroke-linejoin="round">
|
||||
<rect x="3.5" y="3.5" width="7.2" height="7.2" rx="2"/><rect x="13.3" y="3.5" width="7.2" height="7.2" rx="2"/>
|
||||
<rect x="3.5" y="13.3" width="7.2" height="7.2" rx="2"/><rect x="13.3" y="13.3" width="7.2" height="7.2" rx="2"/>
|
||||
</g>
|
||||
</symbol>
|
||||
<symbol id="g-server" viewBox="0 0 24 24">
|
||||
<g fill="none" stroke="currentColor" stroke-width="1.9" stroke-linejoin="round">
|
||||
<rect x="3.5" y="3.5" width="17" height="7.4" rx="2"/><rect x="3.5" y="13.1" width="17" height="7.4" rx="2"/>
|
||||
<circle cx="7.2" cy="7.2" r="0.6" fill="currentColor"/><circle cx="7.2" cy="16.8" r="0.6" fill="currentColor"/>
|
||||
</g>
|
||||
</symbol>
|
||||
<symbol id="g-dots" viewBox="0 0 24 24">
|
||||
<g fill="currentColor">
|
||||
<circle cx="5" cy="5" r="1.7"/><circle cx="12" cy="5" r="1.7"/><circle cx="19" cy="5" r="1.7"/>
|
||||
<circle cx="5" cy="12" r="1.7"/><circle cx="12" cy="12" r="1.7"/><circle cx="19" cy="12" r="1.7"/>
|
||||
<circle cx="5" cy="19" r="1.7"/><circle cx="12" cy="19" r="1.7"/><circle cx="19" cy="19" r="1.7"/>
|
||||
</g>
|
||||
</symbol>
|
||||
<symbol id="g-refresh" viewBox="0 0 24 24">
|
||||
<g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||||
<path d="M20 12a8 8 0 1 1-2.9-6.2"/>
|
||||
<path d="M20.6 2.6l-.5 4.9-4.6-1.7z" fill="currentColor" stroke="none"/>
|
||||
</g>
|
||||
</symbol>
|
||||
<symbol id="g-box" viewBox="0 0 24 24">
|
||||
<g fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="3.5" y="4" width="17" height="4.4" rx="1.6"/>
|
||||
<path d="M5.2 8.4v9a2.4 2.4 0 0 0 2.4 2.4h8.8a2.4 2.4 0 0 0 2.4-2.4v-9"/>
|
||||
<path d="M10 12.5h4"/>
|
||||
</g>
|
||||
</symbol>
|
||||
<symbol id="g-shield-check" viewBox="0 0 24 24">
|
||||
<g fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 2.5 4.5 5.5v6c0 4.6 3.1 8 7.5 9.9 4.4-1.9 7.5-5.3 7.5-9.9v-6z"/>
|
||||
<path d="M8.6 12l2.4 2.4 4.4-4.6"/>
|
||||
</g>
|
||||
</symbol>
|
||||
<symbol id="g-lifebuoy" viewBox="0 0 24 24">
|
||||
<g fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round">
|
||||
<circle cx="12" cy="12" r="8.6"/><circle cx="12" cy="12" r="3.6"/>
|
||||
<path d="M6 6l3.2 3.2M18 6l-3.2 3.2M6 18l3.2-3.2M18 18l-3.2-3.2"/>
|
||||
</g>
|
||||
</symbol>
|
||||
<symbol id="g-power" viewBox="0 0 24 24">
|
||||
<g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||||
<path d="M7.2 6.1a7 7 0 1 0 9.6 0"/>
|
||||
<path d="M12 2.8v8.4"/>
|
||||
</g>
|
||||
</symbol>
|
||||
<symbol id="g-logout" viewBox="0 0 24 24">
|
||||
<g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M14 4h4.5A1.5 1.5 0 0 1 20 5.5v13a1.5 1.5 0 0 1-1.5 1.5H14"/>
|
||||
<path d="M4 12h10M10.5 8.5 14 12l-3.5 3.5"/>
|
||||
</g>
|
||||
</symbol>
|
||||
<symbol id="g-search" viewBox="0 0 24 24">
|
||||
<g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||||
<circle cx="11" cy="11" r="6.5"/>
|
||||
<path d="M15.8 15.8 21 21"/>
|
||||
</g>
|
||||
</symbol>
|
||||
<symbol id="g-copy" viewBox="0 0 24 24">
|
||||
<g fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="9" y="9" width="11" height="11" rx="2.2"/>
|
||||
<path d="M5.5 15H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v.5"/>
|
||||
</g>
|
||||
</symbol>
|
||||
<symbol id="g-check" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.5l5 5 10-11"/></symbol>
|
||||
<symbol id="g-chev" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" d="M9.5 5.5 16 12l-6.5 6.5"/></symbol>
|
||||
<symbol id="g-x" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" d="M6 6l12 12M18 6 6 18"/></symbol>
|
||||
<symbol id="g-wifi" viewBox="0 0 24 24">
|
||||
<g fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round">
|
||||
<path d="M4.5 10.2a11 11 0 0 1 15 0"/>
|
||||
<path d="M7.3 13.3a7 7 0 0 1 9.4 0"/>
|
||||
<circle cx="12" cy="17" r="1.4" fill="currentColor" stroke="none"/>
|
||||
</g>
|
||||
</symbol>
|
||||
<symbol id="g-key" viewBox="0 0 24 24">
|
||||
<g fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="7.5" cy="15.5" r="4"/>
|
||||
<path d="M10.4 12.6 20 3M16.2 6.8l2.4 2.4M18.8 4.2l2.2 2.2"/>
|
||||
</g>
|
||||
</symbol>
|
||||
<symbol id="g-alert" viewBox="0 0 24 24">
|
||||
<g fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 3.6 22.2 20.4H1.8z"/>
|
||||
<path d="M12 9.8v4.6"/><circle cx="12" cy="17.2" r="0.6" fill="currentColor" stroke="none"/>
|
||||
</g>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
||||
<div class="app">
|
||||
|
||||
<!-- ═══ SIDEBAR ═══ -->
|
||||
<aside class="sidebar" id="sidebar">
|
||||
<div class="brand">
|
||||
<svg class="brand-logo"><use href="#g-sovran"/></svg>
|
||||
<div class="brand-text">
|
||||
<div class="brand-title">The <em>Hub</em></div>
|
||||
<div class="brand-sub">Sovran_SystemsOS v{{ sovran_version }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav id="sidebar-nav" aria-label="Service categories"></nav>
|
||||
|
||||
<div class="nav-label">System</div>
|
||||
<div id="sidebar-support"></div>
|
||||
|
||||
<!-- Feature Manager + Preferences (features.js) -->
|
||||
<div id="sidebar-features"></div>
|
||||
|
||||
<div class="sidebar-spacer"></div>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<span class="role-badge" id="role-badge-wrap" title="Sovran_SystemsOS role">
|
||||
<svg width="13" height="13"><use href="#g-shield-check"/></svg>
|
||||
<span id="role-badge">Loading…</span>
|
||||
</span>
|
||||
<div class="host-note">sovransystemsos.local</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- ═══ MAIN ═══ -->
|
||||
<div class="main">
|
||||
<header class="topbar">
|
||||
<div class="page-title" id="page-title">Dashboard</div>
|
||||
|
||||
<div class="search-box">
|
||||
<svg><use href="#g-search"/></svg>
|
||||
<input id="search-input" type="text" placeholder="Search services…" autocomplete="off" />
|
||||
</div>
|
||||
|
||||
<div class="net-chip" title="Internal IP · External IP">
|
||||
<svg><use href="#g-wifi"/></svg>
|
||||
<span class="int-wrap"><span class="ip-label">LAN</span><span class="ip-value" id="ip-internal">…</span></span>
|
||||
<span class="net-separator">|</span>
|
||||
<span class="ext-wrap"><span class="ip-label">WAN</span><span class="ip-value" id="ip-external">…</span></span>
|
||||
</div>
|
||||
|
||||
<div class="top-actions">
|
||||
<button class="btn btn-header-reboot" id="btn-header-reboot" title="Restart the entire computer">Reboot</button>
|
||||
<button class="btn btn-logout" id="btn-logout" title="Sign out">Sign Out</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="content" id="content">
|
||||
<!-- Widgets row (dashboard.js) -->
|
||||
<section class="widgets" id="widgets" style="display:none"></section>
|
||||
|
||||
<div id="tiles-area">
|
||||
<div class="dashboard-loading" role="status" aria-live="polite">
|
||||
<span class="dashboard-loading-spinner" aria-hidden="true"></span>
|
||||
<span>Loading service status…</span>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<div class="header-buttons">
|
||||
<span class="role-badge" id="role-badge">Loading…</span>
|
||||
<button class="btn btn-header-reboot" id="btn-header-reboot" title="Restart the entire computer">Reboot</button>
|
||||
<button class="btn btn-logout" id="btn-logout" title="Sign out">Sign Out</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- IP bar -->
|
||||
<div class="ip-bar">
|
||||
<span>
|
||||
<span class="ip-label">Internal IP:</span>
|
||||
<span class="ip-value" id="ip-internal">…</span>
|
||||
</span>
|
||||
<span class="ip-separator">|</span>
|
||||
<span>
|
||||
<span class="ip-label">External IP:</span>
|
||||
<span class="ip-value" id="ip-external">…</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Service tiles -->
|
||||
<main class="main-content">
|
||||
<aside class="sidebar" id="sidebar">
|
||||
<div id="sidebar-support"></div>
|
||||
<div id="sidebar-features"></div>
|
||||
</aside>
|
||||
<div id="tiles-area">
|
||||
<div class="dashboard-loading" role="status" aria-live="polite">
|
||||
<span class="dashboard-loading-spinner" aria-hidden="true"></span>
|
||||
<span>Loading service status…</span>
|
||||
<!-- ═══ SYSTEMS OPERATIONAL MODAL (dashboard.js) ═══ -->
|
||||
<div class="modal-overlay" id="systems-modal" role="dialog" aria-modal="true" aria-labelledby="systems-modal-title">
|
||||
<div class="creds-dialog">
|
||||
<div class="creds-header">
|
||||
<span class="creds-title" id="systems-modal-title">
|
||||
<span class="widget-chip chip-green" style="width:54px;height:54px;border-radius:16px"><svg style="width:27px;height:27px"><use href="#g-shield-check"/></svg></span>
|
||||
<span>Systems Operational</span>
|
||||
</span>
|
||||
<button class="creds-close-btn" id="systems-close-btn" title="Close">✕</button>
|
||||
</div>
|
||||
<div class="creds-body" id="systems-body">
|
||||
<p class="creds-loading">Loading…</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Update modal -->
|
||||
<div class="modal-overlay" id="update-modal" role="dialog" aria-modal="true" aria-labelledby="modal-title-text">
|
||||
@@ -256,7 +447,7 @@
|
||||
<div class="reboot-overlay" id="reboot-overlay">
|
||||
<!-- Normal restarting card -->
|
||||
<div class="reboot-card" id="reboot-main-card">
|
||||
<div class="reboot-icon" aria-hidden="true">↻</div>
|
||||
<div class="reboot-icon" aria-hidden="true"></div>
|
||||
<h2 class="reboot-title">Restarting Entire System</h2>
|
||||
<p class="reboot-message">
|
||||
The entire computer is restarting, including the desktop and all hosted services.<br />
|
||||
@@ -271,7 +462,7 @@
|
||||
</div>
|
||||
<!-- Error card (shown if restart request fails definitively) -->
|
||||
<div class="reboot-card" id="reboot-error-card" style="display:none">
|
||||
<div class="reboot-icon" aria-hidden="true">⚠</div>
|
||||
<div class="reboot-icon" aria-hidden="true"></div>
|
||||
<h2 class="reboot-title">Restart could not be started</h2>
|
||||
<p class="reboot-message">
|
||||
The computer did not begin restarting. No services were intentionally stopped. Please try again.
|
||||
@@ -316,6 +507,7 @@
|
||||
<script src="/static/js/rebuild.js?v={{ asset_version }}"></script>
|
||||
<script src="/static/js/features.js?v={{ asset_version }}"></script>
|
||||
<script src="/static/js/security.js?v={{ asset_version }}"></script>
|
||||
<script src="/static/js/dashboard.js?v={{ asset_version }}"></script>
|
||||
<script src="/static/js/events.js?v={{ asset_version }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Sovran Hub — Login</title>
|
||||
<title>The Hub — Login</title>
|
||||
<link rel="stylesheet" href="/static/css/base.css?v={{ asset_version }}" />
|
||||
<link rel="stylesheet" href="/static/css/buttons.css?v={{ asset_version }}" />
|
||||
</head>
|
||||
@@ -11,8 +11,9 @@
|
||||
<div class="login-wrapper">
|
||||
<div class="login-card">
|
||||
<div class="login-header">
|
||||
<img src="/static/sovran-hub-icon.svg" alt="Sovran Hub" class="login-logo" />
|
||||
<div class="login-title">Sovran Hub</div>
|
||||
<img src="/static/sovran-hub-icon.svg" alt="The Hub" class="login-logo" />
|
||||
<div class="login-title">The <em>Hub</em></div>
|
||||
<div class="login-sub">Sovran_SystemsOS</div>
|
||||
</div>
|
||||
|
||||
<form class="login-form" id="login-form" onsubmit="return false;">
|
||||
|
||||
@@ -50,10 +50,10 @@
|
||||
<p class="onboarding-body-text" style="text-align:center; margin-bottom:4px;">
|
||||
Your new login password is:
|
||||
</p>
|
||||
<div id="migration-password-value" style="font-family:monospace; font-size:1.35rem; font-weight:700; color:var(--text-primary); background:rgba(109, 191, 139, 0.10); border:1.5px solid rgba(109, 191, 139, 0.35); border-radius:8px; padding:14px 24px; letter-spacing:0.04em; text-align:center; word-break:break-all; margin-bottom:8px;">
|
||||
<div id="migration-password-value" style="font-family:monospace; font-size:1.35rem; font-weight:700; color:var(--text-primary); background:rgba(62, 207, 142, 0.10); border:1.5px solid rgba(62, 207, 142, 0.35); border-radius:8px; padding:14px 24px; letter-spacing:0.04em; text-align:center; word-break:break-all; margin-bottom:8px;">
|
||||
|
||||
</div>
|
||||
<div style="padding:10px 14px; background-color:rgba(229, 165, 10, 0.1); border:1px solid rgba(229, 165, 10, 0.35); border-radius:8px; font-size:0.92rem; color:var(--yellow); line-height:1.55;">
|
||||
<div style="padding:10px 14px; background-color:rgba(233, 182, 74, 0.1); border:1px solid rgba(233, 182, 74, 0.35); border-radius:8px; font-size:0.92rem; color:var(--yellow); line-height:1.55;">
|
||||
⚠ Write this password down! You will need it to log in next time. This is also your Sovran Hub login password.
|
||||
</div>
|
||||
<div id="migration-password-status" class="onboarding-save-status" style="margin-top:8px;"></div>
|
||||
|
||||
Reference in New Issue
Block a user