Compare commits

...

41 Commits

Author SHA1 Message Date
c69694031c test 2025-12-02 18:04:27 +01:00
05dbc38507 test 2025-12-02 18:03:22 +01:00
3233ce1998 test 2025-12-02 18:02:06 +01:00
7a12f25d0a test 2025-12-02 17:57:23 +01:00
bc7e0644e3 test 2025-12-02 17:54:15 +01:00
f6929726f4 test 2025-12-02 17:46:21 +01:00
55c4d88608 test 2025-12-02 17:43:41 +01:00
0f07e73d7d test 2025-12-02 17:41:40 +01:00
0c0b4effea test 2025-12-02 17:35:37 +01:00
ff6500e709 test 2025-12-02 17:34:49 +01:00
Gitea Deploy
eca11a82a9 Fix build script to copy static files 2025-12-02 17:30:01 +01:00
root
e3b67752b5 Test auto-deploy 2025-12-02 16:25:22 +01:00
3dd1e4d576 nTest auto-deploy 2025-12-02 16:05:12 +01:00
ce48826dd8 npm 2025-12-02 15:54:22 +01:00
cf7541b5ff npm 2025-12-02 15:52:23 +01:00
b55fdb532b npm 2025-12-02 15:45:09 +01:00
8653c510b7 npm 2025-12-02 15:44:35 +01:00
bd3eb14edc viewer 2025-12-02 14:08:53 +01:00
f6c2cb0a8a test.. 2025-12-02 13:50:28 +01:00
c2646d9680 test.. 2025-12-02 13:40:22 +01:00
2ac206ebd9 test.. 2025-12-02 13:27:26 +01:00
f6ccae25ca wiki 2025-12-02 13:21:59 +01:00
eb12cc4dd5 Images 2025-12-02 13:21:42 +01:00
aa02ea67aa wiki 2025-12-02 13:12:29 +01:00
ca3a190e32 work? 2025-12-02 12:54:36 +01:00
7a4e310acc work? 2025-12-02 12:51:30 +01:00
dba487efd4 Add wiki submodule 2025-12-02 12:45:57 +01:00
7fe32f1715 Initial wiki content 2025-12-02 12:45:56 +01:00
449006b1df Remove wiki from main repo 2025-12-02 12:44:49 +01:00
5ae2fba1e4 work? 2025-12-02 12:44:03 +01:00
77b951d415 Add wiki submodule 2025-12-02 12:43:05 +01:00
be532fd78f Remove wiki files from main repo to prepare for submodule 2025-12-02 12:43:05 +01:00
e3a8d3b98e Fix wiki submodule configuration 2025-12-02 12:42:18 +01:00
3e6ef60b9c Add wiki content 2025-12-02 12:35:14 +01:00
f2de4d7100 wiki 2025-12-02 12:29:00 +01:00
836d7dd65b Add wiki as submodule 2025-12-02 12:28:40 +01:00
02afce8229 Add documentation 2025-12-02 12:27:29 +01:00
8d536151a8 view 2025-12-02 12:24:02 +01:00
bef1bd6181 Add wiki as submodule 2025-12-02 12:20:27 +01:00
9d2a94c568 view 2025-12-02 12:07:45 +01:00
1bee1ecfbc view 2025-12-02 12:07:20 +01:00
27 changed files with 7863 additions and 1123 deletions

1
.app-type Normal file
View File

@@ -0,0 +1 @@
type=static-fe

42
.dockerignore Normal file
View File

@@ -0,0 +1,42 @@
# Virtual environments
.venv/
venv/
env/
# Python cache
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
# Git
.git/
.gitignore
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Documentation (not needed in container)
*.md
wiki/
# Logs
logs/
*.log
# Docker
docker-compose.yml
Dockerfile
# Output files (generated during build)
# *.png
# *.gv

5
.gitignore vendored
View File

@@ -10,10 +10,9 @@ __pycache__/
*.so *.so
.Python .Python
node_modules/
# Generated diagram files # Generated diagram files
*.png
*.pdf *.pdf
*.svg
*.gv *.gv
# IDE # IDE
@@ -27,7 +26,7 @@ __pycache__/
.DS_Store .DS_Store
Thumbs.db Thumbs.db
# Distribution / packaging # Distribution // packaging
dist/ dist/
build/ build/
*.egg-info/ *.egg-info/

23
Dockerfile Normal file
View File

@@ -0,0 +1,23 @@
# -- Build stage --
FROM node:20-alpine AS build
WORKDIR /app
# Alleen package-bestanden eerst (better caching)
COPY package*.json ./
RUN npm ci || npm install
# Dan de rest van de code
COPY . .
# LET OP: pas dit aan als jouw build-script anders heet
RUN npm run build
# -- Serve stage --
FROM nginx:alpine
WORKDIR /usr/share/nginx/html
# Pas dit aan als jouw outputmap niet 'dist' is (bijv. 'build')
COPY --from=build /app/dist ./
# Optioneel: eigen nginx.conf voor SPA routing
# COPY nginx.conf /etc/nginx/conf.d/default.conf

26
docker-compose.yml Normal file
View File

@@ -0,0 +1,26 @@
services:
viewer:
build:
context: /opt/apps/viewer
dockerfile: Dockerfile
container_name: viewer
restart: unless-stopped
networks:
- traefik_net
labels:
- "traefik.enable=true"
- "traefik.http.routers.viewer.rule=Host(`viewer.appmodel.nl`)"
- "traefik.http.routers.viewer.entrypoints=websecure"
- "traefik.http.routers.viewer.tls=true"
- "traefik.http.services.viewer.loadbalancer.server.port=80"
- "traefik.http.routers.viewer-http.rule=Host(`viewer.appmodel.nl`)"
- "traefik.http.routers.viewer-http.entrypoints=web"
- "traefik.http.routers.viewer-http.middlewares=viewer-https"
- "traefik.http.middlewares.viewer-https.redirectscheme.scheme=https"
- "traefik.http.routers.auction.tls.certresolver=letsencrypt",
- "traefik.http.middlewares.viewer-https.redirectscheme.permanent=true"
networks:
traefik_net:
external: true
name: traefik_net

View File

