feat (ui): Light/Dark Theme added Complete
This commit is contained in:
@@ -56,6 +56,18 @@
|
||||
--badge-yellow-text: #111111;
|
||||
--table-zebra: rgba(96,165,250,.03);
|
||||
--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;
|
||||
--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";
|
||||
@@ -115,11 +127,25 @@
|
||||
--badge-yellow-text: #fffbeb;
|
||||
--table-zebra: rgba(15,23,42,.03);
|
||||
--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}
|
||||
body{
|
||||
margin:0;
|
||||
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%);
|
||||
color: var(--text);
|
||||
}
|
||||
@@ -166,6 +192,10 @@ header{
|
||||
cursor:pointer;
|
||||
user-select:none;
|
||||
font-size:14px;
|
||||
transition: transform .12s ease, border-color .16s ease, background .16s ease;
|
||||
}
|
||||
.tab:hover{
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.tab.active{
|
||||
background: var(--tab-active-bg);
|
||||
@@ -200,6 +230,7 @@ header{
|
||||
.cardTitle{
|
||||
font-weight:700;
|
||||
display:flex; gap:10px; align-items:center;
|
||||
font-size: var(--fs-title);
|
||||
}
|
||||
.cardBody{padding:14px}
|
||||
.dashboardKpiGrid{
|
||||
@@ -225,6 +256,7 @@ header{
|
||||
font-size:13px;
|
||||
}
|
||||
.btn:hover{background: var(--btn2)}
|
||||
.btn:active{transform: translateY(1px)}
|
||||
.btn.small{padding:7px 9px; border-radius: 10px}
|
||||
.btn.ghost{background: transparent}
|
||||
.btn.ok{border-color: rgba(45,212,191,.6)}
|
||||
@@ -280,7 +312,7 @@ tr:hover td{background: var(--hover-bg)}
|
||||
padding: 4px 8px;
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
font-size: var(--fs-small);
|
||||
color: var(--muted);
|
||||
white-space: nowrap; /* Tip: dit voorkomt dat je badge tekst afbreekt */
|
||||
}
|
||||
@@ -338,18 +370,18 @@ pre.code{
|
||||
}
|
||||
.statValue{
|
||||
font-weight:800;
|
||||
font-size: 20px;
|
||||
font-size: var(--fs-kpi);
|
||||
line-height: 1.1;
|
||||
letter-spacing: .2px;
|
||||
}
|
||||
.statLabel{
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-size: var(--fs-small);
|
||||
margin-top: 4px;
|
||||
}
|
||||
.statHint{
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-size: var(--fs-small);
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
@@ -543,11 +575,25 @@ pre{
|
||||
.navLabel {
|
||||
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 */
|
||||
.sidebar.collapsed .navLabel {
|
||||
display: none;
|
||||
}
|
||||
.sidebar.collapsed .navCount {
|
||||
display: none;
|
||||
}
|
||||
.sidebar.collapsed .tab {
|
||||
justify-content: center;
|
||||
padding: 10px 10px;
|
||||
@@ -666,6 +712,35 @@ pre{
|
||||
padding:7px 9px;
|
||||
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 {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
@@ -734,6 +809,23 @@ pre{
|
||||
border-radius: 14px;
|
||||
min-height: 420px;
|
||||
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{
|
||||
|
||||
@@ -219,6 +219,9 @@ async function fetchContainers() {
|
||||
|
||||
const list = Array.isArray(containers) ? containers : (containers?.containers || []);
|
||||
document.getElementById('countContainers').textContent = list.length;
|
||||
if (typeof window.updateNavCount === 'function') {
|
||||
window.updateNavCount('countNavContainers', list.length);
|
||||
}
|
||||
|
||||
const podsList = Array.isArray(pods) ? pods : [];
|
||||
const podStatus = {};
|
||||
|
||||
@@ -159,7 +159,19 @@ async function filesRefresh() {
|
||||
const treeEl = document.getElementById('filesTree');
|
||||
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
|
||||
const scoped = (data || []).filter(folder => {
|
||||
@@ -168,10 +180,24 @@ async function filesRefresh() {
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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.
|
||||
const folderByPath = new Map();
|
||||
for (const f of scoped) {
|
||||
|
||||
@@ -2,18 +2,44 @@ let imagesData = [];
|
||||
let imagesSort = { field: null, dir: null };
|
||||
|
||||
async function loadImages() {
|
||||
const res = await fetch("/api/images");
|
||||
const images = await res.json();
|
||||
const tbody = document.getElementById("images-tbody");
|
||||
try {
|
||||
const res = await fetch("/api/images");
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const images = await res.json();
|
||||
|
||||
imagesData = images;
|
||||
updateSortIndicators();
|
||||
applyImageSorting();
|
||||
imagesData = Array.isArray(images) ? images : [];
|
||||
if (typeof window.updateNavCount === "function") {
|
||||
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) {
|
||||
const tbody = document.getElementById("images-tbody");
|
||||
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 => {
|
||||
const tr = document.createElement("tr");
|
||||
|
||||
|
||||
@@ -96,11 +96,18 @@
|
||||
]);
|
||||
state.usage = usage;
|
||||
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()}`;
|
||||
renderNetworksSummary();
|
||||
renderNetworks();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (typeof window.updateNavCount === 'function') {
|
||||
window.updateNavCount('countNavNetworks', 0);
|
||||
}
|
||||
if (statusEl) statusEl.textContent = 'Fout: ' + (e?.message || e);
|
||||
}
|
||||
}
|
||||
@@ -620,6 +627,10 @@
|
||||
|
||||
// leeg host (placeholder weg)
|
||||
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 h = Math.max(420, host.clientHeight || 420);
|
||||
@@ -694,11 +705,27 @@
|
||||
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.classed('graphDim', false);
|
||||
link.classed('graphDim', false).classed('graphActive', false);
|
||||
tooltip.style.display = 'none';
|
||||
});
|
||||
|
||||
node.on('click', (ev, d) => {
|
||||
@@ -822,13 +849,24 @@
|
||||
|
||||
const usage = state.usage;
|
||||
if (!usage || !usage.byNetwork) {
|
||||
tbody.innerHTML = `<tr><td colspan="6" class="muted">Geen data. Klik op Vernieuwen.</td></tr>`;
|
||||
rel.innerHTML = `<div class="muted">Geen data.</div>`;
|
||||
const box = (typeof window.renderStateBox === 'function')
|
||||
? 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;
|
||||
}
|
||||
|
||||
const vmAll = buildNetworksViewModel();
|
||||
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) {
|
||||
const netName = row.name;
|
||||
@@ -1137,4 +1175,4 @@
|
||||
|
||||
// Bind when script loads (DOM is already mostly there because script is at end of body)
|
||||
bindUiOnce();
|
||||
})();
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user