Compare commits

...

32 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
31 changed files with 7862 additions and 4514 deletions

1
.app-type Normal file
View File

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

View File

@@ -1,42 +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
# 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

63
.gitignore vendored
View File

@@ -1,33 +1,32 @@
# Virtual Environment
.venv/
venv/
env/
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
# Generated diagram files
*.png
*.pdf
*.svg
*.gv
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Distribution / packaging
dist/
build/
# Virtual Environment
.venv/
venv/
env/
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
node_modules/
# Generated diagram files
*.pdf
*.gv
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Distribution // packaging
dist/
build/
*.egg-info/

View File

@@ -1,61 +1,23 @@
# Multi-stage Dockerfile for static frontend deployment
# Stage 1: Build the diagrams
FROM python:3.11-slim AS builder
# Install system dependencies for Graphviz
RUN apt-get update && \
apt-get install -y --no-install-recommends \
graphviz \
&& rm -rf /var/lib/apt/lists/*
# Set working directory
WORKDIR /app
# Copy requirements and install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy source files
COPY *.py .
# Generate diagrams (outputs will be in /app)
RUN python lan_architecture.py && \
python main.py
# Verify generated files exist
RUN ls -la *.png || echo "Warning: No PNG files generated"
# Stage 2: Serve with Nginx
FROM nginx:alpine
# Remove default nginx static assets
RUN rm -rf /usr/share/nginx/html/*
# Copy generated diagram files from builder stage
COPY --from=builder /app/*.png /usr/share/nginx/html/
# Copy any HTML/CSS files for the frontend
COPY public/ /usr/share/nginx/html/
# Create a simple nginx config for serving static files
RUN echo 'server { \
listen 80; \
server_name _; \
root /usr/share/nginx/html; \
index index.html; \
location / { \
try_files $uri $uri/ /index.html; \
} \
# Enable CORS if needed \
add_header Access-Control-Allow-Origin *; \
}' > /etc/nginx/conf.d/default.conf
# Expose port 80
EXPOSE 80
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --quiet --tries=1 --spider http://localhost/ || exit 1
# Start nginx
CMD ["nginx", "-g", "daemon off;"]
# -- 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

378
Home.md
View File

@@ -1,378 +0,0 @@
# 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/diagram.git
cd diagram
# 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
```
diagram/
├── 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 diagram static-fe
# This creates:
# - /opt/apps/diagram (git repository)
# - /home/tour/infra/diagram/docker-compose.yml
```
#### 2. Configure Gitea Hook
In repository **Settings → Git Hooks → post-receive**:
```bash
#!/usr/bin/env bash
/usr/local/bin/app-deploy diagram
```
#### 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://diagram.appmodel.nl
### Manual Deployment
If needed:
```bash
# On server
app-deploy diagram
# Or step-by-step
cd /opt/apps/diagram
git pull
cd /home/tour/infra/diagram
docker compose up -d --build diagram
```
### Monitoring
```bash
# View deployment logs
tail -f /var/log/app-deploy-diagram.log
# View container logs
cd /home/tour/infra/diagram
docker compose logs -f diagram
# Check container status
docker compose ps
```
---
## Architecture Overview
### Infrastructure
```
┌─────────────────────────────────────────────┐
│ Production Architecture │
├─────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ │
│ │ Gitea │ (Source of Truth) │
│ │ Tour/diagram │ │
│ └──────┬───────┘ │
│ │ git push │
│ ↓ │
│ ┌──────────────┐ │
│ │ Post-Receive │ (Auto-trigger) │
│ │ Hook │ │
│ └──────┬───────┘ │
│ │ app-deploy diagram │
│ ↓ │
│ ┌──────────────────────┐ │
│ │ /opt/apps/diagram/ │ │
│ │ (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 │
│ ↓ │
│ diagram.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://diagram.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-diagram.log
# Rebuild manually with verbose output
cd /home/tour/infra/diagram
docker compose build --no-cache --progress=plain diagram
```
#### Site Not Accessible
**Problem**: Container runs but site not reachable
**Solution**:
1. Check DNS: `nslookup diagram.appmodel.nl`
2. Verify Traefik labels: `docker inspect diagram-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/diagram
docker compose down
docker compose up -d --build --force-recreate diagram
# 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

View File

@@ -1,239 +0,0 @@
# 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/diagram.git
cd diagram
```
### 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://diagram.appmodel.nl
```
### Manual Deployment
On the server:
```bash
# Trigger deployment
app-deploy diagram
# View logs
tail -f /var/log/app-deploy-diagram.log
# Check status
cd /home/tour/infra/diagram
docker compose ps
```
### View Logs
```bash
# Deployment logs
tail -f /var/log/app-deploy-diagram.log
# Container logs
cd /home/tour/infra/diagram
docker compose logs -f diagram
# Last 100 lines
docker compose logs --tail=100 diagram
```
### Restart Service
```bash
cd /home/tour/infra/diagram
# Restart container
docker compose restart diagram
# Full restart (rebuild)
docker compose down
docker compose up -d --build diagram
```
---
## 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 diagram-viewer | grep -A 10 Health
# Test endpoint
curl -I https://diagram.appmodel.nl
```
---
## Troubleshooting
### Build Fails
```bash
# Check logs
tail -f /var/log/app-deploy-diagram.log
# Rebuild with verbose output
cd /home/tour/infra/diagram
docker compose build --no-cache --progress=plain diagram
```
### Site Not Loading
```bash
# Check container
docker compose ps
# Check Traefik routing
docker logs traefik | grep diagram
# Test DNS
nslookup diagram.appmodel.nl
# Test directly
curl http://localhost:PORT
```
### Changes Not Visible
```bash
# Force rebuild
cd /home/tour/infra/diagram
docker compose up -d --build --force-recreate diagram
# 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://diagram.appmodel.nl) for live diagram editing
---
**Need Help?** Check the [Troubleshooting](Home.md#troubleshooting) section or review deployment logs.

164
README.md
View File

@@ -1,82 +1,82 @@
# Network Architecture Diagrams
Python scripts to generate network architecture diagrams using the [Diagrams](https://diagrams.mingrammer.com/) library.
## Prerequisites
- Python 3.8+
- Graphviz ([installation guide](https://graphviz.org/download/))
### Installing Graphviz
**Windows:**
```bash
choco install graphviz
# or download from https://graphviz.org/download/
```
**macOS:**
```bash
brew install graphviz
```
**Linux:**
```bash
sudo apt-get install graphviz # Debian/Ubuntu
sudo yum install graphviz # RHEL/CentOS
```
## Setup
1. Create a virtual environment:
```bash
python -m venv .venv
```
2. Activate the virtual environment:
```bash
# Windows
.venv\Scripts\activate
# macOS/Linux
source .venv/bin/activate
```
3. Install dependencies:
```bash
pip install -r requirements.txt
```
## Usage
Run the diagram generation scripts:
```bash
# Generate LAN architecture diagram
python lan_architecture.py
# Generate main diagram
python main.py
```
Output files will be generated in the current directory as PNG images.
## Available Diagrams
- `lan_architecture.py` - Home Lab / Auction Stack Architecture diagram
- `main.py` - Network architecture diagram
## Deployment
To deploy or share this project:
1. Ensure Graphviz is installed on the target system
2. Clone the repository
3. Follow the setup instructions above
4. Run the desired diagram script
## Notes
- Generated diagram files (`.png`, `.gv`) are excluded from version control
- The diagrams use a left-to-right (`LR`) layout direction
- Output files are saved with `show=False` to prevent automatic opening
# Network Architecture Diagrams
Python scripts to generate network architecture diagrams using the [Diagrams](https://diagrams.mingrammer.com/) library.
## Prerequisites
- Python 3.8+
- Graphviz ([installation guide](https://graphviz.org/download/))
### Installing Graphviz
**Windows:**
```bash
choco install graphviz
# or download from https://graphviz.org/download/
```
**macOS:**
```bash
brew install graphviz
```
**Linux:**
```bash
sudo apt-get install graphviz # Debian/Ubuntu
sudo yum install graphviz # RHEL/CentOS
```
## Setup
1. Create a virtual environment:
```bash
python -m venv .venv
```
2. Activate the virtual environment:
```bash
# Windows
.venv\Scripts\activate
# macOS/Linux
source .venv/bin/activate
```
3. Install dependencies:
```bash
pip install -r requirements.txt
```
## Usage
Run the diagram generation scripts:
```bash
# Generate LAN architecture diagram
python lan_architecture.py
# Generate main diagram
python main.py
```
Output files will be generated in the current directory as PNG images.
## Available Diagrams
- `lan_architecture.py` - Home Lab / Auction Stack Architecture diagram
- `main.py` - Network architecture diagram
## Deployment
To deploy or share this project:
1. Ensure Graphviz is installed on the target system
2. Clone the repository
3. Follow the setup instructions above
4. Run the desired diagram script
## Notes
- Generated diagram files (`.png`, `.gv`) are excluded from version control
- The diagrams use a left-to-right (`LR`) layout direction
- Output files are saved with `show=False` to prevent automatic opening

View File

@@ -1,28 +1,26 @@
version: '3.8'
services:
diagram:
build:
context: .
dockerfile: Dockerfile
container_name: diagram-viewer
restart: unless-stopped
networks:
- traefik_net
labels:
- "traefik.enable=true"
- "traefik.http.routers.diagram.rule=Host(`diagram.appmodel.nl`)"
- "traefik.http.routers.diagram.entrypoints=websecure"
- "traefik.http.routers.diagram.tls=true"
- "traefik.http.routers.diagram.tls.certresolver=letsencrypt"
- "traefik.http.services.diagram.loadbalancer.server.port=80"
# Optional: mount logs
volumes:
- ./logs:/var/log/nginx
# Optional: environment variables
environment:
- TZ=Europe/Amsterdam
networks:
traefik_net:
external: true
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

@@ -1,218 +1,218 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Home-Lab diagram Mermaid + Kroki (Graphviz)</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-size:1.1rem;font-weight:600}
.wrap{display:flex;height:calc(100vh - 40px)}
.panel{flex:1;display:flex;flex-direction:column;overflow:hidden;border-right:1px solid #ddd}
.panel:last-child{border:none}
h3{margin:.4rem .6rem;font-size:1rem}
.btn{margin:.2rem .6rem;padding:.3rem .6rem;border:1px solid #ccc;border-radius:4px;background:#fff;cursor:pointer}
.btn:hover{background:#eee}
#editor{flex:1}
.diagram{flex:2;background:#fff;padding:.5rem;overflow:auto}
svg{max-width:100%;height:auto}
/* mermaid theme tweaks */
.mermaid{height:100%;display:flex;align-items:center;justify-content:center}
</style>
<!-- ========= 1. LIBRARIES ========= -->
<!-- Monaco editor -->
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.44.0/min/vs/loader.js"></script>
<!-- Mermaid -->
<script src="https://cdn.jsdelivr.net/npm/mermaid@10.6.1/dist/mermaid.min.js"></script>
<!-- Kroki client (tiny wrapper) -->
<script src="https://cdn.jsdelivr.net/npm/kroki-client@1/dist/kroki-client.min.js"></script>
<!-- html2canvas for PNG export -->
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
</head>
<body>
<header>🏠 Home-Lab diagram editable in browser (localStorage auto-saved)</header>
<div class="wrap">
<!-- ---- LEFT: EDITOR ---- -->
<div class="panel">
<h3>Source (Mermaid syntax)</h3>
<div id="editor"></div>
<div style="padding:.4rem .6rem">
<button class="btn" onclick="render()">▶ Render both</button>
<button class="btn" onclick="savePNG()">💾 Save PNG</button>
<button class="btn" onclick="saveSVG()">💾 Save SVG</button>
<label class="btn">
<input type="checkbox" id="dark"> dark
</label>
</div>
</div>
<!-- ---- MIDDLE: MERMAID ---- -->
<div class="panel">
<h3>Mermaid render</h3>
<div id="mermaid" class="diagram"></div>
</div>
<!-- ---- RIGHT: KROKI / GRAPHVIZ ---- -->
<div class="panel">
<h3>Kroki (Graphviz) render</h3>
<div id="kroki" class="diagram"></div>
</div>
</div>
<script>
/* ========= 2. DEFAULT SOURCE ========= */
const DEFAULT = `flowchart TD
%% ---------- colours ----------
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<br/>hub.lan<br/>192.168.1.1}}:::router
subgraph LAN["🏠 LAN 192.168.1.0/24"]:::lan
subgraph CORE["💻 Core server<br/>192.168.1.159"]:::core
traefik[🚦 Traefik]:::core
gitea[📚 Gitea]:::core
dokku[🐳 Dokku]:::core
auction[🧱 Auction stack]:::core
mi50[🧠 MI50 / Ollama]:::core
end
subgraph INFRA["🧭 Infra & DNS<br/>192.168.1.163"]:::infra
adguard[🛡️ AdGuard]:::infra
artifactory[📦 Artifactory]:::infra
end
ha[🏡 Home Assistant<br/>192.168.1.193]:::infra
atlas[🧱 Atlas<br/>192.168.1.100]:::worker
iot1[📺 IoT-1]:::iot
iot2[📟 IoT-2]:::iot
end
subgraph TETHER["📶 Tether 192.168.137.0/24"]:::lan
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
`;
/* ========= 3. EDITOR SETUP ========= */
let editor;
require.config({paths:{vs:'https://cdn.jsdelivr.net/npm/monaco-editor@0.44.0/min/vs'}});
require(['vs/editor/editor.main'], () => {
editor = monaco.editor.create(document.getElementById('editor'), {
value: localStorage.getItem('diagramSrc') || DEFAULT,
language: 'markdown',
theme: 'vs-dark',
minimap: {enabled: false},
wordWrap: 'on'
});
editor.onDidChangeModelContent(() => {
localStorage.setItem('diagramSrc', editor.getValue());
});
render();
});
/* ========= 4. RENDERERS ========= */
mermaid.initialize({startOnLoad: false, theme: 'default'});
function render() {
const src = editor.getValue();
// 4a mermaid
document.getElementById('mermaid').innerHTML = '<div class="mermaid">'+src+'</div>';
mermaid.init();
// 4b kroki graphviz
Kroki.render('graphviz', dotFromMermaid(src)).then(svg => {
document.getElementById('kroki').innerHTML = svg;
}).catch(err => {
document.getElementById('kroki').innerHTML = '<pre>'+err+'</pre>';
});
}
/* crude converter good enough for this topology */
function dotFromMermaid(m) {
let dot = 'digraph G {\nbgcolor=transparent;rankdir=TB;node [shape=rect,style=rounded];\n';
const lines = m.split('\n');
let inSub = false;
lines.forEach(l => {
l = l.trim();
if (l.startsWith('subgraph')) {
const name = (l.match(/subgraph\s+(\w+)/) || [, 'cluster'])[1];
dot += 'subgraph '+name+' {\nlabel="'+l.split('"')[1]+'";\nstyle=filled;fillcolor=lightgrey;\n';
inSub = true;
} else if (l === 'end') {
dot += '}\n';
inSub = false;
} else if (l.includes('[') && l.includes(']')) {
const id = l.split('[')[0].trim();
const label = (l.match(/\[(.*?)\]/) || [, id])[1];
dot += id + ' [label="' + label.replace(/:::.*$/,'') + '"];\n';
} else if (l.includes('-->') || l.includes('==>') || l.includes('-.->')) {
const arr = l.replace(/[~=>\-.]+/g,'->').split('->').map(x=>x.trim()).filter(Boolean);
if (arr.length === 2) dot += arr[0] + ' -> ' + arr[1] + ';\n';
}
});
dot += '}';
return dot;
}
/* ========= 5. EXPORT ========= */
function savePNG() {
html2canvas(document.querySelector('#mermaid svg')).then(canvas => {
download(canvas.toDataURL(), 'home-lab.png');
});
}
function saveSVG() {
const svg = document.querySelector('#mermaid svg');
const url = 'data:image/svg+xml;charset=utf-8,'+encodeURIComponent(svg.outerHTML);
download(url, 'home-lab.svg');
}
function download(href, name) {
const a = Object.assign(document.createElement('a'), {href, download: name});
document.body.appendChild(a); a.click(); a.remove();
}
/* dark toggle */
document.getElementById('dark').onchange = e => {
document.body.style.background = e.target.checked ? '#121212' : '#f7f9fc';
document.body.style.color = e.target.checked ? '#eee' : '#222';
};
</script>
</body>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Home-Lab viewer Mermaid + Kroki (Graphviz)</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-size:1.1rem;font-weight:600}
.wrap{display:flex;height:calc(100vh - 40px)}
.panel{flex:1;display:flex;flex-direction:column;overflow:hidden;border-right:1px solid #ddd}
.panel:last-child{border:none}
h3{margin:.4rem .6rem;font-size:1rem}
.btn{margin:.2rem .6rem;padding:.3rem .6rem;border:1px solid #ccc;border-radius:4px;background:#fff;cursor:pointer}
.btn:hover{background:#eee}
#editor{flex:1}
.diagram{flex:2;background:#fff;padding:.5rem;overflow:auto}
svg{max-width:100%;height:auto}
/* mermaid theme tweaks */
.mermaid{height:100%;display:flex;align-items:center;justify-content:center}
</style>
<!-- ========= 1. LIBRARIES ========= -->
<!-- Monaco editor -->
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.44.0/min/vs/loader.js"></script>
<!-- Mermaid -->
<script src="https://cdn.jsdelivr.net/npm/mermaid@10.6.1/dist/mermaid.min.js"></script>
<!-- Kroki client (tiny wrapper) -->
<script src="https://cdn.jsdelivr.net/npm/kroki-client@1/dist/kroki-client.min.js"></script>
<!-- html2canvas for PNG export -->
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
</head>
<body>
<header>🏠 Home-Lab viewer editable in browser (localStorage auto-saved)</header>
<div class="wrap">
<!-- ---- LEFT: EDITOR ---- -->
<div class="panel">
<h3>Source (Mermaid syntax)</h3>
<div id="editor"></div>
<div style="padding:.4rem .6rem">
<button class="btn" onclick="render()">▶ Render both</button>
<button class="btn" onclick="savePNG()">💾 Save PNG</button>
<button class="btn" onclick="saveSVG()">💾 Save SVG</button>
<label class="btn">
<input type="checkbox" id="dark"> dark
</label>
</div>
</div>
<!-- ---- MIDDLE: MERMAID ---- -->
<div class="panel">
<h3>Mermaid render</h3>
<div id="mermaid" class="diagram"></div>
</div>
<!-- ---- RIGHT: KROKI / GRAPHVIZ ---- -->
<div class="panel">
<h3>Kroki (Graphviz) render</h3>
<div id="kroki" class="diagram"></div>
</div>
</div>
<script>
/* ========= 2. DEFAULT SOURCE ========= */
const DEFAULT = `flowchart TD
%% ---------- colours ----------
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<br/>hub.lan<br/>192.168.1.1}}:::router
subgraph LAN["🏠 LAN 192.168.1.0/24"]:::lan
subgraph CORE["💻 Core server<br/>192.168.1.159"]:::core
traefik[🚦 Traefik]:::core
gitea[📚 Gitea]:::core
dokku[🐳 Dokku]:::core
auction[🧱 Auction stack]:::core
mi50[🧠 MI50 / Ollama]:::core
end
subgraph INFRA["🧭 Infra & DNS<br/>192.168.1.163"]:::infra
adguard[🛡️ AdGuard]:::infra
artifactory[📦 Artifactory]:::infra
end
ha[🏡 Home Assistant<br/>192.168.1.193]:::infra
atlas[🧱 Atlas<br/>192.168.1.100]:::worker
iot1[📺 IoT-1]:::iot
iot2[📟 IoT-2]:::iot
end
subgraph TETHER["📶 Tether 192.168.137.0/24"]:::lan
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
`;
/* ========= 3. EDITOR SETUP ========= */
let editor;
require.config({paths:{vs:'https://cdn.jsdelivr.net/npm/monaco-editor@0.44.0/min/vs'}});
require(['vs/editor/editor.main'], () => {
editor = monaco.editor.create(document.getElementById('editor'), {
value: localStorage.getItem('diagramSrc') || DEFAULT,
language: 'markdown',
theme: 'vs-dark',
minimap: {enabled: false},
wordWrap: 'on'
});
editor.onDidChangeModelContent(() => {
localStorage.setItem('diagramSrc', editor.getValue());
});
render();
});
/* ========= 4. RENDERERS ========= */
mermaid.initialize({startOnLoad: false, theme: 'default'});
function render() {
const src = editor.getValue();
// 4a mermaid
document.getElementById('mermaid').innerHTML = '<div class="mermaid">'+src+'</div>';
mermaid.init();
// 4b kroki graphviz
Kroki.render('graphviz', dotFromMermaid(src)).then(svg => {
document.getElementById('kroki').innerHTML = svg;
}).catch(err => {
document.getElementById('kroki').innerHTML = '<pre>'+err+'</pre>';
});
}
/* crude converter good enough for this topology */
function dotFromMermaid(m) {
let dot = 'digraph G {\nbgcolor=transparent;rankdir=TB;node [shape=rect,style=rounded];\n';
const lines = m.split('\n');
let inSub = false;
lines.forEach(l => {
l = l.trim();
if (l.startsWith('subgraph')) {
const name = (l.match(/subgraph\s+(\w+)/) || [, 'cluster'])[1];
dot += 'subgraph '+name+' {\nlabel="'+l.split('"')[1]+'";\nstyle=filled;fillcolor=lightgrey;\n';
inSub = true;
} else if (l === 'end') {
dot += '}\n';
inSub = false;
} else if (l.includes('[') && l.includes(']')) {
const id = l.split('[')[0].trim();
const label = (l.match(/\[(.*?)\]/) || [, id])[1];
dot += id + ' [label="' + label.replace(/:::.*$/,'') + '"];\n';
} else if (l.includes('-->') || l.includes('==>') || l.includes('-.->')) {
const arr = l.replace(/[~=>\-.]+/g,'->').split('->').map(x=>x.trim()).filter(Boolean);
if (arr.length === 2) dot += arr[0] + ' -> ' + arr[1] + ';\n';
}
});
dot += '}';
return dot;
}
/* ========= 5. EXPORT ========= */
function savePNG() {
html2canvas(document.querySelector('#mermaid svg')).then(canvas => {
download(canvas.toDataURL(), 'home-lab.png');
});
}
function saveSVG() {
const svg = document.querySelector('#mermaid svg');
const url = 'data:image/svg+xml;charset=utf-8,'+encodeURIComponent(svg.outerHTML);
download(url, 'home-lab.svg');
}
function download(href, name) {
const a = Object.assign(document.createElement('a'), {href, download: name});
document.body.appendChild(a); a.click(); a.remove();
}
/* dark toggle */
document.getElementById('dark').onchange = e => {
document.body.style.background = e.target.checked ? '#121212' : '#f7f9fc';
document.body.style.color = e.target.checked ? '#eee' : '#222';
};
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

View File

@@ -1,283 +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>
<!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>

View File

@@ -1,121 +1,121 @@
from diagrams import Diagram, Cluster, Edge
from diagrams.onprem.network import Internet
from diagrams.onprem.compute import Server
from diagrams.onprem.iac import Ansible
from diagrams.generic.network import Router, Switch
from diagrams.generic.device import Mobile, Tablet
from diagrams.generic.blank import Blank
from diagrams.onprem.client import Users
from diagrams.onprem.container import Docker
from diagrams.custom import Custom
# Tip: run this in a venv:
# pip install diagrams graphviz
with Diagram("Home Lab / Auction Stack Architecture", show=False, filename="home_lab_architecture", direction="LR"):
internet = Internet("Internet / Cloud")
dev = Users("Dev laptop(s)\nWindows / WSL")
# -------------------- LAN 192.168.1.x --------------------
with Cluster("LAN 192.168.1.0/24"):
router = Router("hub.lan\n192.168.1.1\nRouter / Gateway")
# -------- Core server / desktop (Tour / hephaestus / dokku / ollama) --------
with Cluster("Core server / desktop\nTour / hephaestus / dokku.lan / ollama.lan\n192.168.1.159"):
core_os = Server("Ubuntu host")
traefik = Docker("Traefik\nReverse Proxy")
gitea = Docker("Gitea\n git.appmodel.nl")
dokku = Docker("Dokku\nPaaS / app hosting")
auction_fe = Docker("Auction Frontend\n auction.appmodel.nl")
aupi_be = Docker("Aupi API Backend\n aupi.appmodel.nl")
mi50 = Server("MI50 GPU / Ollama\nAI workloads")
# -------- Infra & DNS (odroid / dns.lan) --------
with Cluster("Infra & DNS\nodroid / dns.lan\n192.168.1.163"):
dns_host = Server("Odroid host")
adguard = Docker("AdGuard Home\nDNS / *.lan / *.appmodel.nl")
artifactory = Docker("Artifactory (future)")
runner = Docker("CI / Build runner (future)")
# -------- Home Assistant --------
with Cluster("Home Automation\nha.lan\n192.168.1.193"):
ha_host = Server("HomeAssistant host")
hass = Docker("Home Assistant")
# -------- Extra node / worker --------
atlas = Server("atlas.lan\n192.168.1.100\n(extra node / worker)")
# -------- IoT / devices --------
with Cluster("IoT / Clients"):
iot_hof = Tablet("hof-E402NA\n192.168.1.214")
iot_s380 = Tablet("S380HB\n192.168.1.59")
iot_ecb5 = Tablet("ecb5faa56c90\n192.168.1.49")
iot_unknown = Tablet("Unknown\n192.168.1.240")
# -------------------- Tether subnet 192.168.137.x --------------------
with Cluster("Tether subnet 192.168.137.0/24"):
hermes = Server("hermes.lan\n192.168.137.239\nworker / node")
plato = Server("plato.lan\n192.168.137.163\nworker / node")
# -------------------- Edges / flows --------------------
# Internet naar router + DNS host
internet >> router
internet >> dns_host
# Dev naar Gitea / Traefik / Dokku
dev >> Edge(label="git / HTTPS") >> traefik
dev >> Edge(label="SSH / HTTPS") >> gitea
dev >> Edge(label="Dokku deploys") >> dokku
# Router naar alle LAN-nodes
router >> core_os
router >> dns_host
router >> ha_host
router >> atlas
router >> iot_hof
router >> iot_s380
router >> iot_ecb5
router >> iot_unknown
# Core server services
core_os >> traefik
core_os >> gitea
core_os >> dokku
core_os >> auction_fe
core_os >> aupi_be
core_os >> mi50
# Infra/DNS services
dns_host >> adguard
dns_host >> artifactory
dns_host >> runner
# Home Assistant
ha_host >> hass
# DNS-queries
core_os >> Edge(label="DNS") >> adguard
ha_host >> Edge(label="DNS") >> adguard
atlas >> Edge(label="DNS") >> adguard
hermes >> Edge(label="DNS") >> adguard
plato >> Edge(label="DNS") >> adguard
# Web traffic / reverse proxy flows
internet >> Edge(label="HTTP/HTTPS") >> traefik
traefik >> Edge(label="git.appmodel.nl") >> gitea
traefik >> Edge(label="auction.appmodel.nl") >> auction_fe
traefik >> Edge(label="aupi.appmodel.nl") >> aupi_be
traefik >> Edge(label="dokku.lan / apps") >> dokku
# App-level flow
auction_fe >> Edge(label="REST API") >> aupi_be
# AI workloads
dev >> Edge(label="LLM / Tuning / Inference") >> mi50
# Tether workers verbonden met core (bv. jobs / agents)
core_os >> Edge(label="jobs / ssh") >> hermes
core_os >> Edge(label="jobs / ssh") >> plato
from diagrams import Diagram, Cluster, Edge
from diagrams.onprem.network import Internet
from diagrams.onprem.compute import Server
from diagrams.onprem.iac import Ansible
from diagrams.generic.network import Router, Switch
from diagrams.generic.device import Mobile, Tablet
from diagrams.generic.blank import Blank
from diagrams.onprem.client import Users
from diagrams.onprem.container import Docker
from diagrams.custom import Custom
# Tip: run this in a venv:
# pip install diagrams graphviz
with Diagram("Home Lab / Auction Stack Architecture", show=False, filename="home_lab_architecture", direction="LR"):
internet = Internet("Internet / Cloud")
dev = Users("Dev laptop(s)\nWindows / WSL")
# -------------------- LAN 192.168.1.x --------------------
with Cluster("LAN 192.168.1.0/24"):
router = Router("hub.lan\n192.168.1.1\nRouter / Gateway")
# -------- Core server / desktop (Tour / hephaestus / dokku / ollama) --------
with Cluster("Core server / desktop\nTour / hephaestus / dokku.lan / ollama.lan\n192.168.1.159"):
core_os = Server("Ubuntu host")
traefik = Docker("Traefik\nReverse Proxy")
gitea = Docker("Gitea\n git.appmodel.nl")
dokku = Docker("Dokku\nPaaS / app hosting")
auction_fe = Docker("Auction Frontend\n auction.appmodel.nl")
aupi_be = Docker("Aupi API Backend\n aupi.appmodel.nl")
mi50 = Server("MI50 GPU / Ollama\nAI workloads")
# -------- Infra & DNS (odroid / dns.lan) --------
with Cluster("Infra & DNS\nodroid / dns.lan\n192.168.1.163"):
dns_host = Server("Odroid host")
adguard = Docker("AdGuard Home\nDNS / *.lan / *.appmodel.nl")
artifactory = Docker("Artifactory (future)")
runner = Docker("CI / Build runner (future)")
# -------- Home Assistant --------
with Cluster("Home Automation\nha.lan\n192.168.1.193"):
ha_host = Server("HomeAssistant host")
hass = Docker("Home Assistant")
# -------- Extra node / worker --------
atlas = Server("atlas.lan\n192.168.1.100\n(extra node / worker)")
# -------- IoT / devices --------
with Cluster("IoT / Clients"):
iot_hof = Tablet("hof-E402NA\n192.168.1.214")
iot_s380 = Tablet("S380HB\n192.168.1.59")
iot_ecb5 = Tablet("ecb5faa56c90\n192.168.1.49")
iot_unknown = Tablet("Unknown\n192.168.1.240")
# -------------------- Tether subnet 192.168.137.x --------------------
with Cluster("Tether subnet 192.168.137.0/24"):
hermes = Server("hermes.lan\n192.168.137.239\nworker / node")
plato = Server("plato.lan\n192.168.137.163\nworker / node")
# -------------------- Edges / flows --------------------
# Internet naar router + DNS host
internet >> router
internet >> dns_host
# Dev naar Gitea / Traefik / Dokku
dev >> Edge(label="git / HTTPS") >> traefik
dev >> Edge(label="SSH / HTTPS") >> gitea
dev >> Edge(label="Dokku deploys") >> dokku
# Router naar alle LAN-nodes
router >> core_os
router >> dns_host
router >> ha_host
router >> atlas
router >> iot_hof
router >> iot_s380
router >> iot_ecb5
router >> iot_unknown
# Core server services
core_os >> traefik
core_os >> gitea
core_os >> dokku
core_os >> auction_fe
core_os >> aupi_be
core_os >> mi50
# Infra/DNS services
dns_host >> adguard
dns_host >> artifactory
dns_host >> runner
# Home Assistant
ha_host >> hass
# DNS-queries
core_os >> Edge(label="DNS") >> adguard
ha_host >> Edge(label="DNS") >> adguard
atlas >> Edge(label="DNS") >> adguard
hermes >> Edge(label="DNS") >> adguard
plato >> Edge(label="DNS") >> adguard
# Web traffic / reverse proxy flows
internet >> Edge(label="HTTP/HTTPS") >> traefik
traefik >> Edge(label="git.appmodel.nl") >> gitea
traefik >> Edge(label="auction.appmodel.nl") >> auction_fe
traefik >> Edge(label="aupi.appmodel.nl") >> aupi_be
traefik >> Edge(label="dokku.lan / apps") >> dokku
# App-level flow
auction_fe >> Edge(label="REST API") >> aupi_be
# AI workloads
dev >> Edge(label="LLM / Tuning / Inference") >> mi50
# Tether workers verbonden met core (bv. jobs / agents)
core_os >> Edge(label="jobs / ssh") >> hermes
core_os >> Edge(label="jobs / ssh") >> plato

318
main.py
View File

@@ -1,159 +1,159 @@
from graphviz import Digraph
def make_network_diagram(output_name: str = "network-architecture"):
g = Digraph(
"network",
filename=f"{output_name}.gv",
format="png",
)
# Globale stijl
g.attr(rankdir="LR", fontname="Segoe UI")
# ---------- WAN / Internet ----------
with g.subgraph(name="cluster_wan") as wan:
wan.attr(
label="🌐 Internet / Cloud",
style="rounded",
color="lightgrey",
fontsize="16",
)
wan.node("extDNS", "📡 Public DNS", shape="rectangle")
wan.node("extGit", "☁️ Externe registries / Git\n(GitHub / Docker Hub)", shape="rectangle")
# ---------- LAN 192.168.1.0/24 ----------
with g.subgraph(name="cluster_lan") as lan:
lan.attr(
label="🏠 LAN 192.168.1.0/24",
style="rounded",
color="lightgrey",
fontsize="16",
)
# Router / gateway
lan.node(
"hub",
"🛜 Router / Gateway\nhub.lan\n192.168.1.1",
shape="rectangle",
style="filled",
fillcolor="#f0f0ff",
)
# ---- Core server / desktop ----
with lan.subgraph(name="cluster_core") as core:
core.attr(
label="💻 Hoofdserver / Desktop\nTour / hephaestus / ollama / dokku.lan\n192.168.1.159",
style="rounded",
color="#aaccee",
)
core.node("traefik", "🚦 Traefik\nReverse Proxy", shape="rectangle")
core.node("gitea", "📚 Gitea\ngit.appmodel.nl", shape="rectangle")
core.node("dokku", "🐳 Dokku\nPaaS / build", shape="rectangle")
core.node("auctionFE", "🧱 Auction Frontend\nauction.appmodel.nl", shape="rectangle")
core.node("aupiAPI", "🧱 Auction Backend API\naupi.appmodel.nl", shape="rectangle")
core.node("mi50", "🧠 MI50 / Ollama\nAI workloads", shape="rectangle")
# Aanvulling: monitoring / logging
core.node("monitoring", "📈 Monitoring / Logging\nPrometheus / Loki / Grafana", shape="rectangle")
# ---- Infra & DNS ----
with lan.subgraph(name="cluster_infra_dns") as infra:
infra.attr(
label="🧭 Infra & DNS\nodroid / dns.lan\n192.168.1.163",
style="rounded",
color="#aaddaa",
)
infra.node("adguard", "🧭 AdGuard Home\nDNS / *.lan / *.appmodel.nl", shape="rectangle")
infra.node("artifactory", "📦 Artifactory", shape="rectangle")
infra.node("runner", "⚙️ Build runners\nCI/CD", shape="rectangle")
# ---- Home Automation ----
with lan.subgraph(name="cluster_ha") as ha:
ha.attr(
label="🏡 Home Automation\nha.lan\n192.168.1.193",
style="rounded",
color="#ffddaa",
)
ha.node("hass", "🏠 Home Assistant", shape="rectangle")
# Overige LAN-hosts / IoT
lan.node("atlas", "🧱 atlas.lan\n192.168.1.100", shape="rectangle")
lan.node("iot1", "📺 hof-E402NA\n192.168.1.214", shape="rectangle")
lan.node("iot2", "🎧 S380HB\n192.168.1.59", shape="rectangle")
lan.node("iot3", "📟 ecb5faa56c90\n192.168.1.49", shape="rectangle")
lan.node("iot4", "❓ Unknown\n192.168.1.240", shape="rectangle")
# ---------- Tether subnet ----------
with g.subgraph(name="cluster_tether") as tether:
tether.attr(
label="📶 Tether subnet 192.168.137.0/24",
style="rounded",
color="lightgrey",
fontsize="16",
)
tether.node("hermes", "🛰️ hermes.lan\n192.168.137.239\nworker / node", shape="rectangle")
tether.node("plato", "🛰️ plato.lan\n192.168.137.163\nworker / node", shape="rectangle")
# ---------- Externe gebruikers (aanvulling) ----------
g.node(
"users",
"👨‍💻 Developers / Users\nlaptops / mobiel",
shape="rectangle",
style="dashed",
)
# ==================== VERBINDINGEN ====================
# Basis LAN connecties (ongeveer jouw '---' links)
for target in ["core", "infraDNS", "ha", "atlas", "iot1", "iot2", "iot3", "iot4"]:
# We linken naar representatieve node binnen subgraph
if target == "core":
g.edge("hub", "traefik", dir="both", label="LAN")
elif target == "infraDNS":
g.edge("hub", "adguard", dir="both", label="LAN")
elif target == "ha":
g.edge("hub", "hass", dir="both", label="LAN")
else:
g.edge("hub", target, dir="both", label="LAN")
# WAN koppeling
g.edge("hub", "extDNS", label="📶 Internet")
g.edge("adguard", "extDNS", label="Upstream DNS")
# DNS-resolutie (core / ha / atlas / tether -> AdGuard)
for client in ["traefik", "hass", "atlas", "hermes", "plato"]:
g.edge(client, "adguard", label="DNS", style="dashed")
# Websites / reverse proxy
g.edge("extDNS", "traefik", label="DNS → Traefik")
g.edge("traefik", "gitea", label="HTTP(S)")
g.edge("traefik", "auctionFE", label="HTTP(S)")
g.edge("traefik", "aupiAPI", label="HTTP(S)")
g.edge("traefik", "dokku", label="Apps / Deploy")
# App flow
g.edge("auctionFE", "aupiAPI", label="API calls")
g.edge("aupiAPI", "adguard", label="DNS lookups", style="dashed")
# AI workloads
g.edge("traefik", "mi50", label="AI / inference", style="dotted")
# Tether workers
g.edge("traefik", "hermes", dir="both", label="Jobs / tasks")
g.edge("traefik", "plato", dir="both", label="Jobs / tasks")
# Monitoring / logging (aanvulling)
for observed in ["traefik", "gitea", "dokku", "auctionFE", "aupiAPI", "mi50", "adguard", "atlas", "hass"]:
g.edge(observed, "monitoring", style="dotted", label="metrics / logs")
# Developers / gebruikers
g.edge("users", "traefik", label="HTTPS")
g.edge("users", "gitea", style="dashed", label="Git / HTTP")
# Genereer bestanden (PNG + .gv)
g.render(cleanup=True)
print(f"Diagram gegenereerd als {output_name}.png")
if __name__ == "__main__":
make_network_diagram()
from graphviz import Digraph
def make_network_diagram(output_name: str = "network-architecture"):
g = Digraph(
"network",
filename=f"{output_name}.gv",
format="png",
)
# Globale stijl
g.attr(rankdir="LR", fontname="Segoe UI")
# ---------- WAN / Internet ----------
with g.subgraph(name="cluster_wan") as wan:
wan.attr(
label="🌐 Internet / Cloud",
style="rounded",
color="lightgrey",
fontsize="16",
)
wan.node("extDNS", "📡 Public DNS", shape="rectangle")
wan.node("extGit", "☁️ Externe registries / Git\n(GitHub / Docker Hub)", shape="rectangle")
# ---------- LAN 192.168.1.0/24 ----------
with g.subgraph(name="cluster_lan") as lan:
lan.attr(
label="🏠 LAN 192.168.1.0/24",
style="rounded",
color="lightgrey",
fontsize="16",
)
# Router / gateway
lan.node(
"hub",
"🛜 Router / Gateway\nhub.lan\n192.168.1.1",
shape="rectangle",
style="filled",
fillcolor="#f0f0ff",
)
# ---- Core server / desktop ----
with lan.subgraph(name="cluster_core") as core:
core.attr(
label="💻 Hoofdserver / Desktop\nTour / hephaestus / ollama / dokku.lan\n192.168.1.159",
style="rounded",
color="#aaccee",
)
core.node("traefik", "🚦 Traefik\nReverse Proxy", shape="rectangle")
core.node("gitea", "📚 Gitea\ngit.appmodel.nl", shape="rectangle")
core.node("dokku", "🐳 Dokku\nPaaS / build", shape="rectangle")
core.node("auctionFE", "🧱 Auction Frontend\nauction.appmodel.nl", shape="rectangle")
core.node("aupiAPI", "🧱 Auction Backend API\naupi.appmodel.nl", shape="rectangle")
core.node("mi50", "🧠 MI50 / Ollama\nAI workloads", shape="rectangle")
# Aanvulling: monitoring / logging
core.node("monitoring", "📈 Monitoring / Logging\nPrometheus / Loki / Grafana", shape="rectangle")
# ---- Infra & DNS ----
with lan.subgraph(name="cluster_infra_dns") as infra:
infra.attr(
label="🧭 Infra & DNS\nodroid / dns.lan\n192.168.1.163",
style="rounded",
color="#aaddaa",
)
infra.node("adguard", "🧭 AdGuard Home\nDNS / *.lan / *.appmodel.nl", shape="rectangle")
infra.node("artifactory", "📦 Artifactory", shape="rectangle")
infra.node("runner", "⚙️ Build runners\nCI/CD", shape="rectangle")
# ---- Home Automation ----
with lan.subgraph(name="cluster_ha") as ha:
ha.attr(
label="🏡 Home Automation\nha.lan\n192.168.1.193",
style="rounded",
color="#ffddaa",
)
ha.node("hass", "🏠 Home Assistant", shape="rectangle")
# Overige LAN-hosts / IoT
lan.node("atlas", "🧱 atlas.lan\n192.168.1.100", shape="rectangle")
lan.node("iot1", "📺 hof-E402NA\n192.168.1.214", shape="rectangle")
lan.node("iot2", "🎧 S380HB\n192.168.1.59", shape="rectangle")
lan.node("iot3", "📟 ecb5faa56c90\n192.168.1.49", shape="rectangle")
lan.node("iot4", "❓ Unknown\n192.168.1.240", shape="rectangle")
# ---------- Tether subnet ----------
with g.subgraph(name="cluster_tether") as tether:
tether.attr(
label="📶 Tether subnet 192.168.137.0/24",
style="rounded",
color="lightgrey",
fontsize="16",
)
tether.node("hermes", "🛰️ hermes.lan\n192.168.137.239\nworker / node", shape="rectangle")
tether.node("plato", "🛰️ plato.lan\n192.168.137.163\nworker / node", shape="rectangle")
# ---------- Externe gebruikers (aanvulling) ----------
g.node(
"users",
"👨‍💻 Developers / Users\nlaptops / mobiel",
shape="rectangle",
style="dashed",
)
# ==================== VERBINDINGEN ====================
# Basis LAN connecties (ongeveer jouw '---' links)
for target in ["core", "infraDNS", "ha", "atlas", "iot1", "iot2", "iot3", "iot4"]:
# We linken naar representatieve node binnen subgraph
if target == "core":
g.edge("hub", "traefik", dir="both", label="LAN")
elif target == "infraDNS":
g.edge("hub", "adguard", dir="both", label="LAN")
elif target == "ha":
g.edge("hub", "hass", dir="both", label="LAN")
else:
g.edge("hub", target, dir="both", label="LAN")
# WAN koppeling
g.edge("hub", "extDNS", label="📶 Internet")
g.edge("adguard", "extDNS", label="Upstream DNS")
# DNS-resolutie (core / ha / atlas / tether -> AdGuard)
for client in ["traefik", "hass", "atlas", "hermes", "plato"]:
g.edge(client, "adguard", label="DNS", style="dashed")
# Websites / reverse proxy
g.edge("extDNS", "traefik", label="DNS → Traefik")
g.edge("traefik", "gitea", label="HTTP(S)")
g.edge("traefik", "auctionFE", label="HTTP(S)")
g.edge("traefik", "aupiAPI", label="HTTP(S)")
g.edge("traefik", "dokku", label="Apps / Deploy")
# App flow
g.edge("auctionFE", "aupiAPI", label="API calls")
g.edge("aupiAPI", "adguard", label="DNS lookups", style="dashed")
# AI workloads
g.edge("traefik", "mi50", label="AI / inference", style="dotted")
# Tether workers
g.edge("traefik", "hermes", dir="both", label="Jobs / tasks")
g.edge("traefik", "plato", dir="both", label="Jobs / tasks")
# Monitoring / logging (aanvulling)
for observed in ["traefik", "gitea", "dokku", "auctionFE", "aupiAPI", "mi50", "adguard", "atlas", "hass"]:
g.edge(observed, "monitoring", style="dotted", label="metrics / logs")
# Developers / gebruikers
g.edge("users", "traefik", label="HTTPS")
g.edge("users", "gitea", style="dashed", label="Git / HTTP")
# Genereer bestanden (PNG + .gv)
g.render(cleanup=True)
print(f"Diagram gegenereerd als {output_name}.png")
if __name__ == "__main__":
make_network_diagram()

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/"
}
}

View File

@@ -1,107 +1,107 @@
<!doctype html>
<html lang="nl">
<head>
<meta charset="utf-8" />
<title>Netwerk architectuur</title>
<!-- Mermaid via CDN -->
<script type="module">
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
mermaid.initialize({ startOnLoad: true, theme: "default" });
</script>
<style>
body { font-family: system-ui, sans-serif; margin: 0; padding: 1rem; }
.mermaid { max-width: 100%; overflow: auto; }
</style>
</head>
<body>
<h1>Netwerk architectuur</h1>
<div class="mermaid">
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
</div>
</body>
</html>
<!doctype html>
<html lang="nl">
<head>
<meta charset="utf-8" />
<title>Netwerk architectuur</title>
<!-- Mermaid via CDN -->
<script type="module">
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
mermaid.initialize({ startOnLoad: true, theme: "default" });
</script>
<style>
body { font-family: system-ui, sans-serif; margin: 0; padding: 1rem; }
.mermaid { max-width: 100%; overflow: auto; }
</style>
</head>
<body>
<h1>Netwerk architectuur</h1>
<div class="mermaid">
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
</div>
</body>
</html>

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

View File

@@ -1,264 +1,266 @@
<!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>
<!-- 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

View File

@@ -1,2 +1,2 @@
diagrams==0.25.1
graphviz==0.20.3
diagrams==0.25.1
graphviz==0.20.3

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)

View File

@@ -1,192 +1,192 @@
# Deployment Guide - App Pipeline
Complete guide for deploying applications using the custom deployment pipeline with Gitea, Docker, and Traefik.
## 1. Create New Application
### 1.1 Gitea Repository
1. Log in to Gitea: https://git.appmodel.nl
2. Create a new repository:
- **Owner**: Tour
- **Name**: `viewer` (or your app name)
### 1.2 Generate Skeleton on Server
On the server, run:
```bash
apps-create viewer static-fe
```
This command:
- Sets up `/opt/apps/viewer` (attempts to clone from `git@git.appmodel.nl:Tour/viewer.git`)
- Generates a multi-stage Dockerfile for a Node-based static frontend
- Creates `~/infra/viewer/docker-compose.yml` with:
- Service `viewer`
- Connection to `traefik_net`
- Traefik route: `https://viewer.appmodel.nl`
## 2. Development & Build
On your development machine:
```bash
# Clone the repository
git clone git@git.appmodel.nl:Tour/viewer.git
cd viewer
# Build your app as usual
npm install
npm run build # output goes to dist/
# Commit and push
git add .
git commit -m "First version"
git push
```
**Note**: The Dockerfile expects a `dist/` directory containing static files.
## 3. Deployment Pipeline
### 3.1 Manual Deploy with `app-deploy`
On the server, use the generic deploy command:
```bash
app-deploy viewer
```
This command performs:
1. `cd /opt/apps/viewer`
2. `git pull --ff-only`
3. `cd /home/tour/infra/viewer`
4. `docker compose up -d --build viewer`
Logs are saved to: `/var/log/app-deploy-viewer.log`
### 3.2 Automatic Deployment via Gitea Hook
Set up automatic deployment on every push:
1. In Gitea, go to repository `Tour/viewer`
2. Navigate to **Settings → Git Hooks**
3. Select **post-receive**
4. Add the following script:
```bash
#!/usr/bin/env bash
/usr/local/bin/app-deploy viewer
```
**Result**: Every `git push` to `Tour/viewer` automatically triggers a deployment.
## 4. Traefik & DNS Configuration
### Traefik Setup
Traefik runs in the traefik stack on the same server and manages:
- `git.appmodel.nl`
- `auction.appmodel.nl`
- `aupi.appmodel.nl`
- ... (new apps via labels)
### Auto-Generated Labels
The `apps-create` command automatically adds the correct Traefik labels:
```yaml
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"
```
### DNS Configuration
Add a DNS record pointing to your server's public IP:
```
viewer.appmodel.nl → <server-public-ip>
```
Traefik + Let's Encrypt will automatically handle SSL certificate generation.
## 5. Future Application Types
The `apps-create` script currently supports:
- **`static-fe`** Node build → Nginx → static frontend
### Planned Types
Future app types can be added:
```bash
# Python API
apps-create stats api-py
# Background worker/crawler
apps-create crawler worker-py
```
Each type will have:
- Custom Dockerfile template
- Standard `docker-compose.yml` configuration
- Same deployment pipeline: `app-deploy <name>`
## Quick Reference
### Create and Deploy New App
```bash
# On server: create skeleton
apps-create viewer static-fe
# On dev machine: develop and push
git clone git@git.appmodel.nl:Tour/viewer.git
cd viewer
npm install
npm run build
git add .
git commit -m "Initial commit"
git push
# On server: deploy (or auto-deploys via hook)
app-deploy viewer
```
### Existing Infrastructure
| Service | URL | Description |
|---------|-----|-------------|
| Gitea | https://git.appmodel.nl | Git repository hosting |
| Auction | https://auction.appmodel.nl | Auction frontend |
| Aupi API | https://aupi.appmodel.nl | Auction backend API |
| Traefik | - | Reverse proxy & SSL |
### Log Locations
- Deployment logs: `/var/log/app-deploy-<app-name>.log`
- Docker logs: `docker compose logs -f <service-name>`
### Useful Commands
```bash
# View deployment logs
tail -f /var/log/app-deploy-viewer.log
# View container logs
cd ~/infra/viewer
docker compose logs -f viewer
# Restart service
docker compose restart viewer
# Rebuild without cache
docker compose up -d --build --no-cache viewer
```
# Deployment Guide - App Pipeline
Complete guide for deploying applications using the custom deployment pipeline with Gitea, Docker, and Traefik.
## 1. Create New Application
### 1.1 Gitea Repository
1. Log in to Gitea: https://git.appmodel.nl
2. Create a new repository:
- **Owner**: Tour
- **Name**: `viewer` (or your app name)
### 1.2 Generate Skeleton on Server
On the server, run:
```bash
apps-create viewer static-fe
```
This command:
- Sets up `/opt/apps/viewer` (attempts to clone from `git@git.appmodel.nl:Tour/viewer.git`)
- Generates a multi-stage Dockerfile for a Node-based static frontend
- Creates `~/infra/viewer/docker-compose.yml` with:
- Service `viewer`
- Connection to `traefik_net`
- Traefik route: `https://viewer.appmodel.nl`
## 2. Development & Build
On your development machine:
```bash
# Clone the repository
git clone git@git.appmodel.nl:Tour/viewer.git
cd viewer
# Build your app as usual
npm install
npm run build # output goes to dist/
# Commit and push
git add .
git commit -m "First version"
git push
```
**Note**: The Dockerfile expects a `dist/` directory containing static files.
## 3. Deployment Pipeline
### 3.1 Manual Deploy with `app-deploy`
On the server, use the generic deploy command:
```bash
app-deploy viewer
```
This command performs:
1. `cd /opt/apps/viewer`
2. `git pull --ff-only`
3. `cd /home/tour/infra/viewer`
4. `docker compose up -d --build viewer`
Logs are saved to: `/var/log/app-deploy-viewer.log`
### 3.2 Automatic Deployment via Gitea Hook
Set up automatic deployment on every push:
1. In Gitea, go to repository `Tour/viewer`
2. Navigate to **Settings → Git Hooks**
3. Select **post-receive**
4. Add the following script:
```bash
#!/usr/bin/env bash
/usr/local/bin/app-deploy viewer
```
**Result**: Every `git push` to `Tour/viewer` automatically triggers a deployment.
## 4. Traefik & DNS Configuration
### Traefik Setup
Traefik runs in the traefik stack on the same server and manages:
- `git.appmodel.nl`
- `auction.appmodel.nl`
- `aupi.appmodel.nl`
- ... (new apps via labels)
### Auto-Generated Labels
The `apps-create` command automatically adds the correct Traefik labels:
```yaml
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"
```
### DNS Configuration
Add a DNS record pointing to your server's public IP:
```
viewer.appmodel.nl → <server-public-ip>
```
Traefik + Let's Encrypt will automatically handle SSL certificate generation.
## 5. Future Application Types
The `apps-create` script currently supports:
- **`static-fe`** Node build → Nginx → static frontend
### Planned Types
Future app types can be added:
```bash
# Python API
apps-create stats api-py
# Background worker/crawler
apps-create crawler worker-py
```
Each type will have:
- Custom Dockerfile template
- Standard `docker-compose.yml` configuration
- Same deployment pipeline: `app-deploy <name>`
## Quick Reference
### Create and Deploy New App
```bash
# On server: create skeleton
apps-create viewer static-fe
# On dev machine: develop and push
git clone git@git.appmodel.nl:Tour/viewer.git
cd viewer
npm install
npm run build
git add .
git commit -m "Initial commit"
git push
# On server: deploy (or auto-deploys via hook)
app-deploy viewer
```
### Existing Infrastructure
| Service | URL | Description |
|---------|-----|-------------|
| Gitea | https://git.appmodel.nl | Git repository hosting |
| Auction | https://auction.appmodel.nl | Auction frontend |
| Aupi API | https://aupi.appmodel.nl | Auction backend API |
| Traefik | - | Reverse proxy & SSL |
### Log Locations
- Deployment logs: `/var/log/app-deploy-<app-name>.log`
- Docker logs: `docker compose logs -f <service-name>`
### Useful Commands
```bash
# View deployment logs
tail -f /var/log/app-deploy-viewer.log
# View container logs
cd ~/infra/viewer
docker compose logs -f viewer
# Restart service
docker compose restart viewer
# Rebuild without cache
docker compose up -d --build --no-cache viewer
```

File diff suppressed because it is too large Load Diff

View File

@@ -1,378 +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/diagram.git
cd diagram
# 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
```
diagram/
├── 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 diagram static-fe
# This creates:
# - /opt/apps/diagram (git repository)
# - /home/tour/infra/diagram/docker-compose.yml
```
#### 2. Configure Gitea Hook
In repository **Settings → Git Hooks → post-receive**:
```bash
#!/usr/bin/env bash
/usr/local/bin/app-deploy diagram
```
#### 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://diagram.appmodel.nl
### Manual Deployment
If needed:
```bash
# On server
app-deploy diagram
# Or step-by-step
cd /opt/apps/diagram
git pull
cd /home/tour/infra/diagram
docker compose up -d --build diagram
```
### Monitoring
```bash
# View deployment logs
tail -f /var/log/app-deploy-diagram.log
# View container logs
cd /home/tour/infra/diagram
docker compose logs -f diagram
# Check container status
docker compose ps
```
---
## Architecture Overview
### Infrastructure
```
┌─────────────────────────────────────────────┐
│ Production Architecture │
├─────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ │
│ │ Gitea │ (Source of Truth) │
│ │ Tour/diagram │ │
│ └──────┬───────┘ │
│ │ git push │
│ ↓ │
│ ┌──────────────┐ │
│ │ Post-Receive │ (Auto-trigger) │
│ │ Hook │ │
│ └──────┬───────┘ │
│ │ app-deploy diagram
│ ↓ │
│ ┌──────────────────────┐ │
│ │ /opt/apps/diagram/ │ │
│ │ (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 │
│ ↓ │
diagram.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://diagram.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-diagram.log
# Rebuild manually with verbose output
cd /home/tour/infra/diagram
docker compose build --no-cache --progress=plain diagram
```
#### Site Not Accessible
**Problem**: Container runs but site not reachable
**Solution**:
1. Check DNS: `nslookup diagram.appmodel.nl`
2. Verify Traefik labels: `docker inspect diagram-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/diagram
docker compose down
docker compose up -d --build --force-recreate diagram
# 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
# 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

View File

@@ -1,239 +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/diagram.git
cd diagram
```
### 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://diagram.appmodel.nl
```
### Manual Deployment
On the server:
```bash
# Trigger deployment
app-deploy diagram
# View logs
tail -f /var/log/app-deploy-diagram.log
# Check status
cd /home/tour/infra/diagram
docker compose ps
```
### View Logs
```bash
# Deployment logs
tail -f /var/log/app-deploy-diagram.log
# Container logs
cd /home/tour/infra/diagram
docker compose logs -f diagram
# Last 100 lines
docker compose logs --tail=100 diagram
```
### Restart Service
```bash
cd /home/tour/infra/diagram
# Restart container
docker compose restart diagram
# Full restart (rebuild)
docker compose down
docker compose up -d --build diagram
```
---
## 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 diagram-viewer | grep -A 10 Health
# Test endpoint
curl -I https://diagram.appmodel.nl
```
---
## Troubleshooting
### Build Fails
```bash
# Check logs
tail -f /var/log/app-deploy-diagram.log
# Rebuild with verbose output
cd /home/tour/infra/diagram
docker compose build --no-cache --progress=plain diagram
```
### Site Not Loading
```bash
# Check container
docker compose ps
# Check Traefik routing
docker logs traefik | grep diagram
# Test DNS
nslookup diagram.appmodel.nl
# Test directly
curl http://localhost:PORT
```
### Changes Not Visible
```bash
# Force rebuild
cd /home/tour/infra/diagram
docker compose up -d --build --force-recreate diagram
# 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://diagram.appmodel.nl) for live diagram editing
---
**Need Help?** Check the [Troubleshooting](Home.md#troubleshooting) section or review deployment logs.
# 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.

View File

@@ -1,361 +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/diagram/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 diagram repository
cd /c/vibe/diagram
# 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/diagram.wiki.git master --squash
# Push changes to wiki
git subtree push --prefix=wiki git@git.appmodel.nl:Tour/diagram.wiki.git master
# Pull changes from wiki
git subtree pull --prefix=wiki git@git.appmodel.nl:Tour/diagram.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/diagram.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/diagram
- 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/diagram.wiki.git
# Copy your wiki files
cd diagram.wiki
cp ../diagram/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/diagram/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 diagram
# Sync wiki (if wiki/ folder exists in repo)
if [ -d "/opt/apps/diagram/wiki" ]; then
cd /tmp
git clone git@git.appmodel.nl:Tour/diagram.wiki.git gitea-wiki-$$
cp /opt/apps/diagram/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 diagram project
cd /c/vibe/diagram
# 2. Clone the wiki repository (for viewer, adjust as needed)
cd ..
git clone git@git.appmodel.nl:Tour/diagram.wiki.git
# 3. Copy wiki files
cp diagram/wiki/*.md diagram.wiki/
# 4. Push to Gitea
cd diagram.wiki
git add .
git commit -m "Initialize wiki with deployment documentation"
git push origin master
# 5. Verify
# Open: https://git.appmodel.nl/Tour/diagram/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/diagram/wiki
cp *.md /c/vibe/diagram.wiki/
cd /c/vibe/diagram.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 ../diagram.wiki/ && cd ../diagram.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 ../diagram.wiki/
@cd ../diagram.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/diagram
2. Click "Wiki" tab
3. Click "Create New Page"
4. Create a dummy page (will be overwritten)
5. Now you can clone `diagram.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 diagram.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
# 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

View File

@@ -1,31 +1,31 @@
# Appmodel Home Lab Build & Deploy Pipeline
Dit document beschrijft hoe een nieuwe applicatie in het home lab wordt aangemaakt en hoe de build- & deploy-pipeline werkt.
## Overzicht
- **Broncode**: Gitea (`https://git.appmodel.nl`)
- **Build & runtime**: Docker containers op netwerk `traefik_net`
- **Routing & TLS**: Traefik (`https://traefik.appmodel.nl`)
- **Automatische deploy**: Gitea `post-receive` hooks → `app-deploy <app>`
## Pipeline in één diagram
```mermaid
flowchart LR
Dev[💻 Dev machine\nVS Code / Git] -->|git push| Gitea[📚 Gitea\nTour/<app>]
subgraph Server[🏠 Home lab server\n192.168.1.159]
Gitea --> Hook[🔔 post-receive hook\n/app-deploy <app>]
Hook --> Deploy[⚙️ app-deploy <app>\n/git pull + docker compose up -d --build <app>]
subgraph Docker[🐳 Docker / traefik_net]
AppC[🧱 App container\n<app>.appmodel.nl]
Traefik[🚦 Traefik\nReverse Proxy]
end
end
Deploy --> AppC
Traefik --> AppC
Client[🌐 Browser / API client] -->|https://<app>.appmodel.nl| Traefik
# Appmodel Home Lab Build & Deploy Pipeline
Dit document beschrijft hoe een nieuwe applicatie in het home lab wordt aangemaakt en hoe de build- & deploy-pipeline werkt.
## Overzicht
- **Broncode**: Gitea (`https://git.appmodel.nl`)
- **Build & runtime**: Docker containers op netwerk `traefik_net`
- **Routing & TLS**: Traefik (`https://traefik.appmodel.nl`)
- **Automatische deploy**: Gitea `post-receive` hooks → `app-deploy <app>`
## Pipeline in één diagram
```mermaid
flowchart LR
Dev[💻 Dev machine\nVS Code / Git] -->|git push| Gitea[📚 Gitea\nTour/<app>]
subgraph Server[🏠 Home lab server\n192.168.1.159]
Gitea --> Hook[🔔 post-receive hook\n/app-deploy <app>]
Hook --> Deploy[⚙️ app-deploy <app>\n/git pull + docker compose up -d --build <app>]
subgraph Docker[🐳 Docker / traefik_net]
AppC[🧱 App container\n<app>.appmodel.nl]
Traefik[🚦 Traefik\nReverse Proxy]
end
end
Deploy --> AppC
Traefik --> AppC
Client[🌐 Browser / API client] -->|https://<app>.appmodel.nl| Traefik

View File

@@ -1,141 +1,141 @@
1. Nieuwe app aanmaken
1.1 Gitea repo
Log in op Gitea: https://git.appmodel.nl
Maak een nieuwe repo aan, bijvoorbeeld:
Owner: Tour
Name: viewer
1.2 Skeleton genereren op de server
Op de server:
apps-create viewer static-fe
Dit doet:
/opt/apps/viewer klaarzetten (proberen te clonen uit git@git.appmodel.nl:Tour/viewer.git)
een multi-stage Dockerfile voor een Node-based static frontend aanmaken
~/infra/viewer/docker-compose.yml aanmaken met:
service viewer
koppeling aan traefik_net
Traefik route: https://viewer.appmodel.nl
2. Develop & build
Op je dev machine:
git clone git@git.appmodel.nl:Tour/viewer.git
cd viewer
# bouw je app zoals normaal
npm install
npm run build # output in dist/
git add .
git commit -m "First version"
git push
De Dockerfile verwacht een dist/ map met static files.
3. Deploy pipeline
3.1 app-deploy <app>
Op de server verzorgt app-deploy de generieke deploy:
app-deploy viewer
Doet:
cd /opt/apps/viewer
git pull --ff-only
cd /home/tour/infra/viewer
docker compose up -d --build viewer
Logs komen in:
/var/log/app-deploy-viewer.log
3.2 Automatisch deployen via Gitea hook
In Gitea (repo Tour/viewer):
Ga naar Instellingen → Git Hooks
Kies post-receive
Gebruik:
#!/usr/bin/env bash
/usr/local/bin/app-deploy viewer
Vanaf nu:
Elke git push naar Tour/viewer triggert automatisch een deploy.
4. Traefik & DNS
Traefik draait in de traefik stack op dezelfde server en beheert:
git.appmodel.nl
auction.appmodel.nl
aupi.appmodel.nl
… (nieuwe apps via labels)
Nieuwe app viewer krijgt via apps-create al de juiste labels:
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"
Je moet alleen in DNS nog een record maken:
viewer.appmodel.nl → publieke IP van de server
Traefik + Lets Encrypt regelen het certificaat automatisch.
5. Nieuwe app types (toekomst)
Het apps-create script ondersteunt nu:
static-fe Node build → Nginx → static frontend
Later kun je extra types toevoegen, bijvoorbeeld:
api-py Python (Flask/FastAPI) API
worker-py background worker / crawler
Door per type een eigen Dockerfile-sjabloon en standaard docker-compose.yml te genereren, wordt een nieuw project neerzetten:
apps-create stats api-py
apps-create crawler worker-py
en blijft de pipeline (app-deploy <naam>) identiek.
Je kunt nu:
apps-create viewer static-fe
1. Nieuwe app aanmaken
1.1 Gitea repo
Log in op Gitea: https://git.appmodel.nl
Maak een nieuwe repo aan, bijvoorbeeld:
Owner: Tour
Name: viewer
1.2 Skeleton genereren op de server
Op de server:
apps-create viewer static-fe
Dit doet:
/opt/apps/viewer klaarzetten (proberen te clonen uit git@git.appmodel.nl:Tour/viewer.git)
een multi-stage Dockerfile voor een Node-based static frontend aanmaken
~/infra/viewer/docker-compose.yml aanmaken met:
service viewer
koppeling aan traefik_net
Traefik route: https://viewer.appmodel.nl
2. Develop & build
Op je dev machine:
git clone git@git.appmodel.nl:Tour/viewer.git
cd viewer
# bouw je app zoals normaal
npm install
npm run build # output in dist/
git add .
git commit -m "First version"
git push
De Dockerfile verwacht een dist/ map met static files.
3. Deploy pipeline
3.1 app-deploy <app>
Op de server verzorgt app-deploy de generieke deploy:
app-deploy viewer
Doet:
cd /opt/apps/viewer
git pull --ff-only
cd /home/tour/infra/viewer
docker compose up -d --build viewer
Logs komen in:
/var/log/app-deploy-viewer.log
3.2 Automatisch deployen via Gitea hook
In Gitea (repo Tour/viewer):
Ga naar Instellingen → Git Hooks
Kies post-receive
Gebruik:
#!/usr/bin/env bash
/usr/local/bin/app-deploy viewer
Vanaf nu:
Elke git push naar Tour/viewer triggert automatisch een deploy.
4. Traefik & DNS
Traefik draait in de traefik stack op dezelfde server en beheert:
git.appmodel.nl
auction.appmodel.nl
aupi.appmodel.nl
… (nieuwe apps via labels)
Nieuwe app viewer krijgt via apps-create al de juiste labels:
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"
Je moet alleen in DNS nog een record maken:
viewer.appmodel.nl → publieke IP van de server
Traefik + Lets Encrypt regelen het certificaat automatisch.
5. Nieuwe app types (toekomst)
Het apps-create script ondersteunt nu:
static-fe Node build → Nginx → static frontend
Later kun je extra types toevoegen, bijvoorbeeld:
api-py Python (Flask/FastAPI) API
worker-py background worker / crawler
Door per type een eigen Dockerfile-sjabloon en standaard docker-compose.yml te genereren, wordt een nieuw project neerzetten:
apps-create stats api-py
apps-create crawler worker-py
en blijft de pipeline (app-deploy <naam>) identiek.
Je kunt nu:
apps-create viewer static-fe
app-deploy viewer

View File

@@ -1,378 +0,0 @@
# 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/diagram.git
cd diagram
# 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
```
diagram/
├── 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 diagram static-fe
# This creates:
# - /opt/apps/diagram (git repository)
# - /home/tour/infra/diagram/docker-compose.yml
```
#### 2. Configure Gitea Hook
In repository **Settings → Git Hooks → post-receive**:
```bash
#!/usr/bin/env bash
/usr/local/bin/app-deploy diagram
```
#### 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://diagram.appmodel.nl
### Manual Deployment
If needed:
```bash
# On server
app-deploy diagram
# Or step-by-step
cd /opt/apps/diagram
git pull
cd /home/tour/infra/diagram
docker compose up -d --build diagram
```
### Monitoring
```bash
# View deployment logs
tail -f /var/log/app-deploy-diagram.log
# View container logs
cd /home/tour/infra/diagram
docker compose logs -f diagram
# Check container status
docker compose ps
```
---
## Architecture Overview
### Infrastructure
```
┌─────────────────────────────────────────────┐
│ Production Architecture │
├─────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ │
│ │ Gitea │ (Source of Truth) │
│ │ Tour/diagram │ │
│ └──────┬───────┘ │
│ │ git push │
│ ↓ │
│ ┌──────────────┐ │
│ │ Post-Receive │ (Auto-trigger) │
│ │ Hook │ │
│ └──────┬───────┘ │
│ │ app-deploy diagram │
│ ↓ │
│ ┌──────────────────────┐ │
│ │ /opt/apps/diagram/ │ │
│ │ (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 │
│ ↓ │
│ diagram.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://diagram.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-diagram.log
# Rebuild manually with verbose output
cd /home/tour/infra/diagram
docker compose build --no-cache --progress=plain diagram
```
#### Site Not Accessible
**Problem**: Container runs but site not reachable
**Solution**:
1. Check DNS: `nslookup diagram.appmodel.nl`
2. Verify Traefik labels: `docker inspect diagram-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/diagram
docker compose down
docker compose up -d --build --force-recreate diagram
# 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

View File

@@ -1,239 +0,0 @@
# 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/diagram.git
cd diagram
```
### 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://diagram.appmodel.nl
```
### Manual Deployment
On the server:
```bash
# Trigger deployment
app-deploy diagram
# View logs
tail -f /var/log/app-deploy-diagram.log
# Check status
cd /home/tour/infra/diagram
docker compose ps
```
### View Logs
```bash
# Deployment logs
tail -f /var/log/app-deploy-diagram.log
# Container logs
cd /home/tour/infra/diagram
docker compose logs -f diagram
# Last 100 lines
docker compose logs --tail=100 diagram
```
### Restart Service
```bash
cd /home/tour/infra/diagram
# Restart container
docker compose restart diagram
# Full restart (rebuild)
docker compose down
docker compose up -d --build diagram
```
---
## 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 diagram-viewer | grep -A 10 Health
# Test endpoint
curl -I https://diagram.appmodel.nl
```
---
## Troubleshooting
### Build Fails
```bash
# Check logs
tail -f /var/log/app-deploy-diagram.log
# Rebuild with verbose output
cd /home/tour/infra/diagram
docker compose build --no-cache --progress=plain diagram
```
### Site Not Loading
```bash
# Check container
docker compose ps
# Check Traefik routing
docker logs traefik | grep diagram
# Test DNS
nslookup diagram.appmodel.nl
# Test directly
curl http://localhost:PORT
```
### Changes Not Visible
```bash
# Force rebuild
cd /home/tour/infra/diagram
docker compose up -d --build --force-recreate diagram
# 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://diagram.appmodel.nl) for live diagram editing
---
**Need Help?** Check the [Troubleshooting](Home.md#troubleshooting) section or review deployment logs.