REST API-Nutzung wird im Dashboard ausgewertet und dokumentiert
This commit is contained in:
174
README.md
174
README.md
@@ -18,6 +18,180 @@ Datumsrechner Live: [https://date.elpatron.me](https://date.elpatron.me)
|
||||
- Start-/Enddatum einer Kalenderwoche eines Jahres
|
||||
- Statistik-Dashboard mit Passwortschutz unter `/stats`
|
||||
|
||||
## REST API
|
||||
|
||||
Alle Datumsfunktionen stehen auch als REST-API zur Verfügung. Die API akzeptiert und liefert JSON.
|
||||
**Basis-URL:** `http://localhost:5000/api/`
|
||||
|
||||
**Hinweis:** Die Nutzung der REST API wird im Statistik-Dashboard ausgewertet und als Diagramm angezeigt.
|
||||
|
||||
### Endpunkte und Beispiele
|
||||
|
||||
#### 1. Tage/Werktage zwischen zwei Daten
|
||||
|
||||
**POST** `/api/tage_werktage`
|
||||
|
||||
```json
|
||||
{
|
||||
"start": "2024-06-01",
|
||||
"end": "2024-06-10",
|
||||
"werktage": true
|
||||
}
|
||||
```
|
||||
|
||||
**Mit curl:**
|
||||
```bash
|
||||
curl -X POST http://localhost:5000/api/tage_werktage \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"start": "2024-06-01", "end": "2024-06-10", "werktage": true}'
|
||||
```
|
||||
|
||||
**Antwort:**
|
||||
```json
|
||||
{ "result": 7 }
|
||||
```
|
||||
|
||||
#### 2. Wochentag zu einem Datum
|
||||
|
||||
**POST** `/api/wochentag`
|
||||
|
||||
```json
|
||||
{ "datum": "2024-06-10" }
|
||||
```
|
||||
|
||||
**Mit curl:**
|
||||
```bash
|
||||
curl -X POST http://localhost:5000/api/wochentag \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"datum": "2024-06-10"}'
|
||||
```
|
||||
|
||||
**Antwort:**
|
||||
```json
|
||||
{ "result": "Montag" }
|
||||
```
|
||||
|
||||
#### 3. Kalenderwoche zu Datum
|
||||
|
||||
**POST** `/api/kw_berechnen`
|
||||
|
||||
```json
|
||||
{ "datum": "2024-06-10" }
|
||||
```
|
||||
|
||||
**Mit curl:**
|
||||
```bash
|
||||
curl -X POST http://localhost:5000/api/kw_berechnen \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"datum": "2024-06-10"}'
|
||||
```
|
||||
|
||||
**Antwort:**
|
||||
```json
|
||||
{ "result": "KW 24 (2024)", "kw": 24, "jahr": 2024 }
|
||||
```
|
||||
|
||||
#### 4. Start-/Enddatum einer Kalenderwoche
|
||||
|
||||
**POST** `/api/kw_datum`
|
||||
|
||||
```json
|
||||
{ "jahr": 2024, "kw": 24 }
|
||||
```
|
||||
|
||||
**Mit curl:**
|
||||
```bash
|
||||
curl -X POST http://localhost:5000/api/kw_datum \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"jahr": 2024, "kw": 24}'
|
||||
```
|
||||
|
||||
**Antwort:**
|
||||
```json
|
||||
{
|
||||
"result": "10.06.2024 bis 16.06.2024",
|
||||
"start": "2024-06-10",
|
||||
"end": "2024-06-16"
|
||||
}
|
||||
```
|
||||
|
||||
#### 5. Datum plus/minus Tage, Wochen, Monate
|
||||
|
||||
**POST** `/api/plusminus`
|
||||
|
||||
```json
|
||||
{
|
||||
"datum": "2024-06-10",
|
||||
"anzahl": 5,
|
||||
"einheit": "tage",
|
||||
"richtung": "add",
|
||||
"werktage": false
|
||||
}
|
||||
```
|
||||
|
||||
**Mit curl:**
|
||||
```bash
|
||||
curl -X POST http://localhost:5000/api/plusminus \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"datum": "2024-06-10", "anzahl": 5, "einheit": "tage", "richtung": "add", "werktage": false}'
|
||||
```
|
||||
|
||||
**Antwort:**
|
||||
```json
|
||||
{ "result": "2024-06-15" }
|
||||
```
|
||||
|
||||
**Hinweis:**
|
||||
- `"einheit"`: `"tage"`, `"wochen"` oder `"monate"`
|
||||
- `"richtung"`: `"add"` (plus) oder `"sub"` (minus)
|
||||
- `"werktage"`: `true` für Werktage, sonst `false` (nur bei `"tage"` unterstützt)
|
||||
|
||||
#### 6. Statistik
|
||||
|
||||
**GET** `/api/stats`
|
||||
|
||||
**Mit curl:**
|
||||
```bash
|
||||
curl http://localhost:5000/api/stats
|
||||
```
|
||||
|
||||
**Antwort:**
|
||||
```json
|
||||
{
|
||||
"pageviews": 42,
|
||||
"func_counts": { "plusminus": 10, "tage_werktage": 5 },
|
||||
"impressions_per_day": { "2024-06-10": 7 }
|
||||
}
|
||||
```
|
||||
|
||||
#### 7. Monitoring & Healthcheck
|
||||
|
||||
**GET** `/api/monitor`
|
||||
|
||||
**Mit curl:**
|
||||
```bash
|
||||
curl http://localhost:5000/api/monitor
|
||||
```
|
||||
|
||||
**Antwort:**
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"message": "App running",
|
||||
"time": "2025-07-24T13:37:00.123456",
|
||||
"uptime_seconds": 12345,
|
||||
"pageviews_last_7_days": 42
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Fehlerfälle** liefern immer einen HTTP-Statuscode 400 und ein JSON mit `"error"`-Feld, z.B.:
|
||||
|
||||
```json
|
||||
{ "error": "Ungültige Eingabe", "details": "..." }
|
||||
```
|
||||
|
||||
## Installation (lokal)
|
||||
|
||||
1. Python 3.8+ installieren
|
||||
|
157
app.py
157
app.py
@@ -148,6 +148,7 @@ def stats():
|
||||
pageviews = 0
|
||||
func_counts = {}
|
||||
impressions_per_day = {}
|
||||
api_counts = {}
|
||||
if os.path.exists(log_path):
|
||||
with open(log_path, encoding='utf-8') as f:
|
||||
for line in f:
|
||||
@@ -163,7 +164,10 @@ def stats():
|
||||
elif 'FUNC:' in line:
|
||||
func = line.split('FUNC:')[1].strip()
|
||||
func_counts[func] = func_counts.get(func, 0) + 1
|
||||
return render_template('stats_dashboard.html', pageviews=pageviews, func_counts=func_counts, impressions_per_day=impressions_per_day)
|
||||
elif 'FUNC_API:' in line:
|
||||
api = line.split('FUNC_API:')[1].strip()
|
||||
api_counts[api] = api_counts.get(api, 0) + 1
|
||||
return render_template('stats_dashboard.html', pageviews=pageviews, func_counts=func_counts, impressions_per_day=impressions_per_day, api_counts=api_counts)
|
||||
|
||||
|
||||
@app.route('/monitor')
|
||||
@@ -184,6 +188,157 @@ def monitor():
|
||||
"pageviews_last_7_days": pageviews
|
||||
})
|
||||
|
||||
# --- REST API ---
|
||||
def log_api_usage(api_name):
|
||||
log_dir = 'log'
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
log_path = os.path.join(log_dir, 'pageviews.log')
|
||||
from datetime import datetime as dt
|
||||
with open(log_path, 'a', encoding='utf-8') as f:
|
||||
f.write(f"{dt.now().isoformat()} FUNC_API: {api_name}\n")
|
||||
|
||||
@app.route('/api/tage_werktage', methods=['POST'])
|
||||
def api_tage_werktage():
|
||||
log_api_usage('tage_werktage')
|
||||
data = request.get_json()
|
||||
start = data.get('start')
|
||||
end = data.get('end')
|
||||
is_werktage = data.get('werktage', False)
|
||||
try:
|
||||
d1 = datetime.strptime(start, '%Y-%m-%d')
|
||||
d2 = datetime.strptime(end, '%Y-%m-%d')
|
||||
if is_werktage:
|
||||
if d1 > d2:
|
||||
d1, d2 = d2, d1
|
||||
tage = int(np.busday_count(d1.date(), (d2 + timedelta(days=1)).date()))
|
||||
else:
|
||||
tage = abs((d2 - d1).days)
|
||||
return jsonify({'result': tage})
|
||||
except Exception as e:
|
||||
return jsonify({'error': 'Ungültige Eingabe', 'details': str(e)}), 400
|
||||
|
||||
@app.route('/api/wochentag', methods=['POST'])
|
||||
def api_wochentag():
|
||||
log_api_usage('wochentag')
|
||||
data = request.get_json()
|
||||
datum = data.get('datum')
|
||||
try:
|
||||
d = datetime.strptime(datum, '%Y-%m-%d')
|
||||
wochentag = WOCHENTAGE[d.weekday()]
|
||||
return jsonify({'result': wochentag})
|
||||
except Exception as e:
|
||||
return jsonify({'error': 'Ungültige Eingabe', 'details': str(e)}), 400
|
||||
|
||||
@app.route('/api/kw_berechnen', methods=['POST'])
|
||||
def api_kw_berechnen():
|
||||
log_api_usage('kw_berechnen')
|
||||
data = request.get_json()
|
||||
datum = data.get('datum')
|
||||
try:
|
||||
d = datetime.strptime(datum, '%Y-%m-%d')
|
||||
kw = d.isocalendar().week
|
||||
return jsonify({'result': f"KW {kw} ({d.year})", 'kw': kw, 'jahr': d.year})
|
||||
except Exception as e:
|
||||
return jsonify({'error': 'Ungültige Eingabe', 'details': str(e)}), 400
|
||||
|
||||
@app.route('/api/kw_datum', methods=['POST'])
|
||||
def api_kw_datum():
|
||||
log_api_usage('kw_datum')
|
||||
data = request.get_json()
|
||||
jahr = data.get('jahr')
|
||||
kw = data.get('kw')
|
||||
try:
|
||||
jahr = int(jahr)
|
||||
kw = int(kw)
|
||||
start = datetime.fromisocalendar(jahr, kw, 1)
|
||||
end = datetime.fromisocalendar(jahr, kw, 7)
|
||||
return jsonify({'result': f"{start.strftime('%d.%m.%Y')} bis {end.strftime('%d.%m.%Y')}", 'start': start.strftime('%Y-%m-%d'), 'end': end.strftime('%Y-%m-%d')})
|
||||
except Exception as e:
|
||||
return jsonify({'error': 'Ungültige Eingabe', 'details': str(e)}), 400
|
||||
|
||||
@app.route('/api/plusminus', methods=['POST'])
|
||||
def api_plusminus():
|
||||
log_api_usage('plusminus')
|
||||
data = request.get_json()
|
||||
datum = data.get('datum')
|
||||
anzahl = data.get('anzahl')
|
||||
einheit = data.get('einheit')
|
||||
richtung = data.get('richtung', 'add')
|
||||
is_werktage = data.get('werktage', False)
|
||||
try:
|
||||
d = datetime.strptime(datum, '%Y-%m-%d')
|
||||
anzahl_int = int(anzahl)
|
||||
if richtung == 'sub':
|
||||
anzahl_int = -anzahl_int
|
||||
if einheit == 'tage':
|
||||
if is_werktage:
|
||||
result = np.busday_offset(d.date(), anzahl_int, roll='forward')
|
||||
result_dt = datetime.strptime(str(result), '%Y-%m-%d')
|
||||
return jsonify({'result': result_dt.strftime('%Y-%m-%d')})
|
||||
else:
|
||||
result = d + timedelta(days=anzahl_int)
|
||||
return jsonify({'result': result.strftime('%Y-%m-%d')})
|
||||
elif einheit == 'wochen':
|
||||
if is_werktage:
|
||||
return jsonify({'error': 'Nicht unterstützt: Werktage + Wochen.'}), 400
|
||||
else:
|
||||
result = d + timedelta(weeks=anzahl_int)
|
||||
return jsonify({'result': result.strftime('%Y-%m-%d')})
|
||||
elif einheit == 'monate':
|
||||
if is_werktage:
|
||||
return jsonify({'error': 'Nicht unterstützt: Werktage + Monate.'}), 400
|
||||
else:
|
||||
result = d + relativedelta(months=anzahl_int)
|
||||
return jsonify({'result': result.strftime('%Y-%m-%d')})
|
||||
else:
|
||||
return jsonify({'error': 'Ungültige Einheit'}), 400
|
||||
except Exception as e:
|
||||
return jsonify({'error': 'Ungültige Eingabe', 'details': str(e)}), 400
|
||||
|
||||
@app.route('/api/stats', methods=['GET'])
|
||||
def api_stats():
|
||||
log_path = os.path.join('log', 'pageviews.log')
|
||||
pageviews = 0
|
||||
func_counts = {}
|
||||
impressions_per_day = {}
|
||||
api_counts = {}
|
||||
if os.path.exists(log_path):
|
||||
with open(log_path, encoding='utf-8') as f:
|
||||
for line in f:
|
||||
if 'PAGEVIEW' in line:
|
||||
pageviews += 1
|
||||
try:
|
||||
date = line[:10]
|
||||
if len(date) == 10 and date[4] == '-' and date[7] == '-':
|
||||
impressions_per_day[date] = impressions_per_day.get(date, 0) + 1
|
||||
except Exception:
|
||||
pass
|
||||
elif 'FUNC:' in line:
|
||||
func = line.split('FUNC:')[1].strip()
|
||||
func_counts[func] = func_counts.get(func, 0) + 1
|
||||
elif 'FUNC_API:' in line:
|
||||
api = line.split('FUNC_API:')[1].strip()
|
||||
api_counts[api] = api_counts.get(api, 0) + 1
|
||||
return render_template('stats_dashboard.html', pageviews=pageviews, func_counts=func_counts, impressions_per_day=impressions_per_day, api_counts=api_counts)
|
||||
|
||||
@app.route('/api/monitor', methods=['GET'])
|
||||
def api_monitor():
|
||||
log_path = os.path.join('log', 'pageviews.log')
|
||||
pageviews = 0
|
||||
if os.path.exists(log_path):
|
||||
with open(log_path, encoding='utf-8') as f:
|
||||
for line in f:
|
||||
if 'PAGEVIEW' in line:
|
||||
pageviews += 1
|
||||
uptime = int(time.time() - app_start_time)
|
||||
return jsonify({
|
||||
"status": "ok",
|
||||
"message": "App running",
|
||||
"time": datetime.now().isoformat(),
|
||||
"uptime_seconds": uptime,
|
||||
"pageviews_last_7_days": pageviews
|
||||
})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True, host="0.0.0.0")
|
@@ -27,9 +27,16 @@
|
||||
<div class="chart-container">
|
||||
<canvas id="funcChart" width="400" height="220"></canvas>
|
||||
</div>
|
||||
{% if api_counts and api_counts|length > 0 %}
|
||||
<div class="chart-container">
|
||||
<canvas id="apiChart" width="400" height="220"></canvas>
|
||||
</div>
|
||||
{% endif %}
|
||||
<pre style="background:#f3f4f6; color:#334155; padding:0.5em; border-radius:6px; font-size:0.9em;">API-Counts: {{ api_counts|tojson }}</pre>
|
||||
<a href="/" style="color:#2563eb;">Zurück zur App</a>
|
||||
</div>
|
||||
<script>
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Impressions pro Tag
|
||||
const imprData = {{ impressions_per_day|tojson }};
|
||||
const imprLabels = Object.keys(imprData);
|
||||
@@ -75,6 +82,28 @@
|
||||
}
|
||||
}
|
||||
});
|
||||
// API-Nutzung
|
||||
const apiCounts = {{ api_counts|tojson }};
|
||||
if (Object.keys(apiCounts).length > 0 && document.getElementById('apiChart')) {
|
||||
new Chart(document.getElementById('apiChart').getContext('2d'), {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: Object.keys(apiCounts),
|
||||
datasets: [{
|
||||
label: 'API-Aufrufe nach Endpunkt',
|
||||
data: Object.values(apiCounts),
|
||||
backgroundColor: '#f59e42',
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
plugins: { legend: { display: false } },
|
||||
scales: {
|
||||
y: { beginAtZero: true, ticks: { stepSize: 1 } }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user