@@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Home-Lab diagram Mermaid + Kroki (Graphviz)</title> <title>Home-Lab viewer Mermaid + Kroki (Graphviz)</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<style> <style>
:root{--bg:#f7f9fc;--text:#222;--accent:#0d6efd} :root{--bg:#f7f9fc;--text:#222;--accent:#0d6efd}
@@ -32,7 +32,7 @@
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
</head> </head>
<body> <body>
<header>🏠 Home-Lab diagram editable in browser (localStorage auto-saved)</header> <header>🏠 Home-Lab viewer editable in browser (localStorage auto-saved)</header>
<div class="wrap"> <div class="wrap">
<!-- ---- LEFT: EDITOR ---- --> <!-- ---- LEFT: EDITOR ---- -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

283
lab/preview.html Normal file
View File

@@ -0,0 +1,283 @@
<!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, #preview{flex:1;padding:.5rem;overflow:auto;background:#fff;font-family:"Consolas","Monaco",monospace}
#src{border:none;outline:none;background:#1e1e1e;color:#d4d4d4;white-space:pre-wrap}
#preview{display:flex;align-items:flex-start;justify-content:center;min-height:0}
svg{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}
</style>
<!-- ONLY Mermaid NO Monaco, NO AMD loader -->
<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>
/* ---------- DEFAULT SOURCE guaranteed valid Mermaid 10.6.1 ---------- */
const DEFAULT = `flowchart TD
%% ---------- styles ----------
classDef internet fill:#e1f5ff,stroke:#007bff
classDef router fill:#fff3cd,stroke:#ffc107
classDef lan fill:#f8f9ff,stroke:#6c757d,stroke-width:2px
classDef core fill:#ffe6e6,stroke:#dc3545
classDef infra fill:#e6ffe6,stroke:#28a745
classDef worker fill:#f0e6ff,stroke:#6f42c1
classDef iot fill:#fff9e6,stroke:#fd7e14
%% ---------- nodes ----------
internet(🌐 Internet / Cloud):::internet
router[🛜 Router\nhub.lan\n192.168.1.1]:::router
subgraph LAN [🏠 LAN 192.168.1.0/24]
subgraph CORE [💻 Core server\n192.168.1.159]
traefik[🚦 Traefik]:::core
gitea[📚 Gitea]:::core
dokku[🐳 Dokku]:::core
auction[🧱 Auction stack]:::core
mi50[🧠 MI50 / Ollama]:::core
end
subgraph INFRA [🧭 Infra & DNS\n192.168.1.163]
adguard[🛡️ AdGuard]:::infra
artifactory[📦 Artifactory]:::infra
end
ha[🏡 Home Assistant\n192.168.1.193]:::infra
atlas[🧱 Atlas\n192.168.1.100]:::worker
iot1[📺 IoT-1]:::iot
iot2[📟 IoT-2]:::iot
end
subgraph TETHER [📶 Tether 192.168.137.0/24]
hermes[🛰️ Hermes]:::worker
plato[🛰️ Plato]:::worker
end
dev[👨‍💻 Dev laptop]:::internet
%% ---------- edges ----------
internet ==> router
router --> CORE
router --> INFRA
router --> ha
router --> atlas
router --> iot1
router --> iot2
dev ==> gitea
dev ==> dokku
dev ==> mi50
traefik --> gitea
traefik --> auction
traefik --> dokku
CORE -.->|DNS| adguard
ha -.->|DNS| adguard
atlas-.->|DNS| adguard
hermes-.->|DNS| adguard
plato-.->|DNS| adguard
CORE === TETHER
`;
/* ---------- safe localStorage access ---------- */
function getStorage(key, fallback) {
try {
const val = localStorage.getItem(key);
return val !== null ? val : fallback;
} catch (e) {
// IntelliJ preview blocks localStorage
console.warn('localStorage unavailable:', e);
return fallback;
}
}
function setStorage(key, val) {
try {
localStorage.setItem(key, val);
} catch (e) {
console.warn('localStorage save failed:', e);
}
}
/* ---------- load state ---------- */
const srcEl = document.getElementById('src');
const errEl = document.getElementById('error');
srcEl.value = getStorage('labDiagram', DEFAULT);
const srcPanel = document.getElementById('srcPanel');
const toggleBtn = document.getElementById('toggleBtn');
if (getStorage('panelCollapsed', 'false') === 'true') {
srcPanel.classList.add('collapsed');
toggleBtn.textContent = '🗔 Show source';
}
/* ---------- panel toggle ---------- */
function togglePanel() {
srcPanel.classList.toggle('collapsed');
const collapsed = srcPanel.classList.contains('collapsed');
toggleBtn.textContent = collapsed ? '🗔 Show source' : '🗔 Hide source';
setStorage('panelCollapsed', collapsed);
}
/* ---------- Mermaid init ---------- */
mermaid.initialize({startOnLoad: false, theme: 'default'});
/* ---------- render ---------- */
function render() {
const src = srcEl.value;
setStorage('labDiagram', src);
showError('');
const preview = document.getElementById('preview');
preview.innerHTML = '';
const mermaidDiv = document.createElement('div');
mermaidDiv.className = 'mermaid';
mermaidDiv.textContent = src;
preview.appendChild(mermaidDiv);
// Render then zoom
mermaid.init(undefined, mermaidDiv).then(() => {
const svg = preview.querySelector('svg');
if (svg) {
svg.style.maxWidth = 'none';
svg.style.width = 'auto';
svg.style.height = 'auto';
svg.style.transformOrigin = 'top left';
applyZoom();
}
}).catch(e => {
showError('Mermaid error: ' + e.message);
});
}
function showError(msg) {
errEl.textContent = msg;
errEl.style.display = msg ? 'block' : 'none';
}
/* ---------- auto-render + zoom ---------- */
srcEl.addEventListener('input', () => {
clearTimeout(srcEl._t);
srcEl._t = setTimeout(render, 300);
});
const zoomEl = document.getElementById('zoom');
zoomEl.addEventListener('input', () => {
document.getElementById('zoomVal').textContent = zoomEl.value + '%';
applyZoom();
});
function applyZoom() {
const svg = document.querySelector('#preview svg');
if (svg) {
svg.style.transform = `scale(${zoomEl.value / 100})`;
}
}
/* ---------- actions ---------- */
function reset() {
if (confirm('Reset to default diagram?')) {
srcEl.value = DEFAULT;
zoomEl.value = 100;
document.getElementById('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 bbox = svg.getBBox();
const width = Math.max(bbox.width, 1200);
const height = Math.max(bbox.height, 800);
const svgData = new XMLSerializer().serializeToString(svg);
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, width, height);
ctx.drawImage(img, 0, 0, width, height);
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);
}
// Initial render
render();
</script>
</body>
</html>

4651
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

9
package.json Normal file
View File

@@ -0,0 +1,9 @@
{
"name": "viewer",
"version": "1.0.0",
"description": "",
"maintainers": ["michael@appmodel.nl"],
"scripts": {
"build": "mkdir -p dist && cp -r public/* dist/"
}
}

BIN
public/home.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

BIN
public/img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

266
public/index.html Normal file
View File

@@ -0,0 +1,266 @@
<!-- 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 TD
%% ---------- styles ----------
classDef internet fill:#e1f5ff,stroke:#007bff
classDef router fill:#fff3cd,stroke:#ffc107
classDef lan fill:#f8f9ff,stroke:#6c757d,stroke-width:2px
classDef core fill:#ffe6e6,stroke:#dc3545
classDef infra fill:#e6ffe6,stroke:#28a745
classDef worker fill:#f0e6ff,stroke:#6f42c1
classDef iot fill:#fff9e6,stroke:#fd7e14
%% ---------- nodes ----------
internet(🌐 Internet / Cloud):::internet
router[🛜 Router\nhub.lan\n192.168.1.1]:::router
subgraph LAN [🏠 LAN 192.168.1.0/24]
subgraph CORE [💻 Core server\n192.168.1.159]
traefik[🚦 Traefik]:::core
gitea[📚 Gitea]:::core
dokku[🐳 Dokku]:::core
auction[🧱 Auction stack]:::core
mi50[🧠 MI50 / Ollama]:::core
end
subgraph INFRA [🧭 Infra & DNS\n192.168.1.163]
adguard[🛡️ AdGuard]:::infra
artifactory[📦 Artifactory]:::infra
end
ha[🏡 Home Assistant\n192.168.1.193]:::infra
atlas[🧱 Atlas\n192.168.1.100]:::worker
iot1[📺 IoT-1]:::iot
iot2[📟 IoT-2]:::iot
end
subgraph TETHER [📶 Tether 192.168.137.0/24]
hermes[🛰️ Hermes]:::worker
plato[🛰️ Plato]:::worker
end
dev[👨‍💻 Dev laptop]:::internet
%% ---------- edges ----------
internet ==> router
router --> CORE
router --> INFRA
router --> ha
router --> atlas
router --> iot1
router --> iot2
dev ==> gitea
dev ==> dokku
dev ==> mi50
traefik --> gitea
traefik --> auction
traefik --> dokku
CORE -.->|DNS| adguard
ha -.->|DNS| adguard
atlas-.->|DNS| adguard
hermes-.->|DNS| adguard
plato-.->|DNS| adguard
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`) -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

40
test.md
View File

@@ -1,40 +0,0 @@
┌─────────────────────────────────────────────────────────────────────┐
│ 🌐 Internet / Cloud │
└────────────────┬────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 🛜 Router (hub.lan) - 192.168.1.1 │
│ Gateway & DHCP │
└────────────────┬────────────────────────────────────────────────────┘
┌────────────┴────────────┬──────────────┬──────────────┐
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ LAN │ │ LAN │ │ LAN │ │ LAN │
│ 192.168.│ │ Infra & │ │ Home │ │ IoT & │
│ 1.x │ │ DNS │ │ Assist │ │ Workers │
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
│ ┌────┐ │ │ ┌────┐ │ │ ┌────┐ │ │ ┌────┐ │
│ │Core│ │ │ │DNS │ │ │ │ HA │ │ │ │Atlas│ │
│ │ Srv│ │ │ │Host│ │ │ │Host│ │ │ │ │ │
│ └┬───┘ │ │ └┬───┘ │ │ └┬───┘ │ │ └┬───┘ │
│ │ │ │ │ │ │ │ │ │ │ │
│ ┌─▼──┐ │ │ ┌─▼──┐ │ │ ┌─▼──┐ │ │ │ │
│ │Dock│ │ │ │AdGr│ │ │ │Hass│ │ │ │ │
│ │er │ │ │ │d │ │ │ │ │ │ │ │ │
│ └────┘ │ │ └────┘ │ │ └────┘ │ │ │ │
│ │ │ │ │ │ │ │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
│ ┌─────────────────────────────────────────────────────┐
└──►│ Tether Subnet 192.168.137.x │
│ ┌──────────┐ ┌──────────┐ │
│ │ Hermes │ │ Plato │ │
│ │ Worker │ │ Worker │ │
│ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────┘
Developer Laptops
(Windows/WSL)

502
wiki/DEPLOY_SERVER_SETUP.md Normal file
View File

@@ -0,0 +1,502 @@
# Deploy Server Setup - Docker Build Pipeline
Complete guide for setting up and using the Docker-based deployment pipeline on the build server.
## Architecture Overview
```
┌─────────────────────────────────────────────────────────────────┐
│ Deployment Flow │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Dev Machine Gitea Server │
│ ┌──────────┐ ┌──────┐ ┌─────────┐ │
│ │ git │ │ Repo │ │ /opt/ │ │
│ │ commit ├─────push────▶│Tour/ ├──hook────▶ │ apps/ │ │
│ │ push │ │<app> │ │ <app>/ │ │
│ └──────────┘ └──────┘ └────┬────┘ │
│ │ │
│ git pull │
│ │ │
│ ┌───────────▼────────┐ │
│ │ app-deploy <app> │ │
│ │ (as user: git) │ │
│ └───────────┬────────┘ │
│ │ │
│ docker compose up -d │
│ --build │
│ │ │
│ ┌───────────▼────────┐ │
│ │ ~/infra/<app>/ │ │
│ │ docker-compose.yml │ │
│ └───────────┬────────┘ │
│ │ │
│ rebuild │
│ │ │
│ ┌───────────▼────────┐ │
│ │ 🐳 Container │ │
│ │ <app>:latest │ │
│ └───────────┬────────┘ │
│ │ │
│ traefik_net │
│ │ │
│ ┌───────────▼────────┐ │
│ │ 🚦 Traefik │ │
│ │ Reverse Proxy │ │
│ └───────────┬────────┘ │
│ │ │
│ https://<app>.appmodel.nl│
│ │
└─────────────────────────────────────────────────────────────────┘
```
## Build Pipeline Workflow
### Key Principles
1. **Source of Truth**: Gitea repository `Tour/<app>` is the authoritative source
2. **Build Server**: Server is only for building and deployment, not development
3. **Automated Deployment**: Git push triggers automatic rebuild and deployment
4. **Isolation**: Each app runs in its own Docker container
5. **Shared Networking**: All apps connect via `traefik_net` for reverse proxy
### Pipeline Steps
#### 1. Git Push (Developer)
```bash
git push origin main
```
#### 2. Post-Receive Hook (Gitea)
Located in: **Settings → Git Hooks → post-receive**
```bash
#!/usr/bin/env bash
/usr/local/bin/app-deploy viewer
```
#### 3. App Deploy Script (Server)
Runs as Linux user `git`:
```bash
app-deploy viewer
```
This script performs:
1. `cd /opt/apps/viewer`
2. `git pull --ff-only` from `git@git.appmodel.nl:Tour/viewer.git`
3. `cd /home/tour/infra/viewer`
4. `docker compose up -d --build viewer`
#### 4. Docker Build (Server)
Multi-stage build process:
- **Stage 1**: Build viewer using Python + Graphviz
- **Stage 2**: Serve with Nginx
#### 5. Traefik Routing (Server)
Traefik automatically detects the container via labels and publishes:
```
https://viewer.appmodel.nl
```
#### 6. DNS Resolution (AdGuard)
AdGuard resolves `viewer.appmodel.nl` → Build server IP
Result: Both internal and external clients use the same URL.
## Server Directory Structure
```
/opt/apps/
└── viewer/ # Git repository (pulled from Gitea)
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
├── lan_architecture.py
├── main.py
└── public/
└── index.html
/home/tour/infra/
└── viewer/
├── docker-compose.yml # Docker Compose config (may differ from repo)
└── logs/ # Nginx logs (optional)
/var/log/
└── app-deploy-viewer.log # Deployment logs
```
## Setup Instructions
### 1. Create Repository in Gitea
1. Navigate to: https://git.appmodel.nl
2. Create new repository:
- **Owner**: `Tour`
- **Name**: `viewer`
- **Visibility**: Private
### 2. Initialize Local Repository
```bash
# On dev machine
git clone git@git.appmodel.nl:Tour/viewer.git
cd viewer
# Add project files
git add Dockerfile docker-compose.yml requirements.txt *.py public/
git commit -m "Initial commit: viewer"
git push origin main
```
### 3. Setup Server Directories
```bash
# On server as appropriate user
sudo -u git mkdir -p /opt/apps/viewer
sudo -u git git clone git@git.appmodel.nl:Tour/viewer.git /opt/apps/viewer
mkdir -p /home/tour/infra/viewer
cp /opt/apps/viewer/docker-compose.yml /home/tour/infra/viewer/
```
### 4. Configure Gitea Post-Receive Hook
1. Go to: https://git.appmodel.nl/Tour/viewer
2. Navigate to: **Settings → Git Hooks**
3. Select: **post-receive**
4. Add script:
```bash
#!/usr/bin/env bash
/usr/local/bin/app-deploy viewer
```
5. Save and test
### 5. Configure DNS
Add DNS record in AdGuard:
```
viewer.appmodel.nl → <server-ip>
```
### 6. Test Deployment
```bash
# Manual test on server
app-deploy viewer
# Verify container is running
cd /home/tour/infra/viewer
docker compose ps
# Check logs
docker compose logs -f viewer
# Test endpoint
curl -I https://viewer.appmodel.nl
```
## Docker Configuration
### Dockerfile Explained
```dockerfile
# Stage 1: Build diagrams
FROM python:3.11-slim AS builder
RUN apt-get update && apt-get install -y graphviz
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY *.py .
RUN python lan_architecture.py && python main.py
# Stage 2: Serve with Nginx
FROM nginx:alpine
COPY --from=builder /app/*.png /usr/share/nginx/html/
COPY public/ /usr/share/nginx/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```
**Benefits**:
- Small final image (only Nginx + static files)
- Build tools not included in production image
- Automatic diagram generation on build
### docker-compose.yml Configuration
```yaml
version: '3.8'
services:
viewer:
build:
context: .
dockerfile: Dockerfile
container_name: viewer
restart: unless-stopped
networks:
- traefik_net
labels:
- "traefik.enable=true"
- "traefik.http.routers.viewer.rule=Host(`viewer.appmodel.nl`)"
- "traefik.http.routers.viewer.entrypoints=websecure"
- "traefik.http.routers.viewer.tls=true"
- "traefik.http.routers.viewer.tls.certresolver=letsencrypt"
- "traefik.http.services.viewer.loadbalancer.server.port=80"
networks:
traefik_net:
external: true
```
**Key Points**:
- Connects to external `traefik_net` network
- Traefik labels configure automatic HTTPS with Let's Encrypt
- Container restarts automatically unless stopped manually
## Development Workflow
### Local Development
```bash
# Edit files locally
vim lan_architecture.py
vim public/index.html
# Test locally (optional)
python -m venv .venv
source .venv/bin/activate # or .venv\Scripts\activate on Windows
pip install -r requirements.txt
python lan_architecture.py
# Commit and push
git add .
git commit -m "Update viewer architecture"
git push
```
### Automatic Deployment
After `git push`, the server automatically:
1. Receives post-receive hook trigger
2. Pulls latest code
3. Rebuilds Docker image
4. Restarts container
5. Updates live site
**Timeline**: Usually completes in 30-60 seconds.
### Manual Deployment
If needed, manually trigger deployment:
```bash
# On server
app-deploy viewer
# Or manually
cd /opt/apps/viewer
git pull
cd /home/tour/infra/viewer
docker compose up -d --build viewer
```
## Monitoring & Troubleshooting
### View Logs
```bash
# Deployment logs
tail -f /var/log/app-deploy-viewer.log
# Container logs
cd /home/tour/infra/viewer
docker compose logs -f viewer
# Nginx access logs (if volume mounted)
tail -f /home/tour/infra/viewer/logs/access.log
```
### Check Container Status
```bash
cd /home/tour/infra/viewer
# List running containers
docker compose ps
# Inspect container
docker compose exec viewer sh
# Test nginx config
docker compose exec viewer nginx -t
```
### Debug Build Issues
```bash
# Rebuild without cache
docker compose build --no-cache viewer
# View build output
docker compose up --build viewer
# Check image layers
docker image history diagram-viewer
```
### Common Issues
#### 1. Build Fails - Missing Dependencies
**Symptom**: Build fails at pip install step
**Solution**: Verify `requirements.txt` is correct
```bash
cd /opt/apps/viewer
cat requirements.txt
```
#### 2. Nginx 404 Error
**Symptom**: Container runs but shows 404
**Solution**: Check if files were copied correctly
```bash
docker compose exec viewer ls -la /usr/share/nginx/html/
```
#### 3. Traefik Not Routing
**Symptom**: Container runs but domain not accessible
**Solution**:
- Verify container is on `traefik_net`: `docker network inspect traefik_net`
- Check Traefik labels: `docker inspect viewer | grep traefik`
- Verify DNS: `nslookup viewer.appmodel.nl`
#### 4. Git Pull Fails
**Symptom**: app-deploy fails at git pull
**Solution**:
- Check SSH keys for git user: `sudo -u git ssh -T git@git.appmodel.nl`
- Verify remote: `cd /opt/apps/viewer && git remote -v`
## Security Considerations
### User Permissions
- **git user**: Owns `/opt/apps/*` directories, runs app-deploy
- **tour user**: Owns `/home/tour/infra/*`, runs Docker Compose
- Principle: Separate concerns between git operations and container management
### Network Isolation
- Containers only exposed via Traefik
- No direct port exposure to host
- `traefik_net` provides isolation between services
### Secrets Management
For apps requiring secrets:
```yaml
# In docker-compose.yml
services:
viewer:
environment:
- SECRET_KEY=${SECRET_KEY}
env_file:
- .env # Not committed to git
```
Create `.env` file on server:
```bash
echo "SECRET_KEY=your-secret-here" > /home/tour/infra/viewer/.env
chmod 600 /home/tour/infra/viewer/.env
```
## Scaling & Future Improvements
### Multi-Container Apps
For apps with multiple services:
```yaml
services:
frontend:
build: ./frontend
labels:
- "traefik.enable=true"
- "traefik.http.routers.app-fe.rule=Host(`app.appmodel.nl`)"
backend:
build: ./backend
labels:
- "traefik.enable=true"
- "traefik.http.routers.app-be.rule=Host(`api.app.appmodel.nl`)"
database:
image: postgres:15
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
```
### CI/CD Integration
Consider adding:
- Automated tests before deployment
- Rollback mechanism
- Blue-green deployments
- Health checks before switching traffic
### Monitoring
Add monitoring stack:
- Prometheus for metrics
- Grafana for dashboards
- Loki for log aggregation
- Alertmanager for notifications
## Quick Reference
### Key Paths
| Path | Purpose |
|------|---------|
| `/opt/apps/<app>/` | Git repository checkout |
| `/home/tour/infra/<app>/` | Docker Compose directory |
| `/var/log/app-deploy-<app>.log` | Deployment logs |
### Key Commands
| Command | Purpose |
|---------|---------|
| `app-deploy <app>` | Deploy/redeploy application |
| `docker compose ps` | List running containers |
| `docker compose logs -f <app>` | Follow container logs |
| `docker compose restart <app>` | Restart service |
| `docker compose build --no-cache` | Force rebuild |
### URLs
| Service | URL |
|---------|-----|
| Gitea | https://git.appmodel.nl |
| Diagram Viewer | https://viewer.appmodel.nl |
| Traefik Dashboard | https://traefik.appmodel.nl/dashboard/ |
## Support
For issues or questions:
1. Check deployment logs: `/var/log/app-deploy-<app>.log`
2. Check container logs: `docker compose logs -f`
3. Review Gitea webhook history in repository settings
4. Test manual deployment: `app-deploy <app>`

378
wiki/Home.md Normal file
View File

@@ -0,0 +1,378 @@
# Diagram Viewer Wiki
Welcome to the **Diagram Viewer** project documentation.
This project provides a static web interface for viewing and managing network architecture diagrams generated with Python's `diagrams` library.
## 📚 Contents
- [Getting Started](#getting-started)
- [Development Guide](#development-guide)
- [Deployment Guide](#deployment-guide)
- [Architecture Overview](#architecture-overview)
---
## Getting Started
### Prerequisites
- Python 3.8+
- Graphviz
- Docker (for deployment)
### Quick Start
```bash
# Clone repository
git clone git@git.appmodel.nl:Tour/viewer.git
cd viewer
# Create virtual environment
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
# Generate diagrams
python lan_architecture.py
python main.py
```
---
## Development Guide
### Project Structure
```
viewer/
├── Dockerfile # Multi-stage build for production
├── docker-compose.yml # Docker Compose configuration
├── requirements.txt # Python dependencies
├── lan_architecture.py # LAN architecture diagram generator
├── main.py # Main diagram generator
├── public/
│ └── index.html # Live Mermaid diagram editor
└── wiki/ # Documentation (this wiki)
```
### Adding New Diagrams
1. Create a new Python file (e.g., `network_diagram.py`)
2. Import required components from `diagrams` library
3. Define your architecture using context managers
4. Run the script to generate PNG output
Example:
```python
from diagrams import Diagram, Cluster
from diagrams.onprem.network import Router
from diagrams.generic.device import Mobile
with Diagram("My Network", show=False, filename="my_network"):
router = Router("Gateway")
device = Mobile("Phone")
device >> router
```
### Testing Locally
```bash
# Generate diagrams
python lan_architecture.py
# View output
ls -la *.png
# Test with local web server
python -m http.server 8000 --directory public/
# Open: http://localhost:8000
```
### Available Icons
The `diagrams` library provides icons from multiple providers:
- **OnPrem**: Network, compute, storage, etc.
- **Cloud**: AWS, Azure, GCP services
- **Generic**: Network devices, blank nodes
- **Custom**: Use your own images
See: [Diagrams Documentation](https://diagrams.mingrammer.com/docs/nodes/overview)
---
## Deployment Guide
### Overview
The project uses a fully automated Docker-based deployment pipeline:
1. **Develop** on local machine
2. **Commit & Push** to Gitea
3. **Auto-Deploy** via post-receive hook
4. **Build** multi-stage Docker image
5. **Serve** via Traefik reverse proxy
### Deployment Steps
#### 1. Initial Setup
```bash
# On server: Create app structure
apps-create viewer static-fe
# This creates:
# - /opt/apps/viewer (git repository)
# - /home/tour/infra/viewer/docker-compose.yml
```
#### 2. Configure Gitea Hook
In repository **Settings → Git Hooks → post-receive**:
```bash
#!/usr/bin/env bash
/usr/local/bin/app-deploy viewer
```
#### 3. Deploy
Simply push to Gitea:
```bash
git push origin main
```
The server automatically:
- Pulls latest code
- Rebuilds Docker image
- Restarts container
- Updates live site at https://viewer.appmodel.nl
### Manual Deployment
If needed:
```bash
# On server
app-deploy viewer
# Or step-by-step
cd /opt/apps/viewer
git pull
cd /home/tour/infra/viewer
docker compose up -d --build viewer
```
### Monitoring
```bash
# View deployment logs
tail -f /var/log/app-deploy-viewer.log
# View container logs
cd /home/tour/infra/viewer
docker compose logs -f viewer
# Check container status
docker compose ps
```
---
## Architecture Overview
### Infrastructure
```
┌─────────────────────────────────────────────┐
│ Production Architecture │
├─────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ │
│ │ Gitea │ (Source of Truth) │
│ │ Tour/viewer │ │
│ └──────┬───────┘ │
│ │ git push │
│ ↓ │
│ ┌──────────────┐ │
│ │ Post-Receive │ (Auto-trigger) │
│ │ Hook │ │
│ └──────┬───────┘ │
│ │ app-deploy viewer │
│ ↓ │
│ ┌──────────────────────┐ │
│ │ /opt/apps/viewer/ │ │
│ │ (Git Repository) │ │
│ └──────┬───────────────┘ │
│ │ git pull │
│ ↓ │
│ ┌──────────────────────┐ │
│ │ Docker Build Process │ │
│ │ ┌─────────────────┐ │ │
│ │ │ Stage 1: Build │ │ │
│ │ │ - Python │ │ │
│ │ │ - Graphviz │ │ │
│ │ │ - Generate PNGs │ │ │
│ │ └─────────────────┘ │ │
│ │ ┌─────────────────┐ │ │
│ │ │ Stage 2: Serve │ │ │
│ │ │ - Nginx │ │ │
│ │ │ - Static files │ │ │
│ │ └─────────────────┘ │ │
│ └──────┬───────────────┘ │
│ │ container starts │
│ ↓ │
│ ┌──────────────────────┐ │
│ │ diagram-viewer │ │
│ │ Container :80 │ │
│ └──────┬───────────────┘ │
│ │ traefik_net │
│ ↓ │
│ ┌──────────────────────┐ │
│ │ Traefik │ │
│ │ Reverse Proxy │ │
│ │ + Let's Encrypt │ │
│ └──────┬───────────────┘ │
│ │ HTTPS │
│ ↓ │
│ viewer.appmodel.nl │
│ │
└─────────────────────────────────────────────┘
```
### Network Topology
The diagram viewer documents our home lab infrastructure:
- **LAN 192.168.1.0/24**: Main network
- Core server (192.168.1.159): Traefik, Gitea, Dokku, Auction stack, MI50/Ollama
- DNS server (192.168.1.163): AdGuard, Artifactory, CI runner
- Home Assistant (192.168.1.193)
- Atlas worker (192.168.1.100)
- IoT devices
- **Tether 192.168.137.0/24**: Isolated subnet
- Hermes worker (192.168.137.239)
- Plato worker (192.168.137.163)
### Services
| Service | URL | Description |
|---------|-----|-------------|
| Diagram Viewer | https://viewer.appmodel.nl | This application |
| Gitea | https://git.appmodel.nl | Git hosting |
| Auction Frontend | https://auction.appmodel.nl | Auction platform |
| Aupi API | https://aupi.appmodel.nl | Backend API |
| Dokku | https://dokku.lan | PaaS platform |
---
## Additional Resources
### Related Documentation
- [DEPLOY_SERVER_SETUP.md](../DEPLOY_SERVER_SETUP.md) - Detailed deployment guide
- [DEPLOYMENT_GUIDE.md](../DEPLOYMENT_GUIDE.md) - General deployment pipeline
- [README.md](../README.md) - Project overview
### External Links
- [Diagrams Library](https://diagrams.mingrammer.com/)
- [Graphviz](https://graphviz.org/)
- [Mermaid.js](https://mermaid.js.org/) (used in live editor)
- [Traefik Documentation](https://doc.traefik.io/traefik/)
- [Docker Compose](https://docs.docker.com/compose/)
### Contributing
To contribute to this project:
1. Create a feature branch
2. Make your changes
3. Test locally
4. Submit a pull request in Gitea
5. Wait for automatic deployment after merge
---
## Troubleshooting
### Common Issues
#### Diagrams Not Generating
**Problem**: Python script fails to generate PNG files
**Solution**:
```bash
# Check Graphviz installation
dot -V
# Reinstall if needed
# macOS: brew install graphviz
# Ubuntu: sudo apt-get install graphviz
# Windows: choco install graphviz
# Verify Python dependencies
pip list | grep diagrams
pip install --upgrade diagrams
```
#### Container Build Fails
**Problem**: Docker build fails during deployment
**Solution**:
```bash
# Check deployment logs
tail -f /var/log/app-deploy-viewer.log
# Rebuild manually with verbose output
cd /home/tour/infra/viewer
docker compose build --no-cache --progress=plain viewer
```
#### Site Not Accessible
**Problem**: Container runs but site not reachable
**Solution**:
1. Check DNS: `nslookup viewer.appmodel.nl`
2. Verify Traefik labels: `docker inspect viewer-viewer | grep traefik`
3. Check Traefik logs: `docker logs traefik`
4. Test container directly: `curl http://localhost:PORT`
#### Changes Not Reflected
**Problem**: Pushed changes but site unchanged
**Solution**:
```bash
# Force rebuild on server
cd /home/tour/infra/viewer
docker compose down
docker compose up -d --build --force-recreate viewer
# Clear browser cache
# Ctrl+Shift+R (Windows/Linux) or Cmd+Shift+R (macOS)
```
---
## Contact & Support
For issues, questions, or suggestions:
1. Check this wiki
2. Review [DEPLOY_SERVER_SETUP.md](../DEPLOY_SERVER_SETUP.md)
3. Check container logs
4. Contact the infrastructure team
---
**Last Updated**: 2025-12-02
**Version**: 1.0.0
**Maintainer**: Tour

239
wiki/Quick-Start.md Normal file
View File

@@ -0,0 +1,239 @@
# Quick Start Guide
Get up and running with the Diagram Viewer in minutes.
## For Developers
### 1. Clone Repository
```bash
git clone git@git.appmodel.nl:Tour/viewer.git
cd viewer
```
### 2. Setup Environment
```bash
# Create virtual environment
python -m venv .venv
# Activate (choose your platform)
source .venv/bin/activate # macOS/Linux
.venv\Scripts\activate # Windows CMD
source .venv/Scripts/activate # Windows Git Bash
```
### 3. Install Dependencies
```bash
pip install -r requirements.txt
```
### 4. Generate Diagrams
```bash
# Generate LAN architecture diagram
python lan_architecture.py
# Generate main diagram
python main.py
# View generated files
ls -la *.png
```
### 5. Test Locally
```bash
# Start local web server
python -m http.server 8000 --directory public/
# Open browser
# Navigate to: http://localhost:8000
```
### 6. Make Changes & Deploy
```bash
# Edit diagram code
vim lan_architecture.py
# Commit changes
git add .
git commit -m "Update network diagram"
# Push to deploy (auto-deploys to production)
git push origin main
```
---
## For Operators
### View Live Site
Simply navigate to:
```
https://viewer.appmodel.nl
```
### Manual Deployment
On the server:
```bash
# Trigger deployment
app-deploy viewer
# View logs
tail -f /var/log/app-deploy-viewer.log
# Check status
cd /home/tour/infra/viewer
docker compose ps
```
### View Logs
```bash
# Deployment logs
tail -f /var/log/app-deploy-viewer.log
# Container logs
cd /home/tour/infra/viewer
docker compose logs -f viewer
# Last 100 lines
docker compose logs --tail=100 viewer
```
### Restart Service
```bash
cd /home/tour/infra/viewer
# Restart container
docker compose restart viewer
# Full restart (rebuild)
docker compose down
docker compose up -d --build viewer
```
---
## Common Tasks
### Add New Diagram
1. Create new Python file:
```python
# my_diagram.py
from diagrams import Diagram
from diagrams.onprem.network import Router
with Diagram("My Network", show=False, filename="my_network"):
Router("Gateway")
```
2. Update Dockerfile if needed (add to RUN command):
```dockerfile
RUN python lan_architecture.py && \
python main.py && \
python my_diagram.py
```
3. Commit and push:
```bash
git add my_diagram.py Dockerfile
git commit -m "Add my network diagram"
git push
```
### Update Frontend
1. Edit HTML:
```bash
vim public/index.html
```
2. Test locally:
```bash
python -m http.server 8000 --directory public/
```
3. Deploy:
```bash
git add public/index.html
git commit -m "Update frontend"
git push
```
### Check Container Health
```bash
# Container status
docker compose ps
# Health check
docker inspect viewer | grep -A 10 Health
# Test endpoint
curl -I https://viewer.appmodel.nl
```
---
## Troubleshooting
### Build Fails
```bash
# Check logs
tail -f /var/log/app-deploy-viewer.log
# Rebuild with verbose output
cd /home/tour/infra/viewer
docker compose build --no-cache --progress=plain viewer
```
### Site Not Loading
```bash
# Check container
docker compose ps
# Check Traefik routing
docker logs traefik | grep viewer
# Test DNS
nslookup viewer.appmodel.nl
# Test directly
curl http://localhost:PORT
```
### Changes Not Visible
```bash
# Force rebuild
cd /home/tour/infra/viewer
docker compose up -d --build --force-recreate viewer
# Clear browser cache
# Ctrl+Shift+R (Windows/Linux)
# Cmd+Shift+R (macOS)
```
---
## Next Steps
- Read the [Home](Home.md) page for comprehensive documentation
- Review [DEPLOY_SERVER_SETUP.md](../DEPLOY_SERVER_SETUP.md) for deployment details
- Check [Diagrams Library docs](https://diagrams.mingrammer.com/) for icon options
- Explore the [Mermaid editor](https://viewer.appmodel.nl) for live diagram editing
---
**Need Help?** Check the [Troubleshooting](Home.md#troubleshooting) section or review deployment logs.

361
wiki/WIKI_INTEGRATION.md Normal file
View File

@@ -0,0 +1,361 @@
# Gitea Wiki Integration Guide
This guide explains how to integrate the local `wiki/` folder with Gitea's wiki system.
## How Gitea Wikis Work
Gitea stores wiki pages in a **separate Git repository** with the naming pattern:
```
<repository>.wiki.git
```
For the `viewer` repository:
- Main repo: `git@git.appmodel.nl:Tour/viewer.git`
- Wiki repo: `git@git.appmodel.nl:Tour/viewer.wiki.git`
Wiki pages are **markdown files** (`.md`) stored in the root of the wiki repository.
## Integration Options
### Option 1: Push Local Wiki to Gitea (Recommended)
This approach replaces the current Gitea wiki with your local content.
```bash
# 1. Clone the Gitea wiki repository
git clone git@git.appmodel.nl:Tour/viewer.wiki.git viewer-wiki
# 2. Copy your wiki files to the wiki repository
cd viewer-wiki
cp ../viewer/wiki/*.md .
# 3. Commit and push
git add .
git commit -m "Initialize wiki with documentation"
git push origin master
# 4. View on Gitea
# Navigate to: https://git.appmodel.nl/Tour/viewer/wiki
```
### Option 2: Use Git Submodule (Bidirectional Sync)
Keep the wiki as a submodule in the main repository for easy synchronization.
```bash
# In the main viewer repository
cd /c/vibe/viewer
# Remove local wiki folder
rm -rf wiki/
# Add Gitea wiki as submodule
git submodule add git@git.appmodel.nl:Tour/viewer.wiki.git wiki
# Copy your documentation
cp ../temp-wiki-backup/*.md wiki/
# Commit to wiki submodule
cd wiki
git add .
git commit -m "Add documentation"
git push origin master
# Update main repository
cd ..
git add wiki
git commit -m "Add wiki as submodule"
git push origin main
```
Now changes can be made either:
- **In Gitea UI**: Edit wiki pages directly
- **Locally**: Edit in `wiki/` folder, commit, and push
To sync changes from Gitea:
```bash
cd wiki
git pull origin master
cd ..
git add wiki
git commit -m "Update wiki submodule"
git push
```
### Option 3: Git Subtree (Advanced)
Similar to submodule but integrates the wiki history into the main repository.
```bash
# Add wiki as subtree
git subtree add --prefix=wiki git@git.appmodel.nl:Tour/viewer.wiki.git master --squash
# Push changes to wiki
git subtree push --prefix=wiki git@git.appmodel.nl:Tour/viewer.wiki.git master
# Pull changes from wiki
git subtree pull --prefix=wiki git@git.appmodel.nl:Tour/viewer.wiki.git master --squash
```
### Option 4: Automated Sync Script
Create a script to automatically sync wiki changes.
Create `sync-wiki.sh`:
```bash
#!/bin/bash
# sync-wiki.sh - Sync local wiki with Gitea
WIKI_REPO="git@git.appmodel.nl:Tour/viewer.wiki.git"
WIKI_DIR="wiki"
TEMP_WIKI="/tmp/gitea-wiki-$$"
# Clone Gitea wiki
git clone "$WIKI_REPO" "$TEMP_WIKI"
# Copy local changes to temp wiki
cp -r "$WIKI_DIR"/*.md "$TEMP_WIKI/"
# Commit and push
cd "$TEMP_WIKI"
git add .
if git diff-staged --quiet; then
echo "No changes to sync"
else
git commit -m "Sync wiki from main repository"
git push origin master
echo "Wiki synced successfully"
fi
# Cleanup
cd -
rm -rf "$TEMP_WIKI"
```
Usage:
```bash
chmod +x sync-wiki.sh
./sync-wiki.sh
```
## Recommended Workflow
For your deployment pipeline, I recommend **Option 1** with a manual push:
### Step-by-Step Setup
1. **Create wiki repository on Gitea** (if not exists):
- Go to: https://git.appmodel.nl/Tour/viewer
- Click "Wiki" tab
- Create first page (triggers wiki repo creation)
2. **Push local wiki content**:
```bash
# Clone wiki repository
cd /c/vibe
git clone git@git.appmodel.nl:Tour/viewer.wiki.git
# Copy your wiki files
cd viewer.wiki
cp ../viewer/wiki/*.md .
# Verify files
ls -la
# Commit and push
git add .
git commit -m "Initialize wiki documentation
Added:
- Home.md: Main documentation page
- Quick-Start.md: Quick start guide
"
git push origin master
```
3. **Verify on Gitea**:
- Navigate to: https://git.appmodel.nl/Tour/viewer/wiki
- You should see your wiki pages
4. **Keep local wiki in main repo** (optional):
- Keep `wiki/` folder in main repo as "source of truth"
- When updating wiki, manually sync to Gitea wiki repo
- Or set up automated sync via CI/CD
## Wiki Page Naming
Gitea wikis follow these conventions:
| Filename | URL | Purpose |
|----------|-----|---------|
| `Home.md` | `/wiki/` or `/wiki/Home` | Main wiki page |
| `Quick-Start.md` | `/wiki/Quick-Start` | Quick start guide |
| `API-Reference.md` | `/wiki/API-Reference` | API documentation |
**Rules**:
- Use PascalCase or kebab-case for multi-word pages
- Spaces in filenames become hyphens in URLs
- First page is always `Home.md`
## Linking Between Pages
In your markdown files:
```markdown
<!-- Link to another wiki page -->
[Quick Start Guide](Quick-Start)
<!-- Link with custom text -->
[Get Started](Quick-Start)
<!-- Link to main repository -->
[View Code](../)
<!-- Link to specific file -->
[Dockerfile](../src/master/Dockerfile)
<!-- External link -->
[Diagrams Library](https://diagrams.mingrammer.com/)
```
## Automation with Post-Receive Hook
To automatically sync wiki on deployment, add to Gitea post-receive hook:
```bash
#!/usr/bin/env bash
# Deploy main app
/usr/local/bin/app-deploy viewer
# Sync wiki (if wiki/ folder exists in repo)
if [ -d "/opt/apps/viewer/wiki" ]; then
cd /tmp
git clone git@git.appmodel.nl:Tour/viewer.wiki.git gitea-wiki-$$
cp /opt/apps/viewer/wiki/*.md /tmp/gitea-wiki-$$/
cd /tmp/gitea-wiki-$$
git add .
if ! git diff --staged --quiet; then
git commit -m "Auto-sync from main repository"
git push origin master
fi
cd /tmp
rm -rf /tmp/gitea-wiki-$$
fi
```
## Current Setup Instructions
Based on your current setup, here's what to do:
```bash
# 1. Navigate to your viewer project
cd /c/vibe/viewer
# 2. Clone the wiki repository (for viewer, adjust as needed)
cd ..
git clone git@git.appmodel.nl:Tour/viewer.wiki.git
# 3. Copy wiki files
cp viewer/wiki/*.md viewer.wiki/
# 4. Push to Gitea
cd viewer.wiki
git add .
git commit -m "Initialize wiki with deployment documentation"
git push origin master
# 5. Verify
# Open: https://git.appmodel.nl/Tour/viewer/wiki
```
## Maintaining Both Wikis
If you want to keep both local and Gitea wikis in sync:
### Manual Sync (Simple)
```bash
# After editing local wiki files
cd /c/vibe/viewer/wiki
cp *.md /c/vibe/viewer.wiki/
cd /c/vibe/viewer.wiki
git add .
git commit -m "Update documentation"
git push
```
### Automated Sync (Add to package.json or Makefile)
```json
{
"scripts": {
"sync-wiki": "cp wiki/*.md ../viewer.wiki/ && cd ../viewer.wiki && git add . && git commit -m 'Sync wiki' && git push"
}
}
```
Or create a `Makefile`:
```makefile
.PHONY: sync-wiki
sync-wiki:
@echo "Syncing wiki to Gitea..."
@cp wiki/*.md ../viewer.wiki/
@cd ../viewer.wiki && git add . && git commit -m "Sync wiki from main repo" && git push
@echo "Wiki synced successfully!"
```
Usage: `make sync-wiki`
## Best Practices
1. **Single Source of Truth**: Choose one location (local or Gitea) as authoritative
2. **Version Control**: Keep wiki changes in Git history
3. **Review Process**: Use pull requests for significant documentation changes
4. **Link Checking**: Regularly verify internal links work
5. **Consistent Formatting**: Use markdown linting tools
6. **Table of Contents**: Keep `Home.md` as navigation hub
## Troubleshooting
### Wiki Repository Doesn't Exist
Create the wiki first in Gitea UI:
1. Go to repository: https://git.appmodel.nl/Tour/viewer
2. Click "Wiki" tab
3. Click "Create New Page"
4. Create a dummy page (will be overwritten)
5. Now you can clone `viewer.wiki.git`
### Permission Denied
Ensure your SSH key is added to Gitea:
```bash
ssh -T git@git.appmodel.nl
```
### Merge Conflicts
If both local and Gitea wiki are edited:
```bash
cd viewer.wiki
git pull origin master
# Resolve conflicts manually
git add .
git commit -m "Merge local and remote changes"
git push
```
## Summary
**Recommended approach for your setup**:
1. Keep `wiki/` in main repository for version control
2. Manually push to Gitea wiki repository when documentation is ready
3. Optionally add automated sync in post-receive hook
4. Edit wiki locally, sync to Gitea for visibility
This gives you the best of both worlds:
- ✅ Documentation versioned with code
- ✅ Visible wiki in Gitea UI
- ✅ Easy to maintain and update
- ✅ Works with existing deployment pipeline