add element web

This commit is contained in:
Aron Petau 2025-11-05 14:55:11 +01:00
parent 98417edd8b
commit 000ec5d25f
9 changed files with 209 additions and 236 deletions

150
README.md
View file

@ -1,6 +1,6 @@
# Studio EinsZwoVier Web Application # studio einszwovier Web Application
**FastAPI-based PDF print cost calculator** with automated ink coverage analysis, Matrix integration, and multi-service hub for Studio EinsZwoVier Maker Space. **FastAPI-based PDF print cost calculator** with automated ink coverage analysis, Matrix integration, and multi-service hub for studio einszwovier Maker Space.
[![Docker](https://img.shields.io/badge/Docker-Ready-2496ED?logo=docker)](https://www.docker.com/) [![Docker](https://img.shields.io/badge/Docker-Ready-2496ED?logo=docker)](https://www.docker.com/)
[![FastAPI](https://img.shields.io/badge/FastAPI-0.104+-009688?logo=fastapi)](https://fastapi.tiangolo.com/) [![FastAPI](https://img.shields.io/badge/FastAPI-0.104+-009688?logo=fastapi)](https://fastapi.tiangolo.com/)
@ -33,100 +33,84 @@
```mermaid ```mermaid
graph TB graph TB
subgraph "Studio EinsZwoVier Web App (Port 80)" subgraph "studio einszwovier Web Platform"
LP[🏠 Landing Page<br/>studio-einszwovier.local] LP[🏠 Landing Page<br/>Port 80]
About[ About Page<br/>/about<br/>Courses + Team Info] About[ About Page<br/>/about]
Cost[💰 Cost Calculator<br/>/cost<br/>PDF Analysis] CostCalc[💰 Cost Calculator<br/>/cost]
LP --> About LP --> About
LP --> Cost LP --> CostCalc
end end
subgraph "Course Management" subgraph "Services Accessible from Landing Page"
CSV[📝 courses.csv<br/>Title, Description, Dates, Image] BookStack[<5B> BookStack<br/>Port 6875<br/>Wissenssammlung]
Images[🖼️ Course Images<br/>static/images/courses/] OpenWebUI[🤖 Open WebUI<br/>Port 8080<br/>LLM Chatbot]
Modal[🔍 Image Modal<br/>Click to Enlarge] JupyterHub[<5B> JupyterHub<br/>Port 8001<br/>Python Notebooks]
Forgejo[🦊 Forgejo<br/>Port 3003<br/>Git Server]
About --> CSV ElementWeb[<5B> Element Web<br/>Port 8082<br/>Matrix Chat]
CSV --> Images Portainer[<5B> Portainer<br/>Port 9000<br/>Admin Panel]
Images --> Modal
LP -.->|Link| BookStack
LP -.->|Link| OpenWebUI
LP -.->|Link| JupyterHub
LP -.->|Link| Forgejo
LP -.->|Link| ElementWeb
LP -.->|Link| Portainer
end end
subgraph "PDF Processing Pipeline" subgraph "Backend Services"
Upload[📤 Upload PDF] Synapse[<5B> Synapse<br/>Port 8008<br/>Matrix Server]
Analysis[🔬 Analyze<br/>- Dimensions<br/>- Ink Coverage<br/>- Color Detection] Ollama[🔮 Ollama<br/>Port 11434<br/>LLM Engine]
Quote[📋 Quote Generation<br/>Per-page Breakdown] MariaDB[(<28> MariaDB<br/>BookStack DB)]
Cost --> Upload ElementWeb --> Synapse
Upload --> Analysis
Analysis --> Quote
end
subgraph "Matrix Integration"
MatrixRoom[💬 Matrix Room<br/>Print Orders]
PDF_Upload[📎 PDF Attachment]
Summary[📊 Order Summary<br/>German Language]
Quote --> MatrixRoom
Quote --> PDF_Upload
Quote --> Summary
end
subgraph "Integrated Services"
BookStack[📚 BookStack Wiki<br/>:6875]
Ollama[🤖 Ollama LLM<br/>:11434]
OpenWebUI[💭 Open WebUI<br/>:8080]
Portainer[🐳 Portainer<br/>:9000]
Synapse[📨 Matrix Synapse<br/>:8008]
JupyterHub[📓 JupyterHub<br/>:8001<br/>Drone Programming]
Forgejo[🦊 Forgejo Git<br/>:3003<br/>Code Collaboration]
LP -.-> BookStack
LP -.-> OpenWebUI
LP -.-> Portainer
LP -.-> JupyterHub
LP -.-> Forgejo
OpenWebUI --> Ollama OpenWebUI --> Ollama
MatrixRoom --> Synapse BookStack --> MariaDB
CostCalc --> Synapse
end end
subgraph "Educational Stack" subgraph "Educational Resources"
Notebooks[📒 Jupyter Notebooks<br/>Python Drone Code] Notebooks[📒 Drone Programming<br/>djitellopy Package]
GitRepos[📦 Git Repositories<br/>Project Source Code] GitRepos[📦 Code Repositories<br/>Project Source]
DronePackages[🚁 djitellopy<br/>Tello SDK] Knowledge[<5B> Documentation<br/>Guides & Tutorials]
JupyterHub --> Notebooks JupyterHub --> Notebooks
Forgejo --> GitRepos Forgejo --> GitRepos
Notebooks -.-> DronePackages BookStack --> Knowledge
end end
subgraph "Data Persistence" subgraph "Persistent Storage"
Uploads[📁 data/uploads/<br/>PDF Files] PDFUploads[📁 PDF Files<br/>data/uploads/]
Courses[📋 data/courses.csv<br/>Course List] CoursesCSV[📋 Courses CSV<br/>data/courses.csv]
MatrixDB[(🗄️ Matrix DB<br/>homeserver.db)] MatrixData[(🗄️ Matrix Data<br/>matrix/data/)]
BookDB[(🗄️ BookStack DB<br/>MariaDB)] JupyterVols[(💾 Jupyter Volumes<br/>User Workspaces)]
JupyterVolumes[(💾 Jupyter Volumes<br/>User Workspaces)] ForgejoData[(📚 Forgejo Data<br/>forgejo/data/)]
ForgejoData[(📚 Forgejo Data<br/>Git Repos)]
CostCalc --> PDFUploads
Upload --> Uploads About --> CoursesCSV
CSV --> Courses Synapse --> MatrixData
Synapse --> MatrixDB JupyterHub --> JupyterVols
BookStack --> BookDB
JupyterHub --> JupyterVolumes
Forgejo --> ForgejoData Forgejo --> ForgejoData
end end
subgraph "Infrastructure"
Watchtower[🔄 Watchtower<br/>Auto-Updates<br/>Every 24h]
Network[🌐 einszwovier_network<br/>Docker Network]
end
classDef webapp fill:#e3f2fd,stroke:#1976d2,stroke-width:2px classDef webapp fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
classDef service fill:#fff3e0,stroke:#f57c00,stroke-width:2px classDef service fill:#fff3e0,stroke:#f57c00,stroke-width:2px
classDef data fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px classDef backend fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
classDef matrix fill:#e8f5e9,stroke:#388e3c,stroke-width:2px classDef storage fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
classDef edu fill:#fce4ec,stroke:#c2185b,stroke-width:2px classDef edu fill:#fce4ec,stroke:#c2185b,stroke-width:2px
classDef infra fill:#fafafa,stroke:#616161,stroke-width:2px
class LP,About,Cost,Upload,Analysis,Quote webapp class LP,About,CostCalc webapp
class BookStack,Ollama,OpenWebUI,Portainer,Synapse,JupyterHub,Forgejo service class BookStack,OpenWebUI,JupyterHub,Forgejo,ElementWeb,Portainer service
class Uploads,Courses,MatrixDB,BookDB,JupyterVolumes,ForgejoData data class Synapse,Ollama,MariaDB backend
class MatrixRoom,PDF_Upload,Summary,CSV,Images,Modal matrix class PDFUploads,CoursesCSV,MatrixData,JupyterVols,ForgejoData storage
class Notebooks,GitRepos,DronePackages edu class Notebooks,GitRepos,Knowledge edu
class Watchtower,Network infra
``` ```
--- ---
@ -380,7 +364,7 @@ docker compose up --build
### Project Structure ### Project Structure
``` ```zsh
. .
├── main.py # FastAPI application ├── main.py # FastAPI application
├── cost_calculator.py # PDF analysis logic ├── cost_calculator.py # PDF analysis logic
@ -417,7 +401,7 @@ docker compose up --build
## 🤝 Contributing ## 🤝 Contributing
Contributions are welcome! This is part of **Studio EinsZwoVier**, a collaborative Maker Space. Contributions are welcome! This is part of **studio einszwovier**, a collaborative Maker Space.
1. Fork the repository 1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`) 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
@ -429,13 +413,13 @@ Contributions are welcome! This is part of **Studio EinsZwoVier**, a collaborati
## 📄 License ## 📄 License
This project is part of Studio EinsZwoVier educational initiative. This project is part of studio einszwovier educational initiative.
--- ---
## 📞 Contact ## 📞 Contact
**Studio EinsZwoVier** **studio einszwovier**
Gabriele-von-Bülow-Gymnasium Gabriele-von-Bülow-Gymnasium
Tile-Brügge-Weg 63, 13509 Berlin (Tegel) Tile-Brügge-Weg 63, 13509 Berlin (Tegel)
@ -452,8 +436,8 @@ Tile-Brügge-Weg 63, 13509 Berlin (Tegel)
- Ollama team for local LLM support - Ollama team for local LLM support
- BookStack for the documentation platform - BookStack for the documentation platform
- FastAPI community - FastAPI community
- All contributors and students of Studio EinsZwoVier - All contributors and students of studio einszwovier
--- ---
**Made with ❤️ at Studio EinsZwoVier Maker Space** Made with ❤️ at studio einszwovier Maker Space

View file

@ -1,147 +0,0 @@
# Kurse verwalten
## Kurse bearbeiten
Die aktuellen Kurse werden aus der Datei `courses.csv` geladen und automatisch auf der "Über uns" Seite angezeigt.
### CSV-Format
Die Datei hat fünf Spalten (alle außer `title` sind optional):
- `title` - Der Titel des Kurses (erforderlich)
- `description` - Eine kurze Beschreibung (optional)
- `dates` - Termine und Zeiten (optional)
- `offen_fuer` - Zielgruppe/Altersgruppe (optional)
- `image` - Pfad zum Kursbild (optional)
**Beispiel:**
```csv
title,description,dates,offen_fuer,image
Löten und Leuchten,Herstellung von Nachttischleuchten mit 3D-Design und Löttechnik,"Di 15.10. 14:00-16:00",Klasse 7-9,/static/images/courses/loeten.jpg
Die Vogelvilla,Bau von Vogelhäusern mit Lasercutter und Holzbearbeitung,"Mi 23.10. 13:00-15:00",alle Schüler:innen,
Robotik Intro,,,ab Klasse 5,/static/images/courses/robotik.jpg
```
### Kursbilder hinzufügen
1. Speichere das Bild in `/static/images/courses/`
2. Verwende den Pfad `/static/images/courses/dein-bild.jpg` in der CSV
3. Empfohlene Bildgröße: mindestens 640x400 Pixel
4. Unterstützte Formate: JPG, PNG, WebP
**Tipp:** Wenn kein Bild angegeben ist, wird der Kurs ohne Bild angezeigt (nur Text).### Felder im Detail
#### `title` (erforderlich)
Der Kursname - wird immer angezeigt.
#### `description` (optional)
Kurzbeschreibung des Kurses. Wird nur angezeigt, wenn vorhanden.
#### `dates` (optional)
Termine und Uhrzeiten. Kann mehrere Termine enthalten:
- Einzelner Termin: `Mi 23.10. 13:00-15:00`
- Mehrere Termine: `Di 15.10. 14:00-16:00, Do 17.10. 14:00-16:00`
- Bei Terminen mit Kommas: In Anführungszeichen setzen
#### `offen_fuer` (optional)
Freitextfeld für die Zielgruppe:
- `Klasse 7-9`
- `alle Schüler:innen`
- `ab 14 Jahren`
- `Oberstufe`
### Kurs hinzufügen
Einfach eine neue Zeile am Ende der Datei hinzufügen:
```csv
title,description,dates,offen_fuer
Löten und Leuchten,Herstellung von Nachttischleuchten mit 3D-Design und Löttechnik,"Di 15.10. 14:00-16:00, Do 17.10. 14:00-16:00",Klasse 7-9
Neuer Kurs,Beschreibung des neuen Kurses,"Mo 20.10. 15:00-17:00",Klasse 8-10
```
### Fehlende Informationen
Alle Felder außer `title` sind optional. Beispiele:
**Nur Titel und Beschreibung:**
```csv
title,description,dates,offen_fuer
Workshop XYZ,Toller Workshop über Making,,
```
**Nur Titel und Termine:**
```csv
title,description,dates,offen_fuer
Workshop ABC,,"Fr 25.10. 14:00",
```
**Nur Titel und Zielgruppe:**
```csv
title,description,dates,offen_fuer
Workshop 123,,,Klasse 9-10
```
### Kurs entfernen
Einfach die entsprechende Zeile löschen.
### Wichtig
- **Keine Anführungszeichen** verwenden, außer der Text enthält ein Komma
- **Keine Zeilenumbrüche** innerhalb der Beschreibung
- Die erste Zeile (`title,description,dates,offen_fuer,image`) muss erhalten bleiben
- Nach dem Speichern wird die Änderung sofort auf der Website sichtbar
### Darstellung auf der Website
Die Kurse werden in einem **Grid-Layout** mit **Karten-Design** angezeigt:
- **Große, fette Titel** (1.5em) mit pinker Unterstreichung
- **Hover-Effekt**: Karten heben sich beim Überfahren an
- **Responsive**: Auf Mobilgeräten eine Spalte, auf Desktop mehrere Spalten
- **Kursbilder** (optional): 200px hoch, oben auf der Karte
- **Metadata** am unteren Rand: Datum und Zielgruppe mit Icons
**Beispiel mit Bild:**
```
┌─────────────────────────────┐
│ [Kursbild 640x400] │
├─────────────────────────────┤
│ Löten und Leuchten │ ← 1.5em, fett, pink unterstrichen
│ ───────────────────── │
│ │
│ Herstellung von Nachtisch- │ ← Beschreibung
│ leuchten mit 3D-Design │
│ │
├─────────────────────────────┤
│ 📅 Dez. '24 │ ← Metadata mit Icons
│ 👥 Klasse 5-6 │
└─────────────────────────────┘
```
**Beispiel ohne Bild:**
```
┌─────────────────────────────┐
│ Textiles Plotten │ ← Direkt der Titel
│ ───────────────── │
│ │
│ Erschaffe deine eigenen │
│ Klamottendesigns │
│ │
├─────────────────────────────┤
│ 📅 Mai '25 │
│ 👥 Jhg. 9 │
└─────────────────────────────┘
```
### Live-Update
Die Kurse werden bei jedem Seitenaufruf neu geladen. Es ist **kein Neustart** des Servers erforderlich!
### Keine Kurse anzeigen
Wenn keine Kurse stattfinden, einfach alle Zeilen außer der Kopfzeile löschen:
```csv
title,description
```
Die Seite zeigt dann: "Aktuell sind keine Kurse geplant. Schaut bald wieder vorbei!"

View file

@ -264,6 +264,31 @@ services:
- "description=Forgejo Git Server" - "description=Forgejo Git Server"
- "maintainer=Studio EinsZwoVier" - "maintainer=Studio EinsZwoVier"
element-web:
image: vectorim/element-web:latest
container_name: element-web
restart: unless-stopped
ports:
- "${ELEMENT_PORT:-8082}:80"
volumes:
- ./element-web/config.json:/app/config.json:ro
mem_limit: 512m
cpus: 0.5
mem_reservation: 128m
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:80 || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s
depends_on:
synapse:
condition: service_started
labels:
- "com.centurylinklabs.watchtower.enable=true"
- "description=Element Web Matrix Client"
- "maintainer=studio einszwovier"
volumes: volumes:
portainer_data: portainer_data:
driver: local driver: local

74
element-web/README.md Normal file
View file

@ -0,0 +1,74 @@
# Element Web - Matrix Client
Element Web is a browser-based Matrix client that connects to the studio einszwovier Matrix (Synapse) server.
## Configuration
The `config.json` file configures Element Web to connect to the local Synapse homeserver at `http://einszwovier.local:8008`.
### Key Settings
- **Homeserver**: Points to local Synapse instance
- **Server name**: `einszwovier.local`
- **Brand**: "studio einszwovier Chat"
- **Default language**: German (DE)
- **Default theme**: Light mode
- **Guest access**: Enabled (users can preview without account)
## Access
- **URL**: `http://einszwovier.local:8082`
- **No app required**: Works in any modern web browser
- **Mobile friendly**: Responsive design works on phones and tablets
## Usage
1. Navigate to `http://einszwovier.local:8082`
2. Click "Sign In" or "Create Account"
3. Use existing Matrix credentials or create a new account
4. Join rooms (e.g., the print order room)
5. Chat, share files, and collaborate!
## Features
- ✅ End-to-end encryption
- ✅ File sharing and media uploads
- ✅ Read receipts and typing indicators
- ✅ Markdown formatting
- ✅ Voice and video calls (browser-to-browser)
- ✅ Room directory browsing
- ✅ Cross-device message sync
## Updates
Element Web is managed by Watchtower and updates automatically with the rest of the stack.
## Troubleshooting
**Can't connect to homeserver:**
- Ensure Synapse is running: `docker-compose ps synapse`
- Check Synapse logs: `docker-compose logs synapse`
- Verify hostname resolution: `ping einszwovier.local`
**Login fails:**
- Verify credentials with Matrix admin
- Check Synapse registration settings in `matrix/data/homeserver.yaml`
**Performance issues:**
- Element Web is resource-intensive for large rooms
- Consider using Hydrogen (lighter client) for low-end devices
- Clear browser cache and reload
## Security Notes
- Element Web runs on HTTP in the local network
- For production/internet exposure, configure HTTPS via reverse proxy
- User sessions are stored in browser local storage
- Encryption keys are managed client-side
---
**Maintained by studio einszwovier**

27
element-web/config.json Normal file
View file

@ -0,0 +1,27 @@
{
"default_server_config": {
"m.homeserver": {
"base_url": "http://einszwovier.local:8008",
"server_name": "einszwovier.local"
}
},
"brand": "studio einszwovier Chat",
"disable_guests": false,
"disable_login_language_selector": false,
"default_country_code": "DE",
"show_labs_settings": false,
"default_theme": "light",
"settingDefaults": {
"language": "de"
},
"room_directory": {
"servers": [
"einszwovier.local"
]
},
"enable_presence_by_hs_url": {
"http://einszwovier.local:8008": true
},
"terms_and_conditions_links": [],
"privacy_policy_url": null
}

View file

@ -19,6 +19,7 @@ BOOKSTACK_PORT = os.environ.get("BOOKSTACK_PORT", "6875")
OPENWEBUI_PORT = os.environ.get("OPENWEBUI_PORT", "8080") OPENWEBUI_PORT = os.environ.get("OPENWEBUI_PORT", "8080")
PORTAINER_PORT = os.environ.get("PORTAINER_PORT", "9000") PORTAINER_PORT = os.environ.get("PORTAINER_PORT", "9000")
FORGEJO_PORT = os.environ.get("FORGEJO_PORT", "3003") FORGEJO_PORT = os.environ.get("FORGEJO_PORT", "3003")
ELEMENT_PORT = os.environ.get("ELEMENT_PORT", "8082")
# Courses CSV path # Courses CSV path
COURSES_CSV = Path("data/courses.csv") COURSES_CSV = Path("data/courses.csv")
@ -86,6 +87,7 @@ async def welcome(request: Request):
"openwebui_port": OPENWEBUI_PORT, "openwebui_port": OPENWEBUI_PORT,
"portainer_port": PORTAINER_PORT, "portainer_port": PORTAINER_PORT,
"forgejo_port": FORGEJO_PORT, "forgejo_port": FORGEJO_PORT,
"element_port": ELEMENT_PORT,
}, },
) )
@ -120,7 +122,8 @@ async def cost_dashboard(request: Request):
@app.post("/upload") @app.post("/upload")
async def upload_file(request: Request, file: UploadFile = File(...)): async def upload_file(request: Request, file: UploadFile = File(...)):
if not allowed_file(file.filename): # Ensure filename is present (UploadFile.filename can be None) before validation
if not file.filename or not allowed_file(file.filename):
return templates.TemplateResponse( return templates.TemplateResponse(
"cost-calculator.html", "cost-calculator.html",
{"request": request, "error": "Unsupported file type. Only PDF allowed."}, {"request": request, "error": "Unsupported file type. Only PDF allowed."},

View file

@ -3,14 +3,14 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>{% block title %}Studio Einszwovier{% endblock %}</title> <title>{% block title %}studio einszwovier{% endblock %}</title>
<link rel="stylesheet" href="/static/css/style.css"> <link rel="stylesheet" href="/static/css/style.css">
</head> </head>
<body> <body>
<header> <header>
<a href="/" class="logo-link"> <a href="/" class="logo-link">
<img src="/static/images/logo.png" alt="Studio Einszwovier Logo" class="logo"> <img src="/static/images/logo.png" alt="studio einszwovier Logo" class="logo">
</a> </a>
<nav> <nav>
<a href="/">Startseite</a> | <a href="/">Startseite</a> |

View file

@ -72,6 +72,12 @@
Entwicklung und Projektmanagement.</div> Entwicklung und Projektmanagement.</div>
</a> </a>
<a class="link-card" href="http://{{ server_hostname }}:{{ element_port }}" target="_blank">
<div class="title">Matrix Chat</div>
<div class="tagline">Browser-basierter Chat für die studio einszwovier Community. Nutzt den bestehenden
Matrix-Server keine App-Installation nötig.</div>
</a>
<a class="link-card" href="mailto:einszwovier@gvb-gymnasium.de" target="_blank"> <a class="link-card" href="mailto:einszwovier@gvb-gymnasium.de" target="_blank">
<div class="title">Kontakt</div> <div class="title">Kontakt</div>
<div class="tagline">Schreibe uns direkt an: einszwovier@gvb-gymnasium.de</div> <div class="tagline">Schreibe uns direkt an: einszwovier@gvb-gymnasium.de</div>

View file

@ -2,7 +2,7 @@
<!-- templates/result.html --> <!-- templates/result.html -->
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}Druckkosten Ergebnis Studio EinsZwoVier{% endblock %} {% block title %}Druckkosten Ergebnis studio einszwovier{% endblock %}
{% block content %} {% block content %}
<div class="container"> <div class="container">
@ -70,7 +70,8 @@
<input type="text" id="name" name="name" required placeholder="Dein Name"> <input type="text" id="name" name="name" required placeholder="Dein Name">
<label for="comment"><strong>Zusätzliche Hinweise:</strong></label> <label for="comment"><strong>Zusätzliche Hinweise:</strong></label>
<textarea id="comment" name="comment" rows="4" placeholder="z.&nbsp;B. doppelseitig oder spezielles Papier"></textarea> <textarea id="comment" name="comment" rows="4"
placeholder="z.&nbsp;B. doppelseitig oder spezielles Papier"></textarea>
<button type="submit">Auftrag senden</button> <button type="submit">Auftrag senden</button>
</form> </form>
@ -79,4 +80,4 @@
<a href="/">Neues PDF hochladen</a> <a href="/">Neues PDF hochladen</a>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}