design final
This commit is contained in:
parent
99a690972e
commit
2d69d919a6
9 changed files with 149 additions and 227 deletions
BIN
.DS_Store
vendored
BIN
.DS_Store
vendored
Binary file not shown.
78
DEV_SETUP.md
78
DEV_SETUP.md
|
|
@ -1,78 +0,0 @@
|
|||
# Development Setup Guide
|
||||
|
||||
## Quick Start for Local Development
|
||||
|
||||
The `run_app.py` script mimics Docker production conditions while enabling fast iteration.
|
||||
|
||||
### Standard Development Mode (Recommended)
|
||||
```bash
|
||||
python run_app.py
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- ✓ Loads environment variables from `.env` file
|
||||
- ✓ Single Uvicorn worker with **fast auto-reload**
|
||||
- ✓ Mimics Docker environment (`PYTHONUNBUFFERED=1`)
|
||||
- ✓ Creates upload directory automatically
|
||||
- ✓ Binds to `0.0.0.0:8000` (same as Docker)
|
||||
|
||||
**Best for:** Quick code iterations, template changes, debugging
|
||||
|
||||
### Production-Like Mode
|
||||
```bash
|
||||
python run_app.py --gunicorn
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- ✓ Gunicorn with 4 Uvicorn workers (exactly like Docker)
|
||||
- ✓ 120s timeout (same as Docker)
|
||||
- ✓ Gunicorn's reload (slower but tests production server)
|
||||
- ✓ Tests multi-worker behavior
|
||||
|
||||
**Best for:** Testing before deploying to Docker, load testing
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The script automatically loads `.env` file variables:
|
||||
- `RATE_PER_M2_BLACK` - Black & white print rate (default: 4.0)
|
||||
- `RATE_PER_M2_COLOR` - Color print rate (default: 5.0)
|
||||
- `SERVER_HOSTNAME` - Server hostname (default: einszwovier.local)
|
||||
- `BOOKSTACK_PORT`, `OPENWEBUI_PORT`, `PORTAINER_PORT` - Service ports
|
||||
- `MATRIX_USER`, `MATRIX_PASS`, `MATRIX_HOMESERVER` - Matrix integration
|
||||
|
||||
## Comparison: Dev vs Docker
|
||||
|
||||
| Feature | `python run_app.py` | `docker-compose up` |
|
||||
|---------|---------------------|---------------------|
|
||||
| Server | Uvicorn (single worker) | Gunicorn + Uvicorn (4 workers) |
|
||||
| Auto-reload | ✓ Fast (watches all files) | ✓ Slow (Docker rebuild) |
|
||||
| Env loading | `.env` file | `.env` file |
|
||||
| Port | 8000 | 80→8000 |
|
||||
| Memory limit | None | 1GB limit |
|
||||
| Health checks | No | Yes |
|
||||
| Dependencies | All services | Isolated |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### `.env` file not found
|
||||
Create `.env` file from template:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
### Port 8000 already in use
|
||||
```bash
|
||||
# Find and kill process
|
||||
lsof -ti:8000 | xargs kill -9
|
||||
```
|
||||
|
||||
### Environment variables not loading
|
||||
Ensure `python-dotenv` is installed:
|
||||
```bash
|
||||
pip install python-dotenv
|
||||
```
|
||||
|
||||
### Testing Matrix integration locally
|
||||
1. Start Matrix server: `docker-compose up synapse -d`
|
||||
2. Run app: `python run_app.py`
|
||||
3. Submit test order via `/cost` endpoint
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
services:
|
||||
bookstack:
|
||||
image: lscr.io/linuxserver/bookstack:v25.07.2-ls220
|
||||
container_name: bookstack
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=Europe/Berlin
|
||||
- APP_URL=http://einszwovier.local:6875
|
||||
- APP_KEY=base64:3qjlIoUX4Tw6fUQgZcxMbz6lb8+dAzqpvItqHvahW1c=
|
||||
- DB_HOST=bookstack-mariadb
|
||||
- DB_PORT=3306
|
||||
- DB_DATABASE=bookstack
|
||||
- DB_USERNAME=bookstack
|
||||
- DB_PASSWORD=bookstack8432
|
||||
volumes:
|
||||
- ./bookstack_app_data:/config
|
||||
ports:
|
||||
- 6875:80
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- bookstack-mariadb
|
||||
|
||||
bookstack-mariadb:
|
||||
image: lscr.io/linuxserver/mariadb:11.4.4
|
||||
container_name: bookstack-mariadb
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=Europe/Berlin
|
||||
- MYSQL_ROOT_PASSWORD=mysupersecretrootpassword
|
||||
- MYSQL_DATABASE=bookstack
|
||||
- MYSQL_USER=bookstack
|
||||
- MYSQL_PASSWORD=bookstack8432
|
||||
volumes:
|
||||
- ./bookstack_db_data:/config
|
||||
restart: unless-stopped
|
||||
3
main.py
3
main.py
|
|
@ -101,7 +101,8 @@ async def about(request: Request):
|
|||
|
||||
return templates.TemplateResponse("about.html", {
|
||||
"request": request,
|
||||
"courses": courses
|
||||
"courses": courses,
|
||||
"bookstack_url": f"http://{SERVER_HOSTNAME}:{BOOKSTACK_PORT}"
|
||||
})
|
||||
|
||||
@app.get("/cost", response_class=HTMLResponse)
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
services:
|
||||
synapse:
|
||||
image: matrixdotorg/synapse:latest
|
||||
container_name: synapse
|
||||
restart: always
|
||||
ports:
|
||||
- "8008:8008" # client-server API (internal)
|
||||
volumes:
|
||||
- ./matrix/data:/data
|
||||
environment:
|
||||
- SYNAPSE_SERVER_NAME=localhost
|
||||
- SYNAPSE_REPORT_STATS=no
|
||||
|
|
@ -616,6 +616,109 @@ th {
|
|||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background-color: #e3f2fd;
|
||||
border-left: 4px solid #1976d2;
|
||||
border-radius: 8px;
|
||||
padding: 1.5em;
|
||||
margin: 1.5em 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.info-box p {
|
||||
margin: 0.5em 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.info-box p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.info-box p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-box a {
|
||||
color: #1976d2;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.info-box a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Pricing Card - Prominent pricing display */
|
||||
.pricing-card {
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
border: 2px solid #28a745;
|
||||
border-radius: 12px;
|
||||
padding: 2em;
|
||||
margin: 2em 0;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.pricing-card h2 {
|
||||
color: #28a745;
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 1.5em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.price-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1.5em;
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
.price-item {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
padding: 1.5em;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.price-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.price-item.bw {
|
||||
border-top: 4px solid #6c757d;
|
||||
}
|
||||
|
||||
.price-item.color {
|
||||
border-top: 4px solid #fd7e14;
|
||||
}
|
||||
|
||||
.price-label {
|
||||
display: block;
|
||||
font-size: 1.1rem;
|
||||
color: #6c757d;
|
||||
margin-bottom: 0.5em;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.price-value {
|
||||
display: block;
|
||||
font-size: 2rem;
|
||||
color: #1C1C1E;
|
||||
font-weight: bold;
|
||||
font-family: 'HealTheWebB', sans-serif;
|
||||
}
|
||||
|
||||
.pricing-note {
|
||||
text-align: center;
|
||||
color: #6c757d;
|
||||
font-size: 0.95rem;
|
||||
margin: 0;
|
||||
padding-top: 1em;
|
||||
border-top: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.disclaimer-alert {
|
||||
margin-top: 10px;
|
||||
padding: 10px 15px;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@
|
|||
<p>Seit Dezember 2024 trägt unser Maker Space den Namen <strong>studio einszwovier</strong>. Er ist ein
|
||||
innovativer, digitaler Lernraum, der Kreativität, Technik und Bildungsgerechtigkeit verbindet. Hier wird
|
||||
„Making“ erlebbar: Lernende gestalten ihren Lernprozess aktiv, entdecken individuelle Stärken und erleben
|
||||
durch Selbstwirksamkeit besondere Motivation.</p>
|
||||
durch Selbstwirksamkeit besondere Motivation.
|
||||
Betreut wird der Maker Space von <strong>Aron Petau</strong> und <strong>Friedrich Weber</strong>. Einfach vorbeischauen, Ideen vorstellen und loslegen!
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
|
|
@ -27,25 +29,24 @@
|
|||
<li><strong>Drohnen:</strong> Flugexperimente und Luftbildfotografie.</li>
|
||||
<li><strong>LEGO SPIKE Roboter:</strong> Spielerisches Erlernen von Robotik und Programmierung.</li>
|
||||
</ul>
|
||||
|
||||
Wenn du mehr über unsere Ausstattung erfahren möchtest oder spezielle Geräte suchst, schau in unsere
|
||||
<a href="{{ bookstack_url }}" target="_blank">Wissenssammlung</a> oder frag uns direkt im Studio!
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Betreuungsteam</h2>
|
||||
<p>Betreut wird der Maker Space von <strong>Aron Petau</strong> und <strong>Friedrich Weber</strong>. Sie sind
|
||||
von Dienstag bis Donnerstag von 11:00 bis 16:00 Uhr vor Ort. Einfach vorbeischauen, Ideen vorstellen und loslegen!
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Öffnungszeiten</h2>
|
||||
<h2>Öffnungszeiten + Kontakt</h2>
|
||||
<p>Dienstag bis Donnerstag: 11:00 – 16:00 Uhr<br>
|
||||
Raum 124, Gabriele-von-Bülow-Gymnasium</p>
|
||||
<p>E-Mail: <a href="mailto:einszwovier@gvb-gymnasium.de">einszwovier@gvb-gymnasium.de</a></p>
|
||||
<h2>Standort</h2>
|
||||
<p>Gabriele-von-Bülow-Gymnasium<br>
|
||||
Tile-Brügge-Weg 63, 13509 Berlin (Tegel)<br>
|
||||
Telefon: 030 21 00 52 460<br>
|
||||
E-Mail: <a href="mailto:info@gvb-gymnasium.de">info@gvb-gymnasium.de</a></p>
|
||||
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Kontakt</h2>
|
||||
<p>E-Mail: <a href="mailto:einszwovier@gvb-gymnasium.de">einszwovier@gvb-gymnasium.de</a></p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Aktuelle Kurse</h2>
|
||||
|
|
@ -114,13 +115,5 @@
|
|||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<section>
|
||||
<h2>Standort</h2>
|
||||
<p>Gabriele-von-Bülow-Gymnasium<br>
|
||||
Tile-Brügge-Weg 63, 13509 Berlin (Tegel)<br>
|
||||
Telefon: 030 21 00 52 460<br>
|
||||
E-Mail: <a href="mailto:info@gvb-gymnasium.de">info@gvb-gymnasium.de</a></p>
|
||||
</section>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -8,6 +8,36 @@
|
|||
<div class="container">
|
||||
<h1>Kostenrechner für Drucke</h1>
|
||||
|
||||
<div class="info-box">
|
||||
<p>
|
||||
<strong>So funktioniert's:</strong> Lade dein PDF hoch, um eine Kostenschätzung zu erhalten.
|
||||
Du kannst beliebige PDFs hochladen, um die Kosten zu berechnen.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Wichtig:</strong> Schicke den Auftrag nur ab, wenn du die Datei wirklich drucken lassen möchtest!
|
||||
Für reine Kostenschätzung reicht es, die Berechnung anzusehen, ohne den Auftrag zu senden.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Alternative:</strong> Du kannst uns deine Datei und Infos auch per E-Mail schicken:
|
||||
<a href="mailto:einszwovier@gvb-gymnasium.de">einszwovier@gvb-gymnasium.de</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="pricing-card">
|
||||
<h2>📊 Aktuelle Preise</h2>
|
||||
<div class="price-grid">
|
||||
<div class="price-item bw">
|
||||
<span class="price-label">Schwarz/Weiß</span>
|
||||
<span class="price-value">{{ rate_black if rate_black else 'RATE_PER_M2_BLACK' }} €/m²</span>
|
||||
</div>
|
||||
<div class="price-item color">
|
||||
<span class="price-label">Farbe</span>
|
||||
<span class="price-value">{{ rate_color if rate_color else 'RATE_PER_M2_COLOR' }} €/m²</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="pricing-note">💡 Die Preise decken nur Materialkosten(Tinte & Papier) – kein Gewinn!</p>
|
||||
</div>
|
||||
|
||||
{% if error %}
|
||||
<p class="error">{{ error }}</p>
|
||||
{% endif %}
|
||||
|
|
@ -17,12 +47,6 @@
|
|||
<button type="submit">Hochladen & Berechnen</button>
|
||||
</form>
|
||||
|
||||
<p class="rate-info">
|
||||
Preise werden über Umgebungsvariablen festgelegt:<br>
|
||||
S/W: {{ rate_black if rate_black else 'RATE_PER_M2_BLACK' }} € / m²,
|
||||
Farbe: {{ rate_color if rate_color else 'RATE_PER_M2_COLOR' }} € / m²
|
||||
</p>
|
||||
|
||||
{% if result %}
|
||||
<h2>Ergebnisse für {{ result.filename }}</h2>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,72 +0,0 @@
|
|||
import os
|
||||
import asyncio
|
||||
from io import BytesIO
|
||||
from nio import AsyncClient, UploadResponse, RoomSendResponse
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
async def main():
|
||||
# Get credentials from environment (adjust if needed)
|
||||
matrix_user = os.environ.get("MATRIX_USER", "@einszwovier_bot:localhost")
|
||||
matrix_pass = os.environ.get("MATRIX_PASS")
|
||||
homeserver = os.environ.get("MATRIX_HOMESERVER", "http://localhost:8008")
|
||||
room_id = os.environ.get("MATRIX_ROOM") # e.g. "!abc123:localhost"
|
||||
|
||||
if not all([matrix_user, matrix_pass, room_id]):
|
||||
raise RuntimeError("Missing MATRIX_USER, MATRIX_PASS or MATRIX_ROOM")
|
||||
|
||||
client = AsyncClient(homeserver, matrix_user)
|
||||
login_resp = await client.login(matrix_pass)
|
||||
if getattr(login_resp, "access_token", None) is None:
|
||||
print("❌ Login failed:", login_resp)
|
||||
return
|
||||
|
||||
print("✅ Logged in as", matrix_user)
|
||||
|
||||
pdf_path = "data/uploads/einszwovier infographics 2.pdf" # <-- put any small PDF here
|
||||
with open(pdf_path, "rb") as f:
|
||||
pdf_bytes = f.read()
|
||||
|
||||
# ✅ Upload PDF (nio returns (resp, err))
|
||||
upload_resp, upload_err = await client.upload(
|
||||
data_provider=BytesIO(pdf_bytes),
|
||||
content_type="application/pdf",
|
||||
filename=os.path.basename(pdf_path),
|
||||
filesize=len(pdf_bytes),
|
||||
)
|
||||
|
||||
if upload_err:
|
||||
print("❌ Upload error:", upload_err)
|
||||
await client.close()
|
||||
return
|
||||
|
||||
if isinstance(upload_resp, UploadResponse) and upload_resp.content_uri:
|
||||
print("✅ Upload succeeded:", upload_resp.content_uri)
|
||||
else:
|
||||
print("❌ Upload failed:", upload_resp)
|
||||
await client.close()
|
||||
return
|
||||
|
||||
# Send file message to room
|
||||
file_resp = await client.room_send(
|
||||
room_id=room_id,
|
||||
message_type="m.room.message",
|
||||
content={
|
||||
"msgtype": "m.file",
|
||||
"body": os.path.basename(pdf_path),
|
||||
"url": upload_resp.content_uri,
|
||||
},
|
||||
)
|
||||
|
||||
if isinstance(file_resp, RoomSendResponse) and file_resp.event_id:
|
||||
print("✅ PDF sent to room", room_id)
|
||||
else:
|
||||
print("❌ Failed to send PDF:", file_resp)
|
||||
|
||||
await client.logout()
|
||||
await client.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue