Files
mek-tech-consulting/settings.ejs
T

421 lines
22 KiB
Plaintext

<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Instellingen — Mek-Tech Enterprise</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/style.css">
<link rel="stylesheet" href="/css/dynamic.css">
<link rel="stylesheet" href="/css/neon.css">
<link rel="icon" href="/img/favicon.svg" type="image/svg+xml">
<script src="https://unpkg.com/lucide@latest"></script>
<style>
body{background:var(--bg);font-family:'Inter',-apple-system,sans-serif;color:var(--text);margin:0;padding:0}
.settings-wrap{max-width:860px;margin:2rem auto;padding:0 1.5rem 3rem}
.settings-wrap h1{font-size:1.5rem;font-weight:700;margin-bottom:.15rem;display:flex;align-items:center;gap:.5rem}
.settings-wrap .sub{color:var(--text-muted);font-size:.8rem;margin-bottom:1.5rem}
.section{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:1.25rem;margin-bottom:1rem}
.section h2{font-size:.8rem;font-weight:600;text-transform:uppercase;letter-spacing:.4px;color:var(--text-muted);margin-bottom:1rem;display:flex;align-items:center;gap:.4rem}
.field{display:flex;align-items:center;gap:1rem;padding:.5rem 0;border-bottom:1px solid var(--border-light)}
.field:last-child{border-bottom:none}
.field label{min-width:140px;font-size:.82rem;color:var(--text);font-weight:500}
.field input,.field select{background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:.4rem .6rem;color:var(--text);font-size:.82rem;font-family:inherit;width:220px;transition:border-color .12s}
.field input:focus,.field select:focus{outline:none;border-color:var(--accent)}
.field input[type=checkbox]{width:18px;height:18px;accent-color:var(--accent);cursor:pointer}
.field .hint{font-size:.7rem;color:var(--text-dim);margin-left:.5rem}
.btn-pri{background:var(--accent);color:#fff;border:none;padding:.5rem 1.25rem;border-radius:var(--radius);font-size:.82rem;font-weight:600;cursor:pointer;transition:opacity .12s;font-family:inherit}
.btn-pri:hover{opacity:.85}
.btn-sec{background:var(--bg);color:var(--text-muted);border:1px solid var(--border);padding:.5rem 1.25rem;border-radius:var(--radius);font-size:.82rem;cursor:pointer;transition:all .12s;font-family:inherit}
.btn-sec:hover{border-color:var(--text-dim);color:var(--text)}
.btn-del{background:var(--red-bg);color:var(--red);border:1px solid transparent;padding:.5rem 1.25rem;border-radius:var(--radius);font-size:.82rem;cursor:pointer;transition:all .12s;font-family:inherit}
.btn-del:hover{border-color:var(--red)}
.actions{display:flex;gap:.5rem;margin-top:1.5rem;flex-wrap:wrap}
.alert{background:rgba(35,134,54,.15);border:1px solid rgba(35,134,54,.3);color:var(--green);padding:.6rem .9rem;border-radius:var(--radius);font-size:.8rem;margin-bottom:1rem;display:flex;align-items:center;gap:.4rem}
.icon-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:.35rem}
.icon-row{display:flex;align-items:center;gap:.5rem;padding:.35rem .5rem;background:var(--bg);border-radius:var(--radius);border:1px solid var(--border-light)}
.icon-row svg{width:16px;height:16px;flex-shrink:0}
.icon-row .il{font-size:.75rem;color:var(--text-muted);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.icon-row input[type=color]{width:32px;height:28px;padding:0;border:1px solid var(--border);border-radius:4px;cursor:pointer;background:none;flex-shrink:0}
.icon-row .hex-val{width:70px;background:var(--bg);border:1px solid var(--border);border-radius:4px;padding:.15rem .35rem;color:var(--text);font-size:.72rem;font-family:monospace;text-align:center}
.icon-row .hex-val:focus{outline:none;border-color:var(--accent)}
/* Theme cards */
.theme-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:.5rem;margin-bottom:1rem}
.theme-card{cursor:pointer;border:2px solid var(--border);border-radius:var(--radius);padding:.6rem;background:var(--bg);transition:all .15s;position:relative}
.theme-card:hover{border-color:var(--text-dim)}
.theme-card.active{border-color:var(--accent)}
.theme-card input[type=radio]{position:absolute;opacity:0;pointer-events:none}
.theme-card .tc-preview{height:50px;border-radius:4px;margin-bottom:.35rem;display:flex;align-items:center;justify-content:center;gap:3px;overflow:hidden}
.theme-card .tc-preview span{width:20%;height:100%;border-radius:2px}
.theme-card .tc-name{font-size:.7rem;font-weight:600;color:var(--text);text-align:center}
.theme-card .tc-mode{font-size:.6rem;color:var(--text-dim);text-align:center;text-transform:uppercase}
.theme-card.active .tc-name{color:var(--accent)}
/* Custom theme color fields */
.color-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:.35rem}
.color-row{display:flex;align-items:center;gap:.4rem;padding:.25rem .4rem;background:var(--bg);border-radius:var(--radius);border:1px solid var(--border-light)}
.color-row .cl{font-size:.68rem;color:var(--text-muted);flex:1;font-family:monospace}
.color-row input[type=color]{width:28px;height:24px;padding:0;border:1px solid var(--border);border-radius:3px;cursor:pointer;background:none;flex-shrink:0}
.color-row .ch{width:62px;background:var(--bg);border:1px solid var(--border);border-radius:3px;padding:.1rem .3rem;color:var(--text);font-size:.65rem;font-family:monospace;text-align:center}
.color-row .ch:focus{outline:none;border-color:var(--accent)}
.custom-theme-toggle{margin-bottom:.65rem}
.custom-theme-toggle label{font-size:.78rem;color:var(--text-muted);cursor:pointer;display:flex;align-items:center;gap:.4rem}
.main-color-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:.65rem;margin-bottom:1rem}
.main-color-card{background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:.75rem .85rem;display:flex;flex-direction:column;gap:.35rem}
.main-color-card label{font-size:.78rem;font-weight:600;color:var(--text)}
.main-color-card .hint{font-size:.62rem;color:var(--text-dim);line-height:1.35}
.main-color-card .picker-row{display:flex;align-items:center;gap:.5rem;margin-top:.15rem}
.main-color-card input[type=color]{width:42px;height:34px;padding:2px;border:1px solid var(--border);border-radius:6px;cursor:pointer;background:var(--bg-card);flex-shrink:0}
.main-color-card .ch{flex:1;background:var(--bg-card);border:1px solid var(--border);border-radius:6px;padding:.35rem .5rem;color:var(--text);font-size:.75rem;font-family:monospace}
.main-color-card .ch:focus{outline:none;border-color:var(--accent)}
.live-preview{border-radius:10px;overflow:hidden;border:1px solid var(--border);margin-bottom:1rem;height:72px;display:flex}
.live-preview .lp-nav{width:22%;display:flex;align-items:center;justify-content:center;font-size:.55rem;font-weight:600;color:var(--text-muted)}
.live-preview .lp-main{flex:1;padding:.5rem .65rem;display:flex;flex-direction:column;gap:.35rem}
.live-preview .lp-card{height:28px;border-radius:6px;border:1px solid var(--border);display:flex;align-items:center;padding:0 .5rem;font-size:.55rem;color:var(--text-muted)}
.live-preview .lp-accent{width:40px;height:6px;border-radius:3px;margin-top:auto}
.advanced-colors{margin-top:.75rem;border-top:1px solid var(--border-light);padding-top:.75rem}
.advanced-colors summary{font-size:.72rem;color:var(--text-muted);cursor:pointer;font-weight:600;user-select:none}
.advanced-colors summary:hover{color:var(--accent)}
@media(max-width:600px){.field{flex-wrap:wrap;gap:.3rem}.field label{min-width:100px}.field input,.field select{width:100%}}
</style>
</head>
<body>
<nav class="navbar"><div class="nav-inner"><a href="/" class="nav-brand"><img src="/img/logo.svg" class="nav-logo" alt="Mek-Tech"></a><div style="display:flex;align-items:center;gap:.4rem"><a href="/" class="nav-icon-btn" title="Dashboard"><i data-lucide="layout-dashboard" style="width:15px;height:15px"></i> Dashboard</a><button class="theme-toggle" id="themeToggle" onclick="toggleTheme()"></button><a href="/auth/logout" class="nav-icon-btn" style="color:var(--red)"><i data-lucide="log-out" style="width:15px;height:15px"></i> Uitloggen</a></div></div></nav>
<div class="settings-wrap">
<a href="javascript:history.back()" class="back-link"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg> Terug</a>
<h1><i data-lucide="settings"></i> Instellingen</h1>
<p class="sub">Configureer het platform naar wens.</p>
<% if (saved) { %>
<div class="alert"><i data-lucide="check-circle" style="color:var(--green)"></i> Instellingen opgeslagen!</div>
<% } %>
<div class="section" style="border-color:rgba(0,212,255,.35);background:linear-gradient(135deg,rgba(0,212,255,.06),transparent)">
<h2><i data-lucide="database-backup"></i> Backup & Demo Portal</h2>
<div style="display:flex;gap:.75rem;flex-wrap:wrap;align-items:center">
<a href="/backup" class="btn-pri" style="display:inline-flex;align-items:center;gap:.35rem;text-decoration:none"><i data-lucide="download" style="width:14px;height:14px"></i> Backup & Herstel</a>
<a href="<%= settings.showcase_url || 'http://192.168.1.247:3010' %>" target="_blank" rel="noopener" class="btn-sec" style="display:inline-flex;align-items:center;gap:.35rem;text-decoration:none"><i data-lucide="external-link" style="width:14px;height:14px"></i> Live Labs Demo (:3010)</a>
</div>
<p style="font-size:.72rem;color:var(--text-dim);margin-top:.65rem;margin-bottom:0">Backup = intern CRM. Live Labs = apart demo-platform voor klantbezoeken.</p>
</div>
<form method="POST" action="/settings">
<div class="section">
<h2><i data-lucide="globe"></i> Algemeen</h2>
<div class="field">
<label>Brandnaam</label>
<input type="text" name="brand_name" value="<%= settings.brand_name %>">
</div>
<div class="field">
<label>Demo Portal URL</label>
<input type="url" name="showcase_url" value="<%= settings.showcase_url || 'http://192.168.1.247:3010' %>" style="width:280px">
<span class="hint">Mek-Tech Live Labs (aparte Docker).</span>
</div>
<div class="field">
<label>Taal</label>
<select name="language">
<option value="nl" <%= settings.language==='nl'?'selected':'' %>>Nederlands</option>
<option value="en" <%= settings.language==='en'?'selected':'' %>>English</option>
</select>
</div>
<div class="field">
<label>Standaard pagina</label>
<select name="default_page">
<option value="/" <%= settings.default_page==='/'?'selected':'' %>>Dashboard</option>
<option value="/clients" <%= settings.default_page==='/clients'?'selected':'' %>>Clients</option>
<option value="/racks" <%= settings.default_page==='/racks'?'selected':'' %>>Racks</option>
<option value="/tasks" <%= settings.default_page==='/tasks'?'selected':'' %>>Tasks</option>
</select>
<span class="hint">Na inloggen.</span>
</div>
</div>
<div class="section">
<h2><i data-lucide="palette"></i> Thema & Kleuren</h2>
<p style="font-size:.72rem;color:var(--text-dim);margin:-.5rem 0 .75rem">Kies een preset als basis en pas de achtergrond en kleuren zelf aan.</p>
<input type="hidden" name="theme" id="themeInput" value="<%= settings.theme %>">
<div class="custom-theme-toggle">
<label><input type="checkbox" name="use_custom_colors" value="1" id="useCustomColors" <%= useCustomColors ? 'checked' : '' %> onchange="toggleCustomColors()"> <strong>Eigen kleuren gebruiken</strong> <span style="font-weight:400;color:var(--text-dim)">— overschrijft het gekozen preset</span></label>
</div>
<div id="customColorsBlock" style="<%= useCustomColors ? '' : 'opacity:.55;pointer-events:none' %>">
<p style="font-size:.72rem;color:var(--text-muted);margin:0 0 .5rem;font-weight:600">Jouw kleuren</p>
<div class="live-preview" id="livePreview">
<div class="lp-nav" id="pvNav">Sidebar</div>
<div class="lp-main" id="pvMain">
<div style="font-size:.6rem;font-weight:700" id="pvText">Voorbeeld tekst</div>
<div class="lp-card" id="pvCard">Kaart / panel</div>
<div class="lp-accent" id="pvAccent"></div>
</div>
</div>
<div class="main-color-grid">
<% themeMainColors.forEach(function(mc) {
var val = mergedColors[mc.key] || '';
var hexVal = toHexForPicker(val, '#0a1628');
var fieldName = 'th_' + mc.key;
%>
<div class="main-color-card">
<label for="<%= fieldName %>"><%= mc.label %></label>
<span class="hint"><%= mc.hint %></span>
<div class="picker-row">
<input type="color" id="<%= fieldName %>_picker" value="<%= hexVal %>" data-theme-key="<%= mc.key %>" data-for="<%= fieldName %>" oninput="onColorPick(this)">
<input class="ch" type="text" name="<%= fieldName %>" value="<%= val %>" data-for="<%= fieldName %>" oninput="onHexInput(this)" maxlength="32" placeholder="#0a1628 of rgba(...)">
</div>
</div>
<% }) %>
</div>
<details class="advanced-colors">
<summary>Geavanceerde kleuren (randen, statuskleuren)</summary>
<div class="color-grid" style="margin-top:.65rem">
<% var thKeys = themeCssKeys.filter(function(k){ return !themeMainColors.some(function(m){ return m.key===k; }); }); %>
<% thKeys.forEach(function(k) {
var val = mergedColors[k] || '';
var hexVal = toHexForPicker(val, '#0a1628');
var fieldName = 'th_' + k;
var label = themeColorLabels[k] || ('--' + k.replace(/_/g, '-'));
%>
<div class="color-row">
<label class="cl"><%= label %></label>
<input type="color" value="<%= hexVal %>" oninput="syncAdvColor(this,'<%= fieldName %>')">
<input class="ch" type="text" name="<%= fieldName %>" value="<%= val %>" maxlength="40">
</div>
<% }) %>
</div>
</details>
</div>
<p style="font-size:.72rem;color:var(--text-dim);margin:1rem 0 .5rem">Preset als startpunt:</p>
<div class="theme-grid">
<% var themeKeys = Object.keys(themePresets).filter(function(k){ return k !== 'custom'; }); %>
<% themeKeys.forEach(function(k) { var p = themePresets[k]; var active = settings.theme === k || (settings.theme === 'custom' && !useCustomColors); %>
<label class="theme-card<%= settings.theme === k ? ' active' : '' %>" data-theme="<%= k %>">
<input type="radio" name="_theme" value="<%= k %>" <%= settings.theme === k ? 'checked' : '' %> onchange="selectTheme('<%= k %>')">
<div class="tc-preview" style="background:<%= p.bg %>">
<span style="background:<%= p.bg_card %>"></span>
<span style="background:<%= p.accent %>"></span>
<span style="background:<%= p.green %>"></span>
<span style="background:<%= p.red %>"></span>
<span style="background:<%= p.cyan %>"></span>
</div>
<div class="tc-name"><%= p.label %></div>
<div class="tc-mode"><%= p.mode === 'dark' ? 'Donker' : 'Licht' %></div>
</label>
<% }) %>
</div>
</div>
<div class="section">
<h2><i data-lucide="palette"></i> Icoon Kleuren</h2>
<p style="font-size:.72rem;color:var(--text-dim);margin:-.5rem 0 .75rem">Kies zelf de kleur van elk icoon in de sidebar en tabbladen.</p>
<div class="icon-grid">
<% icons.forEach(function(icon) {
var color = settings.icon_colors[icon.key] || '#58a6ff';
var fieldName = 'color_' + icon.key.replace(/-/g, '_');
%>
<div class="icon-row">
<i data-lucide="<%= icon.key %>" style="color:<%= color %>"></i>
<span class="il"><%= icon.label %></span>
<input type="color" name="<%= fieldName %>" value="<%= color %>" oninput="this.nextElementSibling.value=this.value">
<input class="hex-val" type="text" value="<%= color %>" oninput="this.previousElementSibling.value=this.value" maxlength="7">
</div>
<% }) %>
</div>
</div>
<div class="section">
<h2><i data-lucide="layout-dashboard"></i> Dashboard</h2>
<div class="field">
<label>Auto-refresh</label>
<select name="auto_refresh">
<option value="0" <%= settings.auto_refresh==='0'?'selected':'' %>>Uit</option>
<option value="15" <%= settings.auto_refresh==='15'?'selected':'' %>>15 seconden</option>
<option value="30" <%= settings.auto_refresh==='30'?'selected':'' %>>30 seconden</option>
<option value="60" <%= settings.auto_refresh==='60'?'selected':'' %>>1 minuut</option>
<option value="300" <%= settings.auto_refresh==='300'?'selected':'' %>>5 minuten</option>
</select>
<span class="hint">Automatisch verversen van het dashboard.</span>
</div>
</div>
<div class="section">
<h2><i data-lucide="bell"></i> Notificaties</h2>
<div class="field">
<label>Slack Webhook URL</label>
<input type="url" name="slack_webhook" value="<%= settings.slack_webhook || '' %>" placeholder="https://hooks.slack.com/services/...">
<span class="hint">Voor Slack notificaties bij nieuwe events</span>
</div>
<div class="field">
<label>SMTP Host</label>
<input type="text" name="smtp_host" value="<%= settings.smtp_host || '' %>" placeholder="smtp.example.com">
</div>
<div class="field">
<label>SMTP Gebruiker</label>
<input type="text" name="smtp_user" value="<%= settings.smtp_user || '' %>">
</div>
<div class="field">
<label>SMTP Wachtwoord</label>
<input type="password" name="smtp_pass" value="<%= settings.smtp_pass || '' %>">
</div>
</div>
<div class="section">
<h2><i data-lucide="terminal"></i> Systeem</h2>
<div class="field">
<label>Items per pagina</label>
<input type="number" name="items_per_page" value="<%= settings.items_per_page %>" min="10" max="500" step="10">
</div>
<div class="field">
<label>Debug mode</label>
<input type="checkbox" name="debug_mode" value="1" <%= settings.debug_mode==='1'?'checked':'' %>>
<span class="hint">Toont technische details bij fouten.</span>
</div>
<div class="field" style="border-bottom:none;padding-top:.75rem">
<label>Backup</label>
<div style="flex:1">
<a href="/backup" class="btn-sec" style="display:inline-flex;align-items:center;gap:.35rem;text-decoration:none"><i data-lucide="database-backup" style="width:14px;height:14px"></i> Backup & Herstel</a>
<span class="hint">Zie ook bovenaan deze pagina.</span>
</div>
</div>
</div>
<div class="actions">
<button class="btn-pri" type="submit"><i data-lucide="save" style="width:14px;height:14px"></i> Opslaan</button>
<button class="btn-sec" type="button" onclick="previewTheme()"><i data-lucide="eye" style="width:14px;height:14px"></i> Voorbeeld</button>
<button class="btn-sec" type="button" onclick="resetDefaults()"><i data-lucide="rotate-ccw" style="width:14px;height:14px"></i> Reset naar standaard</button>
</div>
</form>
</div>
<script>
lucide.createIcons();
var themePresets = <%- JSON.stringify(themePresets) %>;
var THEME_CSS_KEYS = <%- JSON.stringify(themeCssKeys) %>;
function toggleCustomColors() {
var on = document.getElementById('useCustomColors').checked;
var block = document.getElementById('customColorsBlock');
block.style.opacity = on ? '1' : '.55';
block.style.pointerEvents = on ? 'auto' : 'none';
}
function onColorPick(el) {
var text = document.querySelector('input[name="' + el.dataset.for + '"]');
if (text) text.value = el.value;
updateLivePreview();
}
function onHexInput(el) {
var v = el.value.trim();
var picker = document.querySelector('input[data-for="' + el.name + '"]');
if (picker && /^#[0-9a-f]{6}$/i.test(v)) picker.value = v;
updateLivePreview();
}
function syncAdvColor(picker, fieldName) {
var text = document.querySelector('input[name="' + fieldName + '"]');
if (text) text.value = picker.value;
}
function getColor(key) {
var inp = document.querySelector('input[name="th_' + key + '"]');
return inp ? inp.value.trim() : '';
}
function updateLivePreview() {
var bg = getColor('bg') || '#0a1628';
var card = getColor('bg_card') || '#0f1f3a';
var nav = getColor('bg_nav') || '#0c1a30';
var accent = getColor('accent') || '#00d4ff';
var text = getColor('text') || '#e8f4ff';
var muted = getColor('text_muted') || '#7eb8d4';
document.getElementById('livePreview').style.background = bg;
document.getElementById('pvNav').style.background = nav;
document.getElementById('pvNav').style.color = muted;
document.getElementById('pvMain').style.background = bg;
document.getElementById('pvText').style.color = text;
document.getElementById('pvCard').style.background = card;
document.getElementById('pvCard').style.color = muted;
document.getElementById('pvAccent').style.background = accent;
}
function selectTheme(key) {
document.querySelectorAll('.theme-card').forEach(function(c) { c.classList.remove('active'); });
var card = document.querySelector('.theme-card[data-theme="' + key + '"]');
if (card) card.classList.add('active');
document.getElementById('themeInput').value = key;
var p = themePresets[key];
if (!p) return;
['bg','bg_card','bg_nav','accent','text','text_muted'].forEach(function(k) {
var picker = document.querySelector('input[data-for="th_' + k + '"]');
var text = document.querySelector('input[name="th_' + k + '"]');
if (p[k] && text) {
text.value = p[k];
if (picker && /^#[0-9a-f]{6}$/i.test(p[k])) picker.value = p[k];
}
});
THEME_CSS_KEYS.forEach(function(k) {
var adv = document.querySelector('input[name="th_' + k + '"][type=text]');
if (adv && p[k]) adv.value = p[k];
});
document.getElementById('useCustomColors').checked = false;
toggleCustomColors();
updateLivePreview();
}
function previewTheme() {
var colors = {};
THEME_CSS_KEYS.forEach(function(k) {
var v = getColor(k);
if (v) colors[k.replace(/_/g, '-')] = v;
});
var css = ':root{\n';
Object.keys(colors).forEach(function(k) { css += '--' + k + ':' + colors[k] + ';\n'; });
css += '}body{background-color:' + (getColor('bg') || '#0a1628') + '!important}';
var style = document.getElementById('preview-style');
if (!style) {
style = document.createElement('style');
style.id = 'preview-style';
document.head.appendChild(style);
}
style.textContent = css;
alert('Voorbeeld toegepast op deze pagina. Klik Opslaan om permanent te maken.');
}
function resetDefaults() {
if (!confirm('Alle instellingen resetten naar standaard?')) return;
var form = document.createElement('form');
form.method = 'POST';
form.action = '/settings/reset';
document.body.appendChild(form);
form.submit();
}
document.querySelectorAll('.main-color-card .ch').forEach(function(inp) {
inp.addEventListener('change', function() { onHexInput(this); });
});
updateLivePreview();
toggleCustomColors();
</script>
<script src="/js/theme.js"></script></body>
</html>