feat (ui): Light/Dark Theme added Complete
This commit is contained in:
@@ -56,6 +56,18 @@
|
|||||||
--badge-yellow-text: #111111;
|
--badge-yellow-text: #111111;
|
||||||
--table-zebra: rgba(96,165,250,.03);
|
--table-zebra: rgba(96,165,250,.03);
|
||||||
--sticky-head-bg: rgba(17,26,46,.96);
|
--sticky-head-bg: rgba(17,26,46,.96);
|
||||||
|
--state-info-bg: rgba(96,165,250,.08);
|
||||||
|
--state-info-border: rgba(96,165,250,.35);
|
||||||
|
--state-empty-bg: rgba(251,191,36,.08);
|
||||||
|
--state-empty-border: rgba(251,191,36,.35);
|
||||||
|
--state-error-bg: rgba(251,113,133,.08);
|
||||||
|
--state-error-border: rgba(251,113,133,.35);
|
||||||
|
--map-tooltip-bg: rgba(11,18,32,.95);
|
||||||
|
--map-tooltip-border: rgba(36,52,95,.9);
|
||||||
|
--fs-body: 14px;
|
||||||
|
--fs-small: 12px;
|
||||||
|
--fs-title: 16px;
|
||||||
|
--fs-kpi: 22px;
|
||||||
--radius: 14px;
|
--radius: 14px;
|
||||||
--mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
--mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
--sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji","Segoe UI Emoji";
|
--sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji","Segoe UI Emoji";
|
||||||
@@ -115,11 +127,25 @@
|
|||||||
--badge-yellow-text: #fffbeb;
|
--badge-yellow-text: #fffbeb;
|
||||||
--table-zebra: rgba(15,23,42,.03);
|
--table-zebra: rgba(15,23,42,.03);
|
||||||
--sticky-head-bg: rgba(255,255,255,.97);
|
--sticky-head-bg: rgba(255,255,255,.97);
|
||||||
|
--state-info-bg: rgba(37,99,235,.08);
|
||||||
|
--state-info-border: rgba(37,99,235,.3);
|
||||||
|
--state-empty-bg: rgba(180,83,9,.09);
|
||||||
|
--state-empty-border: rgba(180,83,9,.35);
|
||||||
|
--state-error-bg: rgba(225,29,72,.08);
|
||||||
|
--state-error-border: rgba(225,29,72,.35);
|
||||||
|
--map-tooltip-bg: rgba(255,255,255,.98);
|
||||||
|
--map-tooltip-border: rgba(148,163,184,.45);
|
||||||
|
--fs-body: 14px;
|
||||||
|
--fs-small: 12px;
|
||||||
|
--fs-title: 16px;
|
||||||
|
--fs-kpi: 22px;
|
||||||
}
|
}
|
||||||
*{box-sizing:border-box}
|
*{box-sizing:border-box}
|
||||||
body{
|
body{
|
||||||
margin:0;
|
margin:0;
|
||||||
font-family: var(--sans);
|
font-family: var(--sans);
|
||||||
|
font-size: var(--fs-body);
|
||||||
|
line-height: 1.45;
|
||||||
background: radial-gradient(1200px 600px at 20% 0%, var(--bg-grad-start) 0%, var(--bg) 55%);
|
background: radial-gradient(1200px 600px at 20% 0%, var(--bg-grad-start) 0%, var(--bg) 55%);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
@@ -166,6 +192,10 @@ header{
|
|||||||
cursor:pointer;
|
cursor:pointer;
|
||||||
user-select:none;
|
user-select:none;
|
||||||
font-size:14px;
|
font-size:14px;
|
||||||
|
transition: transform .12s ease, border-color .16s ease, background .16s ease;
|
||||||
|
}
|
||||||
|
.tab:hover{
|
||||||
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
.tab.active{
|
.tab.active{
|
||||||
background: var(--tab-active-bg);
|
background: var(--tab-active-bg);
|
||||||
@@ -200,6 +230,7 @@ header{
|
|||||||
.cardTitle{
|
.cardTitle{
|
||||||
font-weight:700;
|
font-weight:700;
|
||||||
display:flex; gap:10px; align-items:center;
|
display:flex; gap:10px; align-items:center;
|
||||||
|
font-size: var(--fs-title);
|
||||||
}
|
}
|
||||||
.cardBody{padding:14px}
|
.cardBody{padding:14px}
|
||||||
.dashboardKpiGrid{
|
.dashboardKpiGrid{
|
||||||
@@ -225,6 +256,7 @@ header{
|
|||||||
font-size:13px;
|
font-size:13px;
|
||||||
}
|
}
|
||||||
.btn:hover{background: var(--btn2)}
|
.btn:hover{background: var(--btn2)}
|
||||||
|
.btn:active{transform: translateY(1px)}
|
||||||
.btn.small{padding:7px 9px; border-radius: 10px}
|
.btn.small{padding:7px 9px; border-radius: 10px}
|
||||||
.btn.ghost{background: transparent}
|
.btn.ghost{background: transparent}
|
||||||
.btn.ok{border-color: rgba(45,212,191,.6)}
|
.btn.ok{border-color: rgba(45,212,191,.6)}
|
||||||
@@ -280,7 +312,7 @@ tr:hover td{background: var(--hover-bg)}
|
|||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
border: 1px solid var(--card-border);
|
border: 1px solid var(--card-border);
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
font-size: 12px;
|
font-size: var(--fs-small);
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
white-space: nowrap; /* Tip: dit voorkomt dat je badge tekst afbreekt */
|
white-space: nowrap; /* Tip: dit voorkomt dat je badge tekst afbreekt */
|
||||||
}
|
}
|
||||||
@@ -338,18 +370,18 @@ pre.code{
|
|||||||
}
|
}
|
||||||
.statValue{
|
.statValue{
|
||||||
font-weight:800;
|
font-weight:800;
|
||||||
font-size: 20px;
|
font-size: var(--fs-kpi);
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
letter-spacing: .2px;
|
letter-spacing: .2px;
|
||||||
}
|
}
|
||||||
.statLabel{
|
.statLabel{
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-size: 12px;
|
font-size: var(--fs-small);
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
.statHint{
|
.statHint{
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-size: 12px;
|
font-size: var(--fs-small);
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -543,11 +575,25 @@ pre{
|
|||||||
.navLabel {
|
.navLabel {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
.navCount{
|
||||||
|
margin-left: auto;
|
||||||
|
min-width: 26px;
|
||||||
|
text-align: center;
|
||||||
|
border: 1px solid var(--card-border);
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--muted);
|
||||||
|
padding: 2px 7px;
|
||||||
|
background: var(--input-bg);
|
||||||
|
}
|
||||||
|
|
||||||
/* Collapsed: alleen icon zichtbaar */
|
/* Collapsed: alleen icon zichtbaar */
|
||||||
.sidebar.collapsed .navLabel {
|
.sidebar.collapsed .navLabel {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.sidebar.collapsed .navCount {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
.sidebar.collapsed .tab {
|
.sidebar.collapsed .tab {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 10px 10px;
|
padding: 10px 10px;
|
||||||
@@ -666,6 +712,35 @@ pre{
|
|||||||
padding:7px 9px;
|
padding:7px 9px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
.stateBox{
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid var(--state-info-border);
|
||||||
|
background: var(--state-info-bg);
|
||||||
|
}
|
||||||
|
.stateBox.empty{
|
||||||
|
border-color: var(--state-empty-border);
|
||||||
|
background: var(--state-empty-bg);
|
||||||
|
}
|
||||||
|
.stateBox.error{
|
||||||
|
border-color: var(--state-error-border);
|
||||||
|
background: var(--state-error-bg);
|
||||||
|
}
|
||||||
|
.stateTitle{
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.stateText{
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: var(--fs-small);
|
||||||
|
}
|
||||||
|
.viewAnim{
|
||||||
|
animation: viewFade .18s ease;
|
||||||
|
}
|
||||||
|
@keyframes viewFade{
|
||||||
|
from{opacity:.0; transform: translateY(3px)}
|
||||||
|
to{opacity:1; transform: translateY(0)}
|
||||||
|
}
|
||||||
.data-table {
|
.data-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
@@ -734,6 +809,23 @@ pre{
|
|||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
min-height: 420px;
|
min-height: 420px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.mapTooltip{
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
max-width: 260px;
|
||||||
|
background: var(--map-tooltip-bg);
|
||||||
|
color: var(--text);
|
||||||
|
border: 1px solid var(--map-tooltip-border);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 7px 9px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.35;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
z-index: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapLegend{
|
.mapLegend{
|
||||||
|
|||||||
@@ -219,6 +219,9 @@ async function fetchContainers() {
|
|||||||
|
|
||||||
const list = Array.isArray(containers) ? containers : (containers?.containers || []);
|
const list = Array.isArray(containers) ? containers : (containers?.containers || []);
|
||||||
document.getElementById('countContainers').textContent = list.length;
|
document.getElementById('countContainers').textContent = list.length;
|
||||||
|
if (typeof window.updateNavCount === 'function') {
|
||||||
|
window.updateNavCount('countNavContainers', list.length);
|
||||||
|
}
|
||||||
|
|
||||||
const podsList = Array.isArray(pods) ? pods : [];
|
const podsList = Array.isArray(pods) ? pods : [];
|
||||||
const podStatus = {};
|
const podStatus = {};
|
||||||
|
|||||||
@@ -159,7 +159,19 @@ async function filesRefresh() {
|
|||||||
const treeEl = document.getElementById('filesTree');
|
const treeEl = document.getElementById('filesTree');
|
||||||
treeEl.textContent = 'Laden...';
|
treeEl.textContent = 'Laden...';
|
||||||
|
|
||||||
const data = await api('/files/tree', 'GET');
|
let data;
|
||||||
|
try {
|
||||||
|
data = await api('/files/tree', 'GET');
|
||||||
|
} catch (e) {
|
||||||
|
if (typeof window.updateNavCount === 'function') {
|
||||||
|
window.updateNavCount('countNavFiles', 0);
|
||||||
|
}
|
||||||
|
treeEl.innerHTML = (typeof window.renderStateBox === 'function')
|
||||||
|
? window.renderStateBox('error', 'Files laden mislukt', e.message || String(e))
|
||||||
|
: 'Files laden mislukt.';
|
||||||
|
filesUpdateEditorStatus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Filter alleen systemd subtree
|
// Filter alleen systemd subtree
|
||||||
const scoped = (data || []).filter(folder => {
|
const scoped = (data || []).filter(folder => {
|
||||||
@@ -168,10 +180,24 @@ async function filesRefresh() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!scoped.length) {
|
if (!scoped.length) {
|
||||||
treeEl.textContent = 'Geen bestanden gevonden onder systemd.';
|
if (typeof window.updateNavCount === 'function') {
|
||||||
|
window.updateNavCount('countNavFiles', 0);
|
||||||
|
}
|
||||||
|
treeEl.innerHTML = (typeof window.renderStateBox === 'function')
|
||||||
|
? window.renderStateBox('empty', 'Geen bestanden', 'Er zijn geen bestanden gevonden onder systemd.')
|
||||||
|
: 'Geen bestanden gevonden onder systemd.';
|
||||||
|
filesUpdateEditorStatus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let totalFiles = 0;
|
||||||
|
for (const folder of scoped) {
|
||||||
|
totalFiles += Array.isArray(folder?.files) ? folder.files.length : 0;
|
||||||
|
}
|
||||||
|
if (typeof window.updateNavCount === 'function') {
|
||||||
|
window.updateNavCount('countNavFiles', totalFiles);
|
||||||
|
}
|
||||||
|
|
||||||
// Bouw een geneste folder-tree uit de "platte" API response.
|
// Bouw een geneste folder-tree uit de "platte" API response.
|
||||||
const folderByPath = new Map();
|
const folderByPath = new Map();
|
||||||
for (const f of scoped) {
|
for (const f of scoped) {
|
||||||
|
|||||||
@@ -2,18 +2,44 @@ let imagesData = [];
|
|||||||
let imagesSort = { field: null, dir: null };
|
let imagesSort = { field: null, dir: null };
|
||||||
|
|
||||||
async function loadImages() {
|
async function loadImages() {
|
||||||
const res = await fetch("/api/images");
|
const tbody = document.getElementById("images-tbody");
|
||||||
const images = await res.json();
|
try {
|
||||||
|
const res = await fetch("/api/images");
|
||||||
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
|
const images = await res.json();
|
||||||
|
|
||||||
imagesData = images;
|
imagesData = Array.isArray(images) ? images : [];
|
||||||
updateSortIndicators();
|
if (typeof window.updateNavCount === "function") {
|
||||||
applyImageSorting();
|
window.updateNavCount("countNavImages", imagesData.length);
|
||||||
|
}
|
||||||
|
updateSortIndicators();
|
||||||
|
applyImageSorting();
|
||||||
|
} catch (e) {
|
||||||
|
imagesData = [];
|
||||||
|
if (typeof window.updateNavCount === "function") {
|
||||||
|
window.updateNavCount("countNavImages", 0);
|
||||||
|
}
|
||||||
|
if (tbody) {
|
||||||
|
const box = (typeof window.renderStateBox === "function")
|
||||||
|
? window.renderStateBox("error", "Images laden mislukt", e.message || String(e))
|
||||||
|
: "Images laden mislukt.";
|
||||||
|
tbody.innerHTML = `<tr><td colspan="8">${box}</td></tr>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderImages(images) {
|
function renderImages(images) {
|
||||||
const tbody = document.getElementById("images-tbody");
|
const tbody = document.getElementById("images-tbody");
|
||||||
tbody.innerHTML = "";
|
tbody.innerHTML = "";
|
||||||
|
|
||||||
|
if (!images.length) {
|
||||||
|
const box = (typeof window.renderStateBox === "function")
|
||||||
|
? window.renderStateBox("empty", "Geen images", "Er zijn momenteel geen images gevonden.")
|
||||||
|
: "Geen images gevonden.";
|
||||||
|
tbody.innerHTML = `<tr><td colspan="8">${box}</td></tr>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
images.forEach(img => {
|
images.forEach(img => {
|
||||||
const tr = document.createElement("tr");
|
const tr = document.createElement("tr");
|
||||||
|
|
||||||
|
|||||||
@@ -96,11 +96,18 @@
|
|||||||
]);
|
]);
|
||||||
state.usage = usage;
|
state.usage = usage;
|
||||||
state.list = list;
|
state.list = list;
|
||||||
|
if (typeof window.updateNavCount === 'function') {
|
||||||
|
const n = Array.isArray(list?.networks) ? list.networks.length : 0;
|
||||||
|
window.updateNavCount('countNavNetworks', n);
|
||||||
|
}
|
||||||
if (statusEl) statusEl.textContent = `Laatst geladen: ${new Date().toLocaleString()}`;
|
if (statusEl) statusEl.textContent = `Laatst geladen: ${new Date().toLocaleString()}`;
|
||||||
renderNetworksSummary();
|
renderNetworksSummary();
|
||||||
renderNetworks();
|
renderNetworks();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
if (typeof window.updateNavCount === 'function') {
|
||||||
|
window.updateNavCount('countNavNetworks', 0);
|
||||||
|
}
|
||||||
if (statusEl) statusEl.textContent = 'Fout: ' + (e?.message || e);
|
if (statusEl) statusEl.textContent = 'Fout: ' + (e?.message || e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -620,6 +627,10 @@
|
|||||||
|
|
||||||
// leeg host (placeholder weg)
|
// leeg host (placeholder weg)
|
||||||
host.innerHTML = '';
|
host.innerHTML = '';
|
||||||
|
const tooltip = document.createElement('div');
|
||||||
|
tooltip.className = 'mapTooltip';
|
||||||
|
tooltip.style.display = 'none';
|
||||||
|
host.appendChild(tooltip);
|
||||||
|
|
||||||
const w = Math.max(600, host.clientWidth || 600);
|
const w = Math.max(600, host.clientWidth || 600);
|
||||||
const h = Math.max(420, host.clientHeight || 420);
|
const h = Math.max(420, host.clientHeight || 420);
|
||||||
@@ -694,11 +705,27 @@
|
|||||||
d3.select(this).classed('graphActive', false);
|
d3.select(this).classed('graphActive', false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const typeLabel = d.type === 'network' ? 'Netwerk' : 'Container';
|
||||||
|
const extra = d.type === 'network'
|
||||||
|
? `Driver: ${d?.meta?.driver || 'onbekend'}`
|
||||||
|
: (d?.pod ? `Pod: ${d.pod}` : 'Pod: -');
|
||||||
|
tooltip.innerHTML = `<strong>${typeLabel}</strong><br>${d.label || d.key}<br>${extra}`;
|
||||||
|
tooltip.style.display = 'block';
|
||||||
|
});
|
||||||
|
|
||||||
|
node.on('mousemove', (ev) => {
|
||||||
|
const rect = host.getBoundingClientRect();
|
||||||
|
const x = (ev.clientX - rect.left) + 14;
|
||||||
|
const y = (ev.clientY - rect.top) + 14;
|
||||||
|
tooltip.style.left = `${x}px`;
|
||||||
|
tooltip.style.top = `${y}px`;
|
||||||
});
|
});
|
||||||
|
|
||||||
node.on('mouseleave', () => {
|
node.on('mouseleave', () => {
|
||||||
node.classed('graphDim', false);
|
node.classed('graphDim', false);
|
||||||
link.classed('graphDim', false).classed('graphActive', false);
|
link.classed('graphDim', false).classed('graphActive', false);
|
||||||
|
tooltip.style.display = 'none';
|
||||||
});
|
});
|
||||||
|
|
||||||
node.on('click', (ev, d) => {
|
node.on('click', (ev, d) => {
|
||||||
@@ -822,13 +849,24 @@
|
|||||||
|
|
||||||
const usage = state.usage;
|
const usage = state.usage;
|
||||||
if (!usage || !usage.byNetwork) {
|
if (!usage || !usage.byNetwork) {
|
||||||
tbody.innerHTML = `<tr><td colspan="6" class="muted">Geen data. Klik op Vernieuwen.</td></tr>`;
|
const box = (typeof window.renderStateBox === 'function')
|
||||||
rel.innerHTML = `<div class="muted">Geen data.</div>`;
|
? window.renderStateBox('empty', 'Geen netwerkdata', 'Klik op Vernieuwen om netwerkdata op te halen.')
|
||||||
|
: 'Geen data.';
|
||||||
|
tbody.innerHTML = `<tr><td colspan="6">${box}</td></tr>`;
|
||||||
|
rel.innerHTML = box;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const vmAll = buildNetworksViewModel();
|
const vmAll = buildNetworksViewModel();
|
||||||
const vm = applyFiltersAndSort(vmAll);
|
const vm = applyFiltersAndSort(vmAll);
|
||||||
|
if (!vm.length) {
|
||||||
|
const box = (typeof window.renderStateBox === 'function')
|
||||||
|
? window.renderStateBox('empty', 'Geen resultaten', 'Pas filters aan of schakel opties uit om netwerken te tonen.')
|
||||||
|
: 'Geen resultaten.';
|
||||||
|
tbody.innerHTML = `<tr><td colspan="6">${box}</td></tr>`;
|
||||||
|
rel.innerHTML = box;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (const row of vm) {
|
for (const row of vm) {
|
||||||
const netName = row.name;
|
const netName = row.name;
|
||||||
|
|||||||
+25
-5
@@ -48,16 +48,16 @@
|
|||||||
<span class="navIcon">🏠</span><span class="navLabel">Dashboard</span>
|
<span class="navIcon">🏠</span><span class="navLabel">Dashboard</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab" id="tab-containers" onclick="setTab('containers')" title="Containers">
|
<div class="tab" id="tab-containers" onclick="setTab('containers')" title="Containers">
|
||||||
<span class="navIcon">📦</span><span class="navLabel">Containers</span>
|
<span class="navIcon">📦</span><span class="navLabel">Containers</span><span class="navCount" id="countNavContainers">-</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab" id="tab-networks" onclick="setTab('networks')" title="Netwerk">
|
<div class="tab" id="tab-networks" onclick="setTab('networks')" title="Netwerk">
|
||||||
<span class="navIcon">🌐</span><span class="navLabel">Netwerk</span>
|
<span class="navIcon">🌐</span><span class="navLabel">Netwerk</span><span class="navCount" id="countNavNetworks">-</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab" id="tab-images" onclick="setTab('images')" title="Images">
|
<div class="tab" id="tab-images" onclick="setTab('images')" title="Images">
|
||||||
<span class="navIcon">📦</span><span class="navLabel">Images</span>
|
<span class="navIcon">📦</span><span class="navLabel">Images</span><span class="navCount" id="countNavImages">-</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab" id="tab-files" onclick="setTab('files')" title="Files">
|
<div class="tab" id="tab-files" onclick="setTab('files')" title="Files">
|
||||||
<span class="navIcon">📁</span><span class="navLabel">Files</span>
|
<span class="navIcon">📁</span><span class="navLabel">Files</span><span class="navCount" id="countNavFiles">-</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
@@ -486,6 +486,22 @@
|
|||||||
return `<span class="badge">${esc(s || 'unknown')}</span>`;
|
return `<span class="badge">${esc(s || 'unknown')}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateNavCount(id, value) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (!el) return;
|
||||||
|
el.textContent = Number.isFinite(Number(value)) ? String(value) : '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderStateBox(type, title, message) {
|
||||||
|
const t = type === 'error' ? 'error' : (type === 'empty' ? 'empty' : 'info');
|
||||||
|
return `
|
||||||
|
<div class="stateBox ${t}">
|
||||||
|
<div class="stateTitle">${esc(title || 'Status')}</div>
|
||||||
|
<div class="stateText">${esc(message || '')}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Modal ----
|
// ---- Modal ----
|
||||||
function showModal(title, content) {
|
function showModal(title, content) {
|
||||||
document.getElementById('modalTitle').textContent = title;
|
document.getElementById('modalTitle').textContent = title;
|
||||||
@@ -522,7 +538,10 @@
|
|||||||
document.getElementById('tab-' + tab).classList.add('active');
|
document.getElementById('tab-' + tab).classList.add('active');
|
||||||
|
|
||||||
document.querySelectorAll('[id^="view-"]').forEach(v => v.style.display='none');
|
document.querySelectorAll('[id^="view-"]').forEach(v => v.style.display='none');
|
||||||
document.getElementById('view-' + tab).style.display = '';
|
const view = document.getElementById('view-' + tab);
|
||||||
|
view.style.display = '';
|
||||||
|
view.classList.remove('viewAnim');
|
||||||
|
requestAnimationFrame(() => view.classList.add('viewAnim'));
|
||||||
if (tab === 'files') {
|
if (tab === 'files') {
|
||||||
filesRefresh();
|
filesRefresh();
|
||||||
}
|
}
|
||||||
@@ -610,6 +629,7 @@
|
|||||||
const list = Array.isArray(containers) ? containers : (containers?.containers || []);
|
const list = Array.isArray(containers) ? containers : (containers?.containers || []);
|
||||||
const cCount = list.length;
|
const cCount = list.length;
|
||||||
document.getElementById('countContainers').textContent = cCount;
|
document.getElementById('countContainers').textContent = cCount;
|
||||||
|
updateNavCount('countNavContainers', cCount);
|
||||||
}
|
}
|
||||||
setApiState(true, 'API: OK');
|
setApiState(true, 'API: OK');
|
||||||
setLastRefreshNow();
|
setLastRefreshNow();
|
||||||
|
|||||||
Reference in New Issue
Block a user