docs/dev: sync backend rework, MQTT, and devcontainer hygiene
README: add Versioning (unified SemVer, pre-releases, build metadata); emphasize UTC handling and streaming endpoint; add Dev Container notes (UI-only Remote Containers, npm ci, idempotent aliases) TECH-CHANGELOG: backend rework notes (serialization camelCase, UTC normalization, streaming metadata); add component build metadata template (image tags/SHAs) Copilot instructions: integrate maintenance guardrails; reinforce UTC and camelCase conventions; document MQTT topics and scheduler retained payload behavior Devcontainer: map Remote Containers to UI; remove in-container install; switch to npm ci; make aliases idempotent
This commit is contained in:
@@ -8,7 +8,7 @@ from server.permissions import admin_or_higher, require_role
|
||||
from sqlalchemy import func
|
||||
import sys
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
sys.path.append('/workspace')
|
||||
|
||||
@@ -27,6 +27,14 @@ def get_grace_period():
|
||||
return int(os.environ.get("HEARTBEAT_GRACE_PERIOD_PROD", "170"))
|
||||
|
||||
|
||||
def _to_utc(dt: datetime) -> datetime:
|
||||
if dt is None:
|
||||
return None
|
||||
if dt.tzinfo is None:
|
||||
return dt.replace(tzinfo=timezone.utc)
|
||||
return dt.astimezone(timezone.utc)
|
||||
|
||||
|
||||
def is_client_alive(last_alive, is_active):
|
||||
"""Berechnet, ob ein Client als alive gilt."""
|
||||
if not last_alive or not is_active:
|
||||
@@ -42,7 +50,10 @@ def is_client_alive(last_alive, is_active):
|
||||
return False
|
||||
else:
|
||||
last_alive_dt = last_alive
|
||||
return datetime.utcnow() - last_alive_dt <= timedelta(seconds=grace_period)
|
||||
# Vergleiche immer in UTC und mit tz-aware Datetimes
|
||||
last_alive_utc = _to_utc(last_alive_dt)
|
||||
now_utc = datetime.now(timezone.utc)
|
||||
return (now_utc - last_alive_utc) <= timedelta(seconds=grace_period)
|
||||
|
||||
|
||||
@groups_bp.route("", methods=["POST"])
|
||||
|
||||
@@ -202,3 +202,66 @@ def update_supplement_table_settings():
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
@system_settings_bp.route('/holiday-banner', methods=['GET'])
|
||||
def get_holiday_banner_setting():
|
||||
"""
|
||||
Get holiday banner enabled status.
|
||||
Public endpoint - dashboard needs this.
|
||||
"""
|
||||
session = Session()
|
||||
try:
|
||||
setting = session.query(SystemSetting).filter_by(key='holiday_banner_enabled').first()
|
||||
enabled = setting.value == 'true' if setting else True
|
||||
|
||||
return jsonify({'enabled': enabled}), 200
|
||||
except SQLAlchemyError as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
@system_settings_bp.route('/holiday-banner', methods=['POST'])
|
||||
@admin_or_higher
|
||||
def update_holiday_banner_setting():
|
||||
"""
|
||||
Update holiday banner enabled status.
|
||||
Admin+ only.
|
||||
|
||||
Request body:
|
||||
{
|
||||
"enabled": true/false
|
||||
}
|
||||
"""
|
||||
session = Session()
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({'error': 'No data provided'}), 400
|
||||
|
||||
enabled = data.get('enabled', True)
|
||||
|
||||
# Update or create setting
|
||||
setting = session.query(SystemSetting).filter_by(key='holiday_banner_enabled').first()
|
||||
if setting:
|
||||
setting.value = 'true' if enabled else 'false'
|
||||
else:
|
||||
setting = SystemSetting(
|
||||
key='holiday_banner_enabled',
|
||||
value='true' if enabled else 'false',
|
||||
description='Ferienstatus-Banner auf Dashboard anzeigen'
|
||||
)
|
||||
session.add(setting)
|
||||
|
||||
session.commit()
|
||||
|
||||
return jsonify({
|
||||
'enabled': enabled,
|
||||
'message': 'Holiday banner setting updated successfully'
|
||||
}), 200
|
||||
except SQLAlchemyError as e:
|
||||
session.rollback()
|
||||
return jsonify({'error': str(e)}), 500
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user