diff --git a/control/Dockerfile b/control/Containerfile similarity index 100% rename from control/Dockerfile rename to control/Containerfile diff --git a/webui/html/assets/css/app.css b/webui/html/assets/css/app.css index 6df1bbf..3638306 100644 --- a/webui/html/assets/css/app.css +++ b/webui/html/assets/css/app.css @@ -3,6 +3,7 @@ --panel: #111a2e; --panel2: #0e1730; --text: #e8eefc; + --fg: #e8eefc; --muted:#9bb0da; --border:#24345f; --ok:#2dd4bf; @@ -12,22 +13,117 @@ --btn2:#223564; --accent:#60a5fa; --shadow: 0 10px 30px rgba(0,0,0,.35); + --bg-grad-start: #18244a; + --header-bg: rgba(11,18,32,.7); + --header-border: rgba(36,52,95,.7); + --tab-bg: rgba(17,26,46,.6); + --tab-active-bg: linear-gradient(135deg, rgba(96,165,250,.25), rgba(17,26,46,.6)); + --tab-active-border: rgba(96,165,250,.5); + --card-bg: linear-gradient(180deg, rgba(17,26,46,.85), rgba(14,23,48,.85)); + --card-border: rgba(36,52,95,.9); + --soft-line: rgba(36,52,95,.6); + --hover-bg: rgba(96,165,250,.06); + --input-bg: rgba(8,12,25,.6); + --input-placeholder: rgba(200,210,255,.45); + --overlay-bg: rgba(0,0,0,.55); + --pre-bg: rgba(0,0,0,.35); + --pre-text: #d9e6ff; + --pod-row-bg: rgba(255,255,255,0.04); + --pod-row-top: rgba(255,255,255,0.08); + --pod-row-hover: rgba(255,255,255,0.07); + --menu-hover-bg: rgba(96,165,250,.10); + --menu-hover-border: rgba(96,165,250,.25); + --sidebar-bg: rgba(11,18,32,.55); + --folder-bg: rgba(17,26,46,.35); + --folder-hover: rgba(96,165,250,.08); + --folder-toggle-bg: rgba(8,12,25,.35); + --map-border: rgba(255,255,255,0.10); + --map-bg: rgba(0,0,0,0.18); + --map-line: rgba(255,255,255,0.22); + --map-line-strong: rgba(255,255,255,0.28); + --map-node-stroke: rgba(255,255,255,0.18); + --map-node-net: rgba(80,160,255,0.35); + --map-node-ctr: rgba(150,230,150,0.30); + --map-label: rgba(255,255,255,0.82); + --map-active: rgba(255,255,255,0.55); + --legend-row-border: rgba(255,255,255,0.12); + --legend-row-bg: rgba(255,255,255,0.03); + --legend-row-border-hover: rgba(255,255,255,0.25); + --legend-row-bg-hover: rgba(255,255,255,0.06); + --badge-green-bg: #2ecc71; + --badge-green-text: #ffffff; + --badge-yellow-bg: #f1c40f; + --badge-yellow-text: #111111; --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"; } +[data-theme="light"]{ + --bg: #f3f6fb; + --panel: #ffffff; + --panel2: #eef3fb; + --text: #111827; + --fg: #111827; + --muted:#51617d; + --border:#c7d4ea; + --btn:#e8eef9; + --btn2:#dce7f8; + --accent:#2563eb; + --shadow: 0 10px 28px rgba(13, 27, 54, .12); + --bg-grad-start: #dfeafe; + --header-bg: rgba(243,246,251,.88); + --header-border: rgba(146,169,205,.55); + --tab-bg: rgba(255,255,255,.92); + --tab-active-bg: linear-gradient(135deg, rgba(37,99,235,.16), rgba(255,255,255,.95)); + --tab-active-border: rgba(37,99,235,.42); + --card-bg: linear-gradient(180deg, rgba(255,255,255,.96), rgba(238,243,251,.96)); + --card-border: rgba(176,196,226,.9); + --soft-line: rgba(176,196,226,.8); + --hover-bg: rgba(37,99,235,.08); + --input-bg: rgba(255,255,255,.94); + --input-placeholder: rgba(81,97,125,.72); + --overlay-bg: rgba(17,24,39,.36); + --pre-bg: rgba(223,234,254,.72); + --pre-text: #0f172a; + --pod-row-bg: rgba(148,163,184,0.12); + --pod-row-top: rgba(148,163,184,0.25); + --pod-row-hover: rgba(148,163,184,0.18); + --menu-hover-bg: rgba(37,99,235,.12); + --menu-hover-border: rgba(37,99,235,.28); + --sidebar-bg: rgba(236,242,252,.82); + --folder-bg: rgba(236,242,252,.9); + --folder-hover: rgba(37,99,235,.08); + --folder-toggle-bg: rgba(232,238,249,.92); + --map-border: rgba(148,163,184,.34); + --map-bg: rgba(255,255,255,.86); + --map-line: rgba(71,85,105,.33); + --map-line-strong: rgba(71,85,105,.42); + --map-node-stroke: rgba(71,85,105,.34); + --map-node-net: rgba(37,99,235,.28); + --map-node-ctr: rgba(34,197,94,.25); + --map-label: rgba(17,24,39,.9); + --map-active: rgba(37,99,235,.58); + --legend-row-border: rgba(148,163,184,.4); + --legend-row-bg: rgba(226,236,252,.9); + --legend-row-border-hover: rgba(100,116,139,.46); + --legend-row-bg-hover: rgba(210,224,248,.9); + --badge-green-bg: #166534; + --badge-green-text: #f8fafc; + --badge-yellow-bg: #b45309; + --badge-yellow-text: #fffbeb; +} *{box-sizing:border-box} body{ margin:0; font-family: var(--sans); - background: radial-gradient(1200px 600px at 20% 0%, #18244a 0%, var(--bg) 55%); + background: radial-gradient(1200px 600px at 20% 0%, var(--bg-grad-start) 0%, var(--bg) 55%); color: var(--text); } header{ position: sticky; top:0; z-index:10; - background: rgba(11,18,32,.7); + background: var(--header-bg); backdrop-filter: blur(10px); - border-bottom: 1px solid rgba(36,52,95,.7); + border-bottom: 1px solid var(--header-border); } .wrap{max-width:1700px;margin:0 auto;padding:16px} .topbar{ @@ -55,7 +151,7 @@ header{ } .tab{ border:1px solid var(--border); - background: rgba(17,26,46,.6); + background: var(--tab-bg); color: var(--text); padding:10px 12px; border-radius: 999px; @@ -64,8 +160,8 @@ header{ font-size:14px; } .tab.active{ - background: linear-gradient(135deg, rgba(96,165,250,.25), rgba(17,26,46,.6)); - border-color: rgba(96,165,250,.5); + background: var(--tab-active-bg); + border-color: var(--tab-active-border); } .grid{ display:grid; @@ -77,8 +173,8 @@ header{ .grid{grid-template-columns: 1fr 1fr} } .card{ - background: linear-gradient(180deg, rgba(17,26,46,.85), rgba(14,23,48,.85)); - border: 1px solid rgba(36,52,95,.9); + background: var(--card-bg); + border: 1px solid var(--card-border); border-radius: var(--radius); box-shadow: var(--shadow); overflow:hidden; @@ -91,7 +187,7 @@ header{ .cardHeader{ display:flex; align-items:center; justify-content:space-between; padding:14px 14px; - border-bottom:1px solid rgba(36,52,95,.7); + border-bottom:1px solid var(--soft-line); } .cardTitle{ font-weight:700; @@ -99,7 +195,7 @@ header{ } .cardBody{padding:14px} .btn{ - border:1px solid rgba(36,52,95,.9); + border:1px solid var(--card-border); background: var(--btn); color: var(--text); padding:9px 10px; @@ -130,7 +226,7 @@ header{ display:inline-flex; align-items:center; gap:8px; padding:6px 10px; border-radius:999px; - border:1px solid rgba(36,52,95,.9); + border:1px solid var(--card-border); color: var(--muted); font-size:12px; } @@ -142,19 +238,19 @@ table{ } th,td{ padding:10px 8px; - border-bottom:1px solid rgba(36,52,95,.6); + border-bottom:1px solid var(--soft-line); text-align:left; vertical-align: top; } th{color: var(--muted); font-weight:600} -tr:hover td{background: rgba(96,165,250,.06)} +tr:hover td{background: var(--hover-bg)} .badge { display: inline-flex; align-items: center; vertical-align: middle; line-height: 1; padding: 4px 8px; - border: 1px solid rgba(36, 52, 95, .9); + border: 1px solid var(--card-border); border-radius: 999px; font-size: 12px; color: var(--muted); @@ -163,7 +259,7 @@ tr:hover td{background: rgba(96,165,250,.06)} pre.code{ padding:10px; border-radius:10px; - border:1px solid rgba(255,255,255,.15); + border:1px solid var(--soft-line); } .badge.ok{border-color: rgba(45,212,191,.6); color: var(--ok)} .badge.bad{border-color: rgba(251,113,133,.6); color: var(--bad)} @@ -178,8 +274,8 @@ pre.code{ .flex{display:flex; gap:8px; flex-wrap:wrap; align-items:center} .input, .textarea{ width: 100%; - background: rgba(8,12,25,.6); - border:1px solid rgba(36,52,95,.9); + background: var(--input-bg); + border:1px solid var(--card-border); color: var(--text); border-radius: 12px; padding:10px 12px; @@ -206,8 +302,8 @@ pre.code{ .statGrid{grid-template-columns: repeat(4, 1fr)} } .statCard{ - background: rgba(8,12,25,.45); - border:1px solid rgba(36,52,95,.9); + background: var(--input-bg); + border:1px solid var(--card-border); border-radius: 14px; padding:10px 12px; } @@ -231,14 +327,14 @@ pre.code{ /* Modal */ .modalBack{ position: fixed; inset:0; - background: rgba(0,0,0,.55); + background: var(--overlay-bg); display:none; align-items:center; justify-content:center; padding:18px; z-index: 99; } .modal{ width:min(980px, 100%); - background: linear-gradient(180deg, rgba(17,26,46,.95), rgba(14,23,48,.95)); - border:1px solid rgba(36,52,95,.9); + background: var(--card-bg); + border:1px solid var(--card-border); border-radius: 18px; box-shadow: var(--shadow); overflow:hidden; @@ -246,7 +342,7 @@ pre.code{ .modalHeader{ padding:12px 14px; display:flex; align-items:center; justify-content:space-between; - border-bottom:1px solid rgba(36,52,95,.7); + border-bottom:1px solid var(--soft-line); } .modalTitle{font-weight:700} .modalBody{padding:14px} @@ -256,9 +352,9 @@ pre{ word-break: break-word; font-family: var(--mono); font-size: 12.5px; - color: #d9e6ff; - background: rgba(0,0,0,.35); - border:1px solid rgba(36,52,95,.7); + color: var(--pre-text); + background: var(--pre-bg); + border:1px solid var(--soft-line); border-radius: 14px; padding: 12px; max-height: 60vh; @@ -266,12 +362,12 @@ pre{ } .hint{font-size:12px;color:var(--muted);margin-top:8px;line-height:1.35} .pod-group-row td { - background: rgba(255,255,255,0.04); - border-top: 1px solid rgba(255,255,255,0.08); + background: var(--pod-row-bg); + border-top: 1px solid var(--pod-row-top); font-weight: 600; } .pod-group-row:hover td { - background: rgba(255,255,255,0.07); + background: var(--pod-row-hover); } .stale { opacity: 0.65; @@ -285,8 +381,8 @@ pre{ right: 0; top: 34px; min-width: 140px; - background: linear-gradient(180deg, rgba(17,26,46,.98), rgba(14,23,48,.98)); - border: 1px solid rgba(36,52,95,.9); + background: var(--card-bg); + border: 1px solid var(--card-border); border-radius: 12px; box-shadow: var(--shadow); padding: 6px; @@ -309,8 +405,8 @@ pre{ } .menuItem:hover{ - background: rgba(96,165,250,.10); - border-color: rgba(96,165,250,.25); + background: var(--menu-hover-bg); + border-color: var(--menu-hover-border); } .menuItem.ok{ border-color: rgba(45,212,191,.35); } @@ -321,14 +417,14 @@ pre{ /* Toolbar controls */ .toolbar .input, .toolbar .select{ - background: rgba(8,12,25,.45); - border:1px solid rgba(36,52,95,.9); + background: var(--input-bg); + border:1px solid var(--card-border); color: var(--fg); border-radius: 12px; padding:8px 10px; outline: none; } -.toolbar .input::placeholder{ color: rgba(200,210,255,.45); } +.toolbar .input::placeholder{ color: var(--input-placeholder); } .chip{ display:inline-flex; @@ -336,8 +432,8 @@ pre{ gap:8px; padding:7px 10px; border-radius: 999px; - border:1px solid rgba(36,52,95,.9); - background: rgba(8,12,25,.35); + border:1px solid var(--card-border); + background: var(--input-bg); color: var(--fg); cursor:pointer; user-select:none; @@ -358,8 +454,8 @@ pre{ .sidebar { width: 220px; flex: 0 0 220px; - border-right: 1px solid rgba(36,52,95,.7); - background: rgba(11,18,32,.55); + border-right: 1px solid var(--soft-line); + background: var(--sidebar-bg); backdrop-filter: blur(10px); min-height: calc(100vh - 72px); /* header hoogte approx; mag iets afwijken */ position: sticky; @@ -450,14 +546,14 @@ pre{ user-select:none; padding: 10px 12px; - border: 1px solid rgba(36,52,95,.75); + border: 1px solid var(--card-border); border-radius: 12px; - background: rgba(17,26,46,.35); + background: var(--folder-bg); transition: background .12s ease, border-color .12s ease, transform .06s ease; } .file-folder-row:hover{ - background: rgba(96,165,250,.08); + background: var(--folder-hover); border-color: rgba(96,165,250,.35); } @@ -479,8 +575,8 @@ pre{ width: 18px; height: 18px; border-radius: 6px; - border: 1px solid rgba(36,52,95,.8); - background: rgba(8,12,25,.35); + border: 1px solid var(--card-border); + background: var(--folder-toggle-bg); font-size: 12px; opacity: .9; flex: 0 0 auto; @@ -496,7 +592,7 @@ pre{ margin-left: 0; margin-top: 6px; padding-left: 12px; - border-left: 1px dashed rgba(36,52,95,.55); + border-left: 1px dashed var(--soft-line); } /* File rows inside folders (werkt ook met inline styles die je nu al hebt) */ @@ -505,7 +601,7 @@ pre{ } .file-folder-files > div:hover{ - background: rgba(96,165,250,.06); + background: var(--hover-bg); } .data-table { width: 100%; @@ -515,18 +611,18 @@ pre{ .data-table th, .data-table td { padding: 8px; - border-bottom: 1px solid #ddd; + border-bottom: 1px solid var(--soft-line); text-align: left; } .badge-green { - background: #2ecc71; - color: white; + background: var(--badge-green-bg); + color: var(--badge-green-text); } .badge-yellow { - background: #f1c40f; - color: black; + background: var(--badge-yellow-bg); + color: var(--badge-yellow-text); } .sortable { cursor: pointer; @@ -544,8 +640,8 @@ pre{ /* ===== Netwerken: Tabel/Kaart toggle + kaart placeholders (STAP 3A-1) ===== */ .segToggle{ display:inline-flex; - border:1px solid rgba(255,255,255,0.12); - background: rgba(255,255,255,0.04); + border:1px solid var(--legend-row-border); + background: var(--legend-row-bg); border-radius: 12px; overflow:hidden; } @@ -565,13 +661,13 @@ pre{ .segToggle .seg:hover{ opacity: 1; } .segToggle .seg.active{ - background: rgba(255,255,255,0.08); + background: var(--menu-hover-bg); opacity: 1; } .mapHost{ - border: 1px solid rgba(255,255,255,0.10); - background: rgba(0,0,0,0.18); + border: 1px solid var(--map-border); + background: var(--map-bg); border-radius: 14px; min-height: 420px; overflow: hidden; @@ -580,8 +676,8 @@ pre{ .mapLegend{ margin-top: 10px; padding: 10px 12px; - border: 1px solid rgba(255,255,255,0.10); - background: rgba(0,0,0,0.18); + border: 1px solid var(--map-border); + background: var(--map-bg); border-radius: 14px; max-width: 360px; } @@ -603,7 +699,7 @@ pre{ height: 14px; border-radius: 6px; display:inline-block; - border: 1px solid rgba(255,255,255,0.14); + border: 1px solid var(--map-border); } .mapLegend .legendSwatch.link{ @@ -611,7 +707,7 @@ pre{ height: 2px; border-radius: 2px; border: 0; - background: rgba(255,255,255,0.22); + background: var(--map-line); } .mapLegend .legendSwatch.shared{ @@ -619,47 +715,47 @@ pre{ height: 2px; border-radius: 0; border: 0; - background: rgba(255,255,255,0.22); - background-image: repeating-linear-gradient(90deg, rgba(255,255,255,0.22), rgba(255,255,255,0.22) 6px, transparent 6px, transparent 10px); + background: var(--map-line); + background-image: repeating-linear-gradient(90deg, var(--map-line), var(--map-line) 6px, transparent 6px, transparent 10px); } -.mapLegend .legendSwatch.net{ background: rgba(80,160,255,0.35); } -.mapLegend .legendSwatch.ctr{ background: rgba(150,230,150,0.30); } +.mapLegend .legendSwatch.net{ background: var(--map-node-net); } +.mapLegend .legendSwatch.ctr{ background: var(--map-node-ctr); } /* ===== Netwerken kaart (D3) ===== */ .mapHost svg { width: 100%; height: 100%; display:block; } .graphLink{ - stroke: rgba(255,255,255,0.22); + stroke: var(--map-line); stroke-width: 1.2; } .graphLink.shared{ stroke-dasharray: 6 4; - stroke: rgba(255,255,255,0.28); + stroke: var(--map-line-strong); } .graphNode circle{ - stroke: rgba(255,255,255,0.18); + stroke: var(--map-node-stroke); stroke-width: 1; } -.graphNode.network circle{ fill: rgba(80,160,255,0.35); } -.graphNode.container circle{ fill: rgba(150,230,150,0.30); } +.graphNode.network circle{ fill: var(--map-node-net); } +.graphNode.container circle{ fill: var(--map-node-ctr); } .graphLabel{ - fill: rgba(255,255,255,0.82); + fill: var(--map-label); font-size: 12px; pointer-events: none; } .graphDim{ opacity: 0.18; } -.graphActive{ opacity: 1; stroke: rgba(255,255,255,0.55); stroke-width: 2; } +.graphActive{ opacity: 1; stroke: var(--map-active); stroke-width: 2; } /* ===== Netwerken detailpaneel ===== */ .mapDetail{ margin-top: 10px; padding: 10px 12px; - border: 1px solid rgba(255,255,255,0.10); - background: rgba(0,0,0,0.18); + border: 1px solid var(--map-border); + background: var(--map-bg); border-radius: 14px; } @@ -768,17 +864,17 @@ pre{ align-items:center; gap:8px; padding:6px 10px; - border:1px solid rgba(255,255,255,0.12); + border:1px solid var(--legend-row-border); border-radius:20px; - background:rgba(255,255,255,0.03); + background:var(--legend-row-bg); font-size:13px; white-space:nowrap; transition:all .15s ease; } .mapLegend .legendRow:hover{ - border-color:rgba(255,255,255,0.25); - background:rgba(255,255,255,0.06); + border-color:var(--legend-row-border-hover); + background:var(--legend-row-bg-hover); } @@ -807,8 +903,8 @@ pre{ gap:6px; padding: 3px 8px; border-radius: 999px; - border: 1px solid rgba(36,52,95,.8); - background: rgba(8,12,25,.35); + border: 1px solid var(--card-border); + background: var(--folder-toggle-bg); font-size: 12px; opacity: .92; } diff --git a/webui/html/assets/js/tabs/files.js b/webui/html/assets/js/tabs/files.js index 73f06e5..55dc36f 100644 --- a/webui/html/assets/js/tabs/files.js +++ b/webui/html/assets/js/tabs/files.js @@ -1,5 +1,23 @@ let cmEditor = null; +function filesCurrentTheme() { + const t = document.documentElement.getAttribute('data-theme'); + return (t === 'light') ? 'light' : 'dark'; +} + +function filesCodeMirrorTheme() { + return filesCurrentTheme() === 'light' ? 'default' : 'material-darker'; +} + +function filesSetEditorTheme(themeName) { + if (!cmEditor) return; + const cmTheme = (themeName === 'light') ? 'default' : 'material-darker'; + cmEditor.setOption('theme', cmTheme); + cmEditor.refresh(); +} + +window.filesSetEditorTheme = filesSetEditorTheme; + function _isFolderCollapsed(folderKey) { return localStorage.getItem('files_folder_collapsed:' + folderKey) !== '0'; // default collapsed = true @@ -51,7 +69,7 @@ async function filesRefresh() { lineNumbers: true, lineWrapping: true, mode: 'text/plain', - theme: 'material-darker' + theme: filesCodeMirrorTheme() }); cmEditor.setSize('100%', 360); } diff --git a/webui/html/index.html b/webui/html/index.html index c26cc43..8f4c817 100644 --- a/webui/html/index.html +++ b/webui/html/index.html @@ -30,6 +30,7 @@
+
@@ -581,8 +582,67 @@ } } + // ---- Theme (light/dark) ---- + const THEME_KEY = 'mvp_theme_v1'; + + function getSystemTheme() { + return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches + ? 'dark' + : 'light'; + } + + function getStoredTheme() { + const t = localStorage.getItem(THEME_KEY); + return (t === 'dark' || t === 'light') ? t : ''; + } + + function getInitialTheme() { + return getStoredTheme() || getSystemTheme(); + } + + function updateThemeToggleUi(theme) { + const btn = document.getElementById('themeToggleBtn'); + if (!btn) return; + const next = theme === 'dark' ? 'light' : 'dark'; + btn.textContent = `Theme: ${theme === 'dark' ? 'Dark' : 'Light'}`; + btn.title = `Schakel naar ${next === 'dark' ? 'dark' : 'light'} mode`; + } + + function applyTheme(theme, persist = false) { + const t = (theme === 'light') ? 'light' : 'dark'; + document.documentElement.setAttribute('data-theme', t); + updateThemeToggleUi(t); + + if (persist) localStorage.setItem(THEME_KEY, t); + + if (typeof window.filesSetEditorTheme === 'function') { + window.filesSetEditorTheme(t); + } + } + + function toggleTheme() { + const current = document.documentElement.getAttribute('data-theme') || getInitialTheme(); + const next = current === 'dark' ? 'light' : 'dark'; + applyTheme(next, true); + } + // ---- Init ---- (async function init(){ + applyTheme(getInitialTheme(), false); + + const themeBtn = document.getElementById('themeToggleBtn'); + if (themeBtn) themeBtn.onclick = toggleTheme; + + if (window.matchMedia) { + const mq = window.matchMedia('(prefers-color-scheme: dark)'); + const onSystemThemeChange = (ev) => { + if (getStoredTheme()) return; + applyTheme(ev.matches ? 'dark' : 'light', false); + }; + if (mq.addEventListener) mq.addEventListener('change', onSystemThemeChange); + else if (mq.addListener) mq.addListener(onSystemThemeChange); + } + applySidebarState(); const t = document.getElementById('sidebarToggle'); if (t) t.onclick = toggleSidebar;