mirror of
https://github.com/skoelle/mvg-departures.git
synced 2026-09-18 02:40:25 +00:00
Compare commits
10
Commits
f413cabfc0
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9789eedc9e | ||
|
|
8472475a2c | ||
|
|
10d1a71ab7 | ||
|
|
8260d1a098 | ||
|
|
523ee24b0b | ||
|
|
3a2d803788 | ||
|
|
b90b2f9ba1 | ||
|
|
1c3ea8fb87 | ||
|
|
85a95a1aa4 | ||
|
|
c8b8f4031d |
@@ -0,0 +1,6 @@
|
||||
title: "MVG Departures"
|
||||
emoji: "🚇"
|
||||
category: code
|
||||
subcategory: "Smart Home Apps"
|
||||
status: active
|
||||
stack: [Python, FastAPI, Uvicorn, Jinja2]
|
||||
@@ -11,10 +11,11 @@ MVG Departures Monitor: FastAPI-App für Münchner U-/S-Bahn-Abfahrtsanzeige. Mo
|
||||
| `app/main.py` | FastAPI-App, Endpunkte, Caching, MVG-API-Aufrufe |
|
||||
| `app/config.py` | Config-Loader: YAML → Dataclasses |
|
||||
| `app/templates/index.html` | Jinja2-HTML-Template (Mobile-UI) |
|
||||
| `config.yaml` | Stationskonfiguration (User-Input) |
|
||||
| `config.yaml` | Profil- und Stationskonfiguration (User-Input) |
|
||||
| `requirements.txt` | Python-Dependencies |
|
||||
| `Dockerfile` | Container-Build |
|
||||
| `.github/workflows/build.yml` | CI/CD |
|
||||
| `LICENSE` | MIT-Lizenz |
|
||||
|
||||
## Code-Conventions
|
||||
|
||||
@@ -24,6 +25,7 @@ MVG Departures Monitor: FastAPI-App für Münchner U-/S-Bahn-Abfahrtsanzeige. Mo
|
||||
- **Logging**: `logging.getLogger("mvg-departures")`
|
||||
- **Naming**: snake_case (Python), camelCase (HTML/CSS-Klassen)
|
||||
- **Keine Comments**: Nur wenn explizit angefordert
|
||||
- **Lizenz**: MIT — Copyright-Header in allen Quelldateien (`# Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de) - MIT License`), HTML-Kommentar im Template
|
||||
|
||||
## Entwicklung
|
||||
|
||||
@@ -58,6 +60,9 @@ Kein Linter konfiguriert. Bei Bedarf: `ruff check app/` oder `black app/`
|
||||
### Neue Station hinzufügen
|
||||
→ Nur `config.yaml` editieren, kein Code nötig
|
||||
|
||||
### Neues Profil hinzufügen
|
||||
→ `config.yaml` unter `profiles:` neuen Eintrag mit `name:` und `stations:` ergänzen
|
||||
|
||||
### Neuen Transporttyp hinzufügen
|
||||
→ `TYPE_MAP` und `ICON_MAP` in `main.py` erweitern, CSS-Klasse `.icon-X` in `index.html` hinzufügen
|
||||
|
||||
|
||||
+2
-1
@@ -1,4 +1,4 @@
|
||||
FROM python:3.14-slim@sha256:cea0e6040540fb2b965b6e7fb5ffa00871e632eef63719f0ea54bca189ce14a6
|
||||
FROM python:3.14-slim@sha256:a7fb1e634c4a578f9e0bd6327f11a3cde11b7a9395f48e24360c0988bcc5c2bc
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -7,6 +7,7 @@ RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY app ./app
|
||||
COPY config.yaml ./config.yaml
|
||||
COPY LICENSE ./LICENSE
|
||||
|
||||
ENV CONFIG_PATH=/app/config.yaml
|
||||
ENV TZ=Europe/Berlin
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,21 +1,24 @@
|
||||
# mvg-departures
|
||||
# 🚇 mvg-departures
|
||||
|
||||
Kompakter Abfahrtsmonitor fuer MVG U-Bahn und S-Bahn (Muenchen), primaer fuer mobile Nutzung optimiert.
|
||||
|
||||
## Features
|
||||
|
||||
- Konfigurierbare Stationsliste (`config.yaml`) mit Typ (UBAHN/SBAHN/TRAM/BUS)
|
||||
- Ausschluss-Filter pro Station: nur unerwuenschte Zielrichtungen werden ausgeblendet,
|
||||
neue/unbekannte Ziele werden automatisch weiter angezeigt
|
||||
- Zwei Endpunkte:
|
||||
- `/` — mobile-optimierte HTML-Ansicht mit Auto-Refresh (alle 60s per Meta-Refresh)
|
||||
- `/api/departures` — JSON-Ausgabe derselben Daten
|
||||
- Anzeige von Linie, Ziel, Abfahrtszeit, Verspaetung und Stoerungsmeldungen
|
||||
- Visuelle Unterscheidung von U-Bahn (blau) und S-Bahn (gruen) per Icon
|
||||
|
||||

