Merge pull request #24 from naturallaw777/copilot/refactor-sidebar-layout

[WIP] Refactor dashboard layout to include sidebar
This commit is contained in:
Sovran_Systems
2026-04-03 13:02:39 -05:00
committed by GitHub
3 changed files with 172 additions and 12 deletions

View File

@@ -15,8 +15,6 @@ const CATEGORY_ORDER = [
"communication", "communication",
"apps", "apps",
"nostr", "nostr",
"support",
"feature-manager",
]; ];
const FEATURE_SUBCATEGORY_LABELS = { const FEATURE_SUBCATEGORY_LABELS = {
@@ -59,6 +57,8 @@ let _rebuildIsEnabling = true;
// ── DOM refs ────────────────────────────────────────────────────── // ── DOM refs ──────────────────────────────────────────────────────
const $tilesArea = document.getElementById("tiles-area"); const $tilesArea = document.getElementById("tiles-area");
const $sidebarSupport = document.getElementById("sidebar-support");
const $sidebarFeatures = document.getElementById("sidebar-features");
const $updateBtn = document.getElementById("btn-update"); const $updateBtn = document.getElementById("btn-update");
const $updateBadge = document.getElementById("update-badge"); const $updateBadge = document.getElementById("update-badge");
const $refreshBtn = document.getElementById("btn-refresh"); const $refreshBtn = document.getElementById("btn-refresh");
@@ -172,11 +172,19 @@ async function apiFetch(path, options) {
function buildTiles(services, categoryLabels) { function buildTiles(services, categoryLabels) {
_servicesCache = services; _servicesCache = services;
var grouped = {}; var grouped = {};
var supportServices = [];
for (var i = 0; i < services.length; i++) { for (var i = 0; i < services.length; i++) {
var cat = services[i].category || "other"; var svc = services[i];
if (!grouped[cat]) grouped[cat] = []; // Support tiles go to the sidebar, not the main grid
grouped[cat].push(services[i]); if (svc.category === "support" || svc.type === "support") {
supportServices.push(svc);
continue;
} }
var cat = svc.category || "other";
if (!grouped[cat]) grouped[cat] = [];
grouped[cat].push(svc);
}
renderSidebarSupport(supportServices);
$tilesArea.innerHTML = ""; $tilesArea.innerHTML = "";
var orderedKeys = CATEGORY_ORDER.filter(function(k) { return grouped[k]; }); var orderedKeys = CATEGORY_ORDER.filter(function(k) { return grouped[k]; });
Object.keys(grouped).forEach(function(k) { Object.keys(grouped).forEach(function(k) {
@@ -202,6 +210,28 @@ function buildTiles(services, categoryLabels) {
} }
} }
function renderSidebarSupport(supportServices) {
$sidebarSupport.innerHTML = "";
for (var i = 0; i < supportServices.length; i++) {
var svc = supportServices[i];
var btn = document.createElement("button");
btn.className = "sidebar-support-btn";
btn.innerHTML =
'<span class="sidebar-support-icon">🛟</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>' +
'</span>';
btn.addEventListener("click", function() { openSupportModal(); });
$sidebarSupport.appendChild(btn);
}
if (supportServices.length > 0) {
var hr = document.createElement("hr");
hr.className = "sidebar-divider";
$sidebarSupport.appendChild(hr);
}
}
function buildTile(svc) { function buildTile(svc) {
var isSupport = svc.type === "support"; var isSupport = svc.type === "support";
var sc = statusClass(svc.status); var sc = statusClass(svc.status);
@@ -1194,7 +1224,7 @@ async function loadFeatureManager() {
function renderFeatureManager(data) { function renderFeatureManager(data) {
// Remove old feature manager section if it exists // Remove old feature manager section if it exists
var old = $tilesArea.querySelector(".feature-manager-section"); var old = $sidebarFeatures.querySelector(".feature-manager-section");
if (old) old.parentNode.removeChild(old); if (old) old.parentNode.removeChild(old);
var section = document.createElement("div"); var section = document.createElement("div");
@@ -1236,7 +1266,7 @@ function renderFeatureManager(data) {
section.appendChild(subcat); section.appendChild(subcat);
} }
$tilesArea.appendChild(section); $sidebarFeatures.appendChild(section);
} }
function buildFeatureCard(feat) { function buildFeatureCard(feat) {

View File

@@ -37,6 +37,9 @@ body {
color: var(--text-primary); color: var(--text-primary);
line-height: 1.5; line-height: 1.5;
min-height: 100vh; min-height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
} }
/* ── Header bar ─────────────────────────────────────────────────── */ /* ── Header bar ─────────────────────────────────────────────────── */
@@ -189,9 +192,87 @@ button:disabled {
/* ── Main content ───────────────────────────────────────────────── */ /* ── Main content ───────────────────────────────────────────────── */
.main-content { .main-content {
max-width: 980px; display: flex;
margin: 0 auto; align-items: flex-start;
padding: 24px 16px 48px; flex: 1;
overflow: hidden;
}
/* ── Sidebar ────────────────────────────────────────────────────── */
.sidebar {
width: 270px;
flex-shrink: 0;
height: 100%;
overflow-y: auto;
border-right: 1px solid var(--border-color);
background-color: var(--surface-color);
padding: 20px 14px;
display: flex;
flex-direction: column;
gap: 0;
}
/* ── Sidebar: Tech Support button ───────────────────────────────── */
.sidebar-support-btn {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
background-color: var(--card-color);
border: 2px dashed var(--accent-color);
border-radius: 12px;
padding: 12px 14px;
color: var(--text-primary);
cursor: pointer;
transition: border-style 0.15s, border-color 0.15s, background-color 0.15s;
text-align: left;
}
.sidebar-support-btn:hover {
border-style: solid;
border-color: #a8c8ff;
background-color: #35354a;
}
.sidebar-support-icon {
font-size: 1.5rem;
flex-shrink: 0;
}
.sidebar-support-text {
display: flex;
flex-direction: column;
gap: 2px;
}
.sidebar-support-title {
font-size: 0.88rem;
font-weight: 700;
color: var(--text-primary);
}
.sidebar-support-hint {
font-size: 0.72rem;
color: var(--accent-color);
font-weight: 600;
}
.sidebar-divider {
border: none;
border-top: 1px solid var(--border-color);
margin: 16px 0;
}
/* ── Tiles area ─────────────────────────────────────────────────── */
#tiles-area {
flex: 1;
height: 100%;
overflow-y: auto;
padding: 24px 20px 48px;
min-width: 0;
} }
/* ── Category sections ──────────────────────────────────────────── */ /* ── Category sections ──────────────────────────────────────────── */
@@ -859,6 +940,28 @@ button.btn-reboot:hover:not(:disabled) {
/* ── Responsive ─────────────────────────────────────────────────── */ /* ── Responsive ─────────────────────────────────────────────────── */
@media (max-width: 768px) {
body {
overflow: auto;
}
.main-content {
flex-direction: column;
overflow: visible;
}
.sidebar {
width: 100%;
height: auto;
border-right: none;
border-bottom: 1px solid var(--border-color);
padding: 14px 12px;
}
#tiles-area {
height: auto;
overflow-y: visible;
padding: 16px 12px 40px;
}
}
@media (max-width: 600px) { @media (max-width: 600px) {
.header-bar { .header-bar {
padding: 10px 14px; padding: 10px 14px;
@@ -872,9 +975,6 @@ button.btn-reboot:hover:not(:disabled) {
flex-wrap: wrap; flex-wrap: wrap;
padding: 8px 14px; padding: 8px 14px;
} }
.main-content {
padding: 16px 12px 40px;
}
.tiles-grid { .tiles-grid {
justify-content: center; justify-content: center;
} }
@@ -1444,3 +1544,29 @@ button.btn-reboot:hover:not(:disabled) {
.tile-ports-label--loading { .tile-ports-label--loading {
color: var(--text-dim); color: var(--text-dim);
} }
/* ── Sidebar: compact feature card overrides ─────────────────────── */
.sidebar .feature-manager-section {
margin-bottom: 0;
}
.sidebar .feature-subcategory {
margin-bottom: 16px;
}
.sidebar .feature-card {
padding: 10px 12px;
}
.sidebar .feature-card-name {
font-size: 0.88rem;
}
.sidebar .feature-card-desc {
font-size: 0.78rem;
}
.sidebar .section-header {
font-size: 0.75rem;
}

View File

@@ -34,6 +34,10 @@
<!-- Service tiles --> <!-- Service tiles -->
<main class="main-content"> <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> <div id="tiles-area"></div>
</main> </main>