This commit is contained in:
Tour
2025-12-08 09:08:43 +01:00
commit 067abd1280
30 changed files with 5348 additions and 0 deletions

282
public/deployment.html Normal file
View File

@@ -0,0 +1,282 @@
<!-- test321 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Home-Lab diagram live editor</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
:root{--bg:#f7f9fc;--text:#222;--accent:#0d6efd}
body{margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial;font-size:14px;background:var(--bg);color:var(--text)}
header{background:var(--accent);color:#fff;padding:.6rem 1rem;font-weight:600;display:flex;gap:.5rem;align-items:center}
header button{padding:.2rem .6rem;border:none;border-radius:4px;background:#fff;color:var(--accent);cursor:pointer}
.wrap{display:flex;height:calc(100vh - 40px)}
.panel{flex:1;display:flex;flex-direction:column;overflow:hidden;border-right:1px solid #ddd;transition:flex .3s ease}
.panel:last-child{border:none}
h3{margin:.4rem .6rem;font-size:1rem}
#src{flex:1;padding:.5rem;overflow:auto;background:#1e1e1e;color:#d4d4d4;font-family:"Consolas","Monaco",monospace;border:none;outline:none;white-space:pre-wrap}
#preview{flex:1;padding:.5rem;overflow:auto;background:#fff}
/* IMPORTANT: Let the SVG flow naturally and scroll if needed */
#preview svg{display:block;margin:0 auto;max-width:100%;height:auto}
#error{color:#d32f2f;background:#fff3cd;padding:.5rem;margin:.5rem;border-left:4px solid #d32f2f;display:none}
#zoomVal{margin-left:.5rem;font-size:.9rem;color:#fff}
/* collapsed state */
#srcPanel.collapsed{flex:0}
#srcPanel.collapsed #src{display:none}
/* Debug: uncomment to see element bounds */
/* #preview{outline:2px solid red} #preview svg{outline:2px solid blue} */
</style>
<script src="https://cdn.jsdelivr.net/npm/mermaid@10.6.1/dist/mermaid.min.js"></script>
</head>
<body>
<header>
<button id="toggleBtn" onclick="togglePanel()">🗔 Hide source</button>
Home-Lab diagram live Mermaid editor
<button onclick="saveSVG()">💾 SVG</button>
<button onclick="savePNG()">💾 PNG</button>
<button onclick="reset()">🔄 Reset</button>
<label style="margin-left:auto;display:flex;align-items:center;gap:.3rem;color:#fff">
Zoom: <input type="range" id="zoom" min="50" max="300" value="100" style="width:100px"><span id="zoomVal">100%</span>
</label>
</header>
<div class="wrap">
<div class="panel" id="srcPanel">
<h3>Source (edit here)</h3>
<textarea id="src" spellcheck="false"></textarea>
</div>
<div class="panel">
<h3>Live preview</h3>
<div id="preview"></div>
<div id="error"></div>
</div>
</div>
<script>
const DEFAULT = `flowchart LR
%% ============ Internet ============
subgraph WAN[🌐 Internet / Cloud]
extDNS[(📡 Public DNS)]
extGit[(☁️ Externe registries / Git)]
end
%% ============ LAN 192.168.1.x ============
subgraph LAN[🏠 LAN 192.168.1.0/24]
hub[🛜 Router / Gateway\\nhub.lan\\n192.168.1.1]
subgraph core[💻 Hoofdserver / Desktop\\nTour / hephaestus / ollama / dokku.lan\\n192.168.1.159]
traefik[🚦 Traefik\\nReverse Proxy]
gitea[📚 Gitea\\n git.appmodel.nl]
dokku[🐳 Dokku\\nPaaS / build]
auctionFE[🧱 Auction Frontend\\n auction.appmodel.nl]
aupiAPI[🧱 Auction Backend API\\n aupi.appmodel.nl]
mi50[🧠 MI50 / Ollama\\nAI workloads]
end
subgraph infraDNS[🧭 Infra & DNS\\nodroid / dns.lan\\n192.168.1.163]
adguard[🧭 AdGuard Home\\nDNS / *.lan / *.appmodel.nl]
artifactory[📦 Artifactory]
runner[⚙️ Build runners]
end
subgraph ha[🏡 Home Automation\\nha.lan\\n192.168.1.193]
hass[🏠 Home Assistant]
end
atlas[🧱 atlas.lan\\n192.168.1.100\\n]
iot1[📺 hof-E402NA\\n192.168.1.214]
iot2[🎧 S380HB\\n192.168.1.59]
iot3[📟 ecb5faa56c90\\n192.168.1.49]
iot4[❓ Unknown\\n192.168.1.240]
end
%% ============ Tether subnet ============
subgraph TETHER[📶 Tether subnet 192.168.137.0/24]
hermes[🛰️ hermes.lan\\n192.168.137.239\\nworker / node]
plato[🛰️ plato.lan\\n192.168.137.163\\nworker / node]
end
%% ============ Verkeer ============
%% Basis LAN connecties
hub --- core
hub --- infraDNS
hub --- ha
hub --- atlas
hub --- iot1
hub --- iot2
hub --- iot3
hub --- iot4
%% WAN koppeling
hub --> WAN
infraDNS --> WAN
%% DNS-resolutie
core --> adguard
ha --> adguard
atlas --> adguard
TETHER --> adguard
%% Websites / reverse proxy
extDNS --> traefik
traefik --> gitea
traefik --> auctionFE
traefik --> aupiAPI
traefik --> dokku
%% App flow
auctionFE --> aupiAPI
aupiAPI --> adguard
%% AI workloads
core --> mi50
%% Tether workers
core --- TETHER
`;
function getStorage(key, fallback) {
try { return localStorage.getItem(key) ?? fallback; } catch { return fallback; }
}
function setStorage(key, val) {
try { localStorage.setItem(key, val); } catch {}
}
const srcEl = document.getElementById('src');
const errEl = document.getElementById('error');
const zoomEl = document.getElementById('zoom');
const zoomVal = document.getElementById('zoomVal');
const srcPanel = document.getElementById('srcPanel');
const toggleBtn = document.getElementById('toggleBtn');
srcEl.value = getStorage('labDiagram', DEFAULT);
if (getStorage('panelCollapsed', 'false') === 'true') {
srcPanel.classList.add('collapsed');
toggleBtn.textContent = '🗔 Show source';
}
function togglePanel() {
srcPanel.classList.toggle('collapsed');
const collapsed = srcPanel.classList.contains('collapsed');
toggleBtn.textContent = collapsed ? '🗔 Show source' : '🗔 Hide source';
setStorage('panelCollapsed', collapsed);
}
mermaid.initialize({startOnLoad: false, theme: 'default'});
function render() {
const src = srcEl.value;
setStorage('labDiagram', src);
errEl.style.display = 'none';
const preview = document.getElementById('preview');
preview.innerHTML = '';
const mermaidDiv = document.createElement('div');
mermaidDiv.className = 'mermaid';
mermaidDiv.textContent = src;
preview.appendChild(mermaidDiv);
// Render and fix sizing
mermaid.init(undefined, mermaidDiv).then(() => {
const svg = preview.querySelector('svg');
if (svg) {
// CRITICAL: Ensure SVG has viewBox for proper sizing
if (!svg.hasAttribute('viewBox')) {
const {width, height} = svg.getBBox();
svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
}
svg.style.maxWidth = '100%';
svg.style.height = 'auto';
applyZoom();
}
}).catch(e => {
errEl.textContent = 'Mermaid error: ' + e.message;
errEl.style.display = 'block';
});
}
srcEl.addEventListener('input', () => {
clearTimeout(srcEl._t);
srcEl._t = setTimeout(render, 300);
});
zoomEl.addEventListener('input', () => {
zoomVal.textContent = zoomEl.value + '%';
applyZoom();
});
function applyZoom() {
const svg = document.querySelector('#preview svg');
if (svg) {
svg.style.transform = `scale(${zoomEl.value / 100})`;
}
}
function reset() {
if (confirm('Reset to default diagram?')) {
srcEl.value = DEFAULT;
zoomEl.value = 100;
zoomVal.textContent = '100%';
setStorage('labDiagram', DEFAULT);
render();
}
}
function saveSVG() {
const svg = document.querySelector('#preview svg');
if (!svg) return alert('Nothing to save yet.');
const clone = svg.cloneNode(true);
clone.removeAttribute('style');
clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
const blob = new Blob([clone.outerHTML], {type: 'image/svg+xml'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'home-lab.svg';
a.click();
setTimeout(() => URL.revokeObjectURL(url), 100);
}
function savePNG() {
const svg = document.querySelector('#preview svg');
if (!svg) return alert('Nothing to save yet.');
const {width, height} = svg.getBBox();
const canvasW = Math.max(width, 1200);
const canvasH = Math.max(height, 800);
const svgData = new XMLSerializer().serializeToString(svg);
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = canvasW;
canvas.height = canvasH;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvasW, canvasH);
ctx.drawImage(img, 0, 0, canvasW, canvasH);
canvas.toBlob(blob => {
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'home-lab.png';
a.click();
});
};
img.onerror = () => alert('PNG conversion failed. Try SVG instead.');
img.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svgData);
}
render();
</script>
</body>
</html>
<!-- Auto-deployed at $(date122`) -->