|
||||
|
||||
## Konfiguration
|
||||
## ✨ Features
|
||||
|
||||
- 🗺️ Konfigurierbare Stationsliste (`config.yaml`) mit Typ (UBAHN/SBAHN/TRAM/BUS)
|
||||
- 🚫 Ausschluss-Filter pro Station: nur unerwuenschte Zielrichtungen werden ausgeblendet,
|
||||
neue/unbekannte Ziele werden automatisch weiter angezeigt
|
||||
- 👤 Profile zur Gruppierung von Stationen (z.B. "Hinfahrt" und "Rückfahrt")
|
||||
- 🖱️ Klickbare Kacheln zum Profilwechsel (nur bei >1 Profil)
|
||||
- 🔗 URL-Param `?profile=<name>` + Auto-Refresh
|
||||
- 🌐 Zwei Endpunkte:
|
||||
- `/` — mobile-optimierte HTML-Ansicht mit Auto-Refresh (alle 60s per Meta-Refresh)
|
||||
- `/api/departures` — JSON-Ausgabe derselben Daten
|
||||
- 📊 Anzeige von Linie, Ziel, Abfahrtszeit, Verspaetung und Stoerungsmeldungen
|
||||
- 🎨 Visuelle Unterscheidung von U-Bahn (blau) und S-Bahn (gruen) per Icon
|
||||
|
||||
## ⚙️ Konfiguration
|
||||
|
||||
Siehe `config.yaml`:
|
||||
|
||||
@@ -24,39 +27,45 @@ refresh_seconds: 60
|
||||
cache_seconds: 20
|
||||
departures_limit: 10
|
||||
|
||||
stations:
|
||||
- name: "Josephsburg, München"
|
||||
type: "UBAHN"
|
||||
exclude_destinations:
|
||||
- "Messestadt Ost"
|
||||
- "Messestadt West"
|
||||
|
||||
- name: "Berg am Laim, München"
|
||||
type: "SBAHN"
|
||||
exclude_destinations:
|
||||
- "Erding"
|
||||
- "Markt Schwaben"
|
||||
profiles:
|
||||
- name: "Hinfahrt"
|
||||
stations:
|
||||
- name: "Josephsburg, München"
|
||||
type: "UBAHN"
|
||||
exclude_destinations:
|
||||
- "Messestadt Ost"
|
||||
- "Messestadt West"
|
||||
- name: "Berg am Laim, München"
|
||||
type: "SBAHN"
|
||||
exclude_destinations:
|
||||
- "Erding"
|
||||
- "Markt Schwaben"
|
||||
- name: "Rückfahrt"
|
||||
stations:
|
||||
- name: "Grosshadern, München"
|
||||
type: "UBAHN"
|
||||
exclude_destinations: []
|
||||
```
|
||||
|
||||
`exclude_destinations` filtert per exaktem `destination`-Stringvergleich. Alles was
|
||||
NICHT in der Liste steht, wird angezeigt — so fallen Linienaenderungen/Umleitungen
|
||||
sofort auf, statt lautlos verschwunden zu sein.
|
||||
|
||||
## Lokal starten
|
||||
## 🏃 Lokal starten
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
uvicorn app.main:app --reload
|
||||
```
|
||||
|
||||
## Docker
|
||||
## 🐳 Docker
|
||||
|
||||
```bash
|
||||
docker build -t mvg-departures .
|
||||
docker run -p 8000:8000 -e TZ=Europe/Berlin -v $(pwd)/config.yaml:/app/config.yaml mvg-departures
|
||||
```
|
||||
|
||||
## Deployment
|
||||
## 🚀 Deployment
|
||||
|
||||
Siehe `docker-compose.yml` im Repo als Referenz. Beispiel (Owner anpassen):
|
||||
|
||||
@@ -76,36 +85,38 @@ services:
|
||||
- "com.centurylinklabs.watchtower.enable=true"
|
||||
```
|
||||
|
||||
## CI/CD
|
||||
## 🔄 CI/CD
|
||||
|
||||
`.github/workflows/build.yml`:
|
||||
|
||||
- Baut bei jedem Push auf `main` das Docker-Image und pusht es nach
|
||||
- 🏗️ Baut bei jedem Push auf `main` das Docker-Image und pusht es nach
|
||||
`ghcr.io/<owner>/<repo>:latest` sowie mit Short-SHA-Tag
|
||||
- Anschliessender Cleanup-Job loescht alte Image-Versionen in der GHCR-Package-Registry
|
||||
- 🧹 Anschliessender Cleanup-Job loescht alte Image-Versionen in der GHCR-Package-Registry
|
||||
und behaelt nur die letzten 4 Versionen (`min-versions-to-keep: 4`)
|
||||
|
||||
### Voraussetzung
|
||||
### 📋 Voraussetzung
|
||||
|
||||
Repo-Settings → Actions → General → Workflow permissions auf
|
||||
"Read and write permissions" stellen, sonst schlaegt der Push nach GHCR fehl.
|
||||
|
||||
## mvg api
|
||||
## 📡 mvg api
|
||||
|
||||
https://www.mvg.de/api/bgw-pt/v3/locations?query=Josephsburg \
|
||||
https://www.mvg.de/api/bgw-pt/v3/locations?query=Berg%20am \
|
||||
https://www.mvg.de/api/bgw-pt/v3/departures?globalId=de:09162:1220&limit=10&transportTypes=UBAHN \
|
||||
https://www.mvg.de/api/bgw-pt/v3/departures?globalId=de:09162:910&limit=10&transportTypes=SBAHN
|
||||
|
||||
## Endpunkte
|
||||
## 🔌 Endpunkte
|
||||
|
||||
| Route | Beschreibung |
|
||||
|---|---|
|
||||
| `/` | HTML-Ansicht, mobile-optimiert, Auto-Refresh alle 60s |
|
||||
| `/api/departures` | JSON-Liste aller gefilterten Abfahrten |
|
||||
| `/?profile=<name>` | HTML-Ansicht für ein bestimmtes Profil |
|
||||
| `/api/departures` | JSON-Liste aller gefilterten Abfahrten (erstes Profil als Fallback) |
|
||||
| `/api/departures?profile=<name>` | JSON-Liste für ein bestimmtes Profil |
|
||||
| `/healthz` | Healthcheck fuer Docker/Watchtower |
|
||||
|
||||
## Projektstruktur
|
||||
## 📁 Projektstruktur
|
||||
|
||||
```
|
||||
mvg-departures/
|
||||
@@ -128,24 +139,28 @@ mvg-departures/
|
||||
└── build.yml # CI/CD: Docker-Build + GHCR-Push
|
||||
```
|
||||
|
||||
## Tech-Stack
|
||||
## 🛠️ Tech-Stack
|
||||
|
||||
- **Runtime**: Python 3.14
|
||||
- **Framework**: FastAPI + Uvicorn
|
||||
- **Templating**: Jinja2 (server-seitig)
|
||||
- **MVG-API**: `mvg` Python-Client (oeffentlich, kein Auth noetig)
|
||||
- **Config**: YAML via `pyyaml`
|
||||
- **Container**: Docker (python:3.14-slim)
|
||||
- ⚡ **Runtime**: Python 3.14
|
||||
- 🚀 **Framework**: FastAPI + Uvicorn
|
||||
- 🎨 **Templating**: Jinja2 (server-seitig)
|
||||
- 🚇 **MVG-API**: `mvg` Python-Client (oeffentlich, kein Auth noetig)
|
||||
- ⚙️ **Config**: YAML via `pyyaml`
|
||||
- 🐳 **Container**: Docker (python:3.14-slim)
|
||||
|
||||
## Caching
|
||||
## 💾 Caching
|
||||
|
||||
- **Station-ID**: In-memory Dict, lifetime=Session (loest Station-Namen auf)
|
||||
- **Abfahrten**: In-memory Dict, TTL=`cache_seconds` (Default: 20s) pro Station+Typ
|
||||
- Keine Persistenz → Neustart = Cache-Verlust
|
||||
- 🆔 **Station-ID**: In-memory Dict, lifetime=Session (loest Station-Namen auf)
|
||||
- ⏱️ **Abfahrten**: In-memory Dict, TTL=`cache_seconds` (Default: 20s) pro Station+Typ
|
||||
- 🔄 Keine Persistenz → Neustart = Cache-Verlust
|
||||
|
||||
## Einschraenkungen
|
||||
## ⚠️ Einschraenkungen
|
||||
|
||||
- Kein WebSocket/Live-Updates (nur periodischer Meta-Refresh)
|
||||
- Keine Datenbank (nur In-Memory)
|
||||
- Kein Test-Framework vorhanden
|
||||
- Ein `config.yaml` pro Deployment (kein Multi-Tenancy)
|
||||
- 📡 Kein WebSocket/Live-Updates (nur periodischer Meta-Refresh)
|
||||
- 💽 Keine Datenbank (nur In-Memory)
|
||||
- 🧪 Kein Test-Framework vorhanden
|
||||
- 📄 Ein `config.yaml` pro Deployment (kein Multi-Tenancy)
|
||||
|
||||
## 📜 Lizenz
|
||||
|
||||
MIT License - Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)
|
||||
|
||||
@@ -42,7 +42,9 @@ config.yaml
|
||||
↓
|
||||
load_config() → AppConfig (Dataclass)
|
||||
↓
|
||||
Für jede Station:
|
||||
Profile auswählen (?profile= param oder erstes Profil)
|
||||
↓
|
||||
Für jede Station im aktiven Profil:
|
||||
resolve_station_id(name) → station_id (mit Memory-Cache)
|
||||
↓
|
||||
MvgApi(station_id).departures(limit, transport_types)
|
||||
@@ -61,7 +63,9 @@ Response: JSON oder HTML-Template
|
||||
| Route | Methode | Beschreibung |
|
||||
|---|---|---|
|
||||
| `/` | GET | Mobile HTML-Ansicht, Auto-Refresh alle 60s |
|
||||
| `/api/departures` | GET | JSON-Liste aller gefilterten Abfahrten |
|
||||
| `/?profile=<name>` | GET | HTML-Ansicht für ein bestimmtes Profil |
|
||||
| `/api/departures` | GET | JSON-Liste aller gefilterten Abfahrten (erstes Profil als Fallback) |
|
||||
| `/api/departures?profile=<name>` | GET | JSON-Liste für ein bestimmtes Profil |
|
||||
| `/healthz` | GET | Healthcheck (`{"status": "ok"}`) |
|
||||
|
||||
## 6. Konfiguration (`config.yaml`)
|
||||
@@ -71,11 +75,18 @@ refresh_seconds: 60 # Meta-Refresh-Intervall (HTML)
|
||||
cache_seconds: 20 # API-Cache pro Station
|
||||
departures_limit: 10 # Max. Abfahrten pro Station
|
||||
|
||||
stations:
|
||||
- name: "Station Name, München"
|
||||
type: "UBAHN" # UBAHN | SBAHN | TRAM | BUS
|
||||
exclude_destinations: # Exakter Stringvergleich
|
||||
- "Zielstation"
|
||||
profiles:
|
||||
- name: "Hinfahrt" # Profilname (einzigartig)
|
||||
stations:
|
||||
- name: "Station Name, München"
|
||||
type: "UBAHN" # UBAHN | SBAHN | TRAM | BUS
|
||||
exclude_destinations: # Exakter Stringvergleich
|
||||
- "Zielstation"
|
||||
- name: "Rückfahrt"
|
||||
stations:
|
||||
- name: "Andere Station"
|
||||
type: "SBAHN"
|
||||
exclude_destinations: []
|
||||
```
|
||||
|
||||
### Filter-Logik
|
||||
@@ -93,6 +104,7 @@ stations:
|
||||
## 8. UI
|
||||
|
||||
- Dunkles Theme (`#111417` Background)
|
||||
- Profil-Kacheln (nur bei >1 Profil): 50% Breite, Basis `#1e242b`, aktiv `#005ca9` (MVG-Blau)
|
||||
- Farbliche Unterscheidung: U-Bahn (blau `#005ca9`), S-Bahn (grün `#00933b`), Tram (rot `#e2001a`), Bus (grau `#55545a`)
|
||||
- Anzeige: Linie, Ziel, Abfahrtszeit, Verspätung (gelb/rot), Entfall, Störungsmeldungen
|
||||
- Responsive, für Smartphone-First optimiert
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de) - MIT License
|
||||
|
||||
+20
-10
@@ -1,3 +1,4 @@
|
||||
# Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de) - MIT License
|
||||
"""Config-Loader für die Stationsliste aus config.yaml."""
|
||||
import os
|
||||
import yaml
|
||||
@@ -12,12 +13,18 @@ class StationConfig:
|
||||
exclude_destinations: List[str] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ProfileConfig:
|
||||
name: str
|
||||
stations: List[StationConfig] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AppConfig:
|
||||
refresh_seconds: int
|
||||
cache_seconds: int
|
||||
departures_limit: int
|
||||
stations: List[StationConfig]
|
||||
profiles: List[ProfileConfig]
|
||||
|
||||
|
||||
def load_config(path: str = None) -> AppConfig:
|
||||
@@ -25,18 +32,21 @@ def load_config(path: str = None) -> AppConfig:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
raw = yaml.safe_load(f)
|
||||
|
||||
stations = [
|
||||
StationConfig(
|
||||
name=s["name"],
|
||||
type=s["type"].upper(),
|
||||
exclude_destinations=s.get("exclude_destinations", []) or [],
|
||||
)
|
||||
for s in raw.get("stations", [])
|
||||
]
|
||||
profiles = []
|
||||
for p in raw.get("profiles", []):
|
||||
stations = [
|
||||
StationConfig(
|
||||
name=s["name"],
|
||||
type=s["type"].upper(),
|
||||
exclude_destinations=s.get("exclude_destinations", []) or [],
|
||||
)
|
||||
for s in p.get("stations", [])
|
||||
]
|
||||
profiles.append(ProfileConfig(name=p["name"], stations=stations))
|
||||
|
||||
return AppConfig(
|
||||
refresh_seconds=int(raw.get("refresh_seconds", 60)),
|
||||
cache_seconds=int(raw.get("cache_seconds", 20)),
|
||||
departures_limit=int(raw.get("departures_limit", 10)),
|
||||
stations=stations,
|
||||
profiles=profiles,
|
||||
)
|
||||
|
||||
+21
-7
@@ -1,3 +1,4 @@
|
||||
# Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de) - MIT License
|
||||
"""MVG Departures Monitor - FastAPI App."""
|
||||
import time
|
||||
import logging
|
||||
@@ -11,7 +12,7 @@ from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from mvg import MvgApi, TransportType
|
||||
|
||||
from app.config import load_config, StationConfig
|
||||
from app.config import load_config, StationConfig, ProfileConfig
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger("mvg-departures")
|
||||
@@ -97,27 +98,40 @@ def fetch_departures_for_station(station_cfg: StationConfig) -> List[Dict[str, A
|
||||
return result
|
||||
|
||||
|
||||
def get_all_departures() -> List[Dict[str, Any]]:
|
||||
def get_profile_by_name(profile_name: str) -> ProfileConfig:
|
||||
for profile in config.profiles:
|
||||
if profile.name == profile_name:
|
||||
return profile
|
||||
return config.profiles[0] if config.profiles else None
|
||||
|
||||
|
||||
def get_departures_for_profile(profile: ProfileConfig) -> List[Dict[str, Any]]:
|
||||
all_deps: List[Dict[str, Any]] = []
|
||||
for station_cfg in config.stations:
|
||||
for station_cfg in profile.stations:
|
||||
all_deps.extend(fetch_departures_for_station(station_cfg))
|
||||
all_deps.sort(key=lambda d: d["time_epoch"] or 0)
|
||||
return all_deps
|
||||
|
||||
|
||||
@app.get("/api/departures")
|
||||
def api_departures():
|
||||
return JSONResponse(content={"departures": get_all_departures()})
|
||||
def api_departures(profile: str = None):
|
||||
active_profile = get_profile_by_name(profile) if profile else config.profiles[0] if config.profiles else None
|
||||
if not active_profile:
|
||||
return JSONResponse(content={"departures": []})
|
||||
return JSONResponse(content={"departures": get_departures_for_profile(active_profile)})
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def index(request: Request):
|
||||
departures = get_all_departures()
|
||||
def index(request: Request, profile: str = None):
|
||||
active_profile = get_profile_by_name(profile) if profile else config.profiles[0] if config.profiles else None
|
||||
departures = get_departures_for_profile(active_profile) if active_profile else []
|
||||
return templates.TemplateResponse(
|
||||
request,
|
||||
"index.html",
|
||||
{
|
||||
"departures": departures,
|
||||
"profiles": config.profiles,
|
||||
"active_profile": active_profile,
|
||||
"refresh_seconds": config.refresh_seconds,
|
||||
"generated_at": datetime.now().strftime("%H:%M:%S"),
|
||||
},
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
<!-- Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de) - MIT License -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Abfahrten</title>
|
||||
<meta http-equiv="refresh" content="{{ refresh_seconds }}">
|
||||
<meta http-equiv="refresh" content="{{ refresh_seconds }};url=/?profile={{ active_profile.name }}">
|
||||
</head>
|
||||
<body>
|
||||
<div style="max-width: 375px; margin: 0 auto; width: 100%;">
|
||||
<h1>Abfahrten <span class="updated">{{ generated_at }}</span></h1>
|
||||
|
||||
{% if profiles|length > 1 %}
|
||||
<div style="display: flex; gap: 8px; margin-bottom: 16px;">
|
||||
{% for p in profiles %}
|
||||
<a href="/?profile={{ p.name }}"
|
||||
style="flex: 1; padding: 12px; text-align: center; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 18px; color: {{ 'white' if p.name == active_profile.name else '#9aa5b1' }}; background: {{ '#005ca9' if p.name == active_profile.name else '#1e242b' }};">
|
||||
{{ p.name }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if departures %}
|
||||
<table>
|
||||
{% for d in departures %}
|
||||
|
||||
+15
-12
@@ -7,15 +7,18 @@ refresh_seconds: 60
|
||||
cache_seconds: 20
|
||||
departures_limit: 10
|
||||
|
||||
stations:
|
||||
- name: "Josephsburg, München"
|
||||
type: "UBAHN"
|
||||
exclude_destinations:
|
||||
- "Messestadt Ost"
|
||||
- "Messestadt West"
|
||||
|
||||
- name: "Berg am Laim, München"
|
||||
type: "SBAHN"
|
||||
exclude_destinations:
|
||||
- "Erding"
|
||||
- "Markt Schwaben"
|
||||
profiles:
|
||||
- name: "Hinfahrt"
|
||||
stations:
|
||||
- name: "Josephsburg, München"
|
||||
type: "UBAHN"
|
||||
exclude_destinations:
|
||||
- "Messestadt Ost"
|
||||
- "Messestadt West"
|
||||
- name: "Berg am Laim, München"
|
||||
type: "SBAHN"
|
||||
exclude_destinations:
|
||||
- "Erding"
|
||||
- "Markt Schwaben"
|
||||
- name: "Rückfahrt"
|
||||
stations: []
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
mvg-departures:
|
||||
image: ghcr.io/skoelle/mvg-departures:latest@sha256:c93ca797aa5dea84611a44d9463dd557816b664d30cdc7e956335f3e7628dcb0
|
||||
image: ghcr.io/skoelle/mvg-departures:latest
|
||||
container_name: mvg-departures
|
||||
ports:
|
||||
- "8000:8000"
|
||||
|
||||
+38
-11
@@ -3,23 +3,50 @@
|
||||
"extends": [
|
||||
"config:recommended",
|
||||
"docker:pinDigests",
|
||||
"helpers:pinGitHubActionDigests",
|
||||
":configMigration",
|
||||
":pinDevDependencies",
|
||||
"abandonments:recommended",
|
||||
"security:minimumReleaseAgeNpm",
|
||||
":dependencyDashboard"
|
||||
"helpers:pinGitHubActionDigests"
|
||||
],
|
||||
"schedule": ["before 6am on monday"],
|
||||
"timezone": "Europe/Berlin",
|
||||
"automergeStrategy": "squash",
|
||||
"ignorePaths": ["docker-compose.yml"],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchUpdateTypes": ["minor", "patch"],
|
||||
"groupName": "{{depName}}",
|
||||
"commitMessagePrefix": "fix(deps)"
|
||||
"description": "GitHub Actions - grouped automerge",
|
||||
"matchManagers": ["github-actions"],
|
||||
"groupName": "github-actions",
|
||||
"commitMessagePrefix": "ci",
|
||||
"automerge": true
|
||||
},
|
||||
{
|
||||
"description": "Docker Digests - automerge (security patches)",
|
||||
"matchManagers": ["dockerfile"],
|
||||
"matchUpdateTypes": ["digest"],
|
||||
"commitMessagePrefix": "docker",
|
||||
"automerge": true
|
||||
},
|
||||
{
|
||||
"description": "Docker Major/Minor - manual review",
|
||||
"matchManagers": ["dockerfile"],
|
||||
"matchUpdateTypes": ["major", "minor"],
|
||||
"groupName": "docker-base-images",
|
||||
"commitMessagePrefix": "docker",
|
||||
"automerge": false
|
||||
},
|
||||
{
|
||||
"description": "Python minor/patch - automerge",
|
||||
"matchManagers": ["pip_requirements"],
|
||||
"matchUpdateTypes": ["minor", "patch"],
|
||||
"commitMessagePrefix": "fix(deps)",
|
||||
"automerge": true
|
||||
},
|
||||
{
|
||||
"description": "Python major - no automerge, label",
|
||||
"matchManagers": ["pip_requirements"],
|
||||
"matchUpdateTypes": ["major"],
|
||||
"groupName": "major-{{depName}}"
|
||||
"commitMessagePrefix": "chore(deps)",
|
||||
"automerge": false,
|
||||
"labels": ["major-update"]
|
||||
}
|
||||
]
|
||||
],
|
||||
"ignoreDeps": ["node", "express", "node-cron", "nodemailer", "actions/setup-node"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user