diff --git a/app/main.py b/app/main.py index dff4584..274d509 100644 --- a/app/main.py +++ b/app/main.py @@ -12,7 +12,8 @@ from sqlalchemy import text app = Flask(__name__) # --- Konfiguration --- -app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URL', 'sqlite:////app/data/raceplanner.db') +db_path = os.path.join('/app/data', 'raceplanner.db') +app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['SECRET_KEY'] = 'renn-strategie-2026-final-v3' app.config['REMEMBER_COOKIE_DURATION'] = timedelta(days=31) @@ -33,7 +34,7 @@ class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True, nullable=False) password = db.Column(db.String(200), nullable=False) - theme = db.Column(db.String(20), default='light') # 'light' oder 'dark' + theme = db.Column(db.String(20), default='light') class RaceConfig(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -63,7 +64,6 @@ def load_user(user_id): def get_calculated_schedule(car_num): config = RaceConfig.query.first() if not config: return [] - stints = Stint.query.filter_by(car_number=car_num).order_by(Stint.order).all() schedule = [] current_time = config.start_time @@ -72,13 +72,10 @@ def get_calculated_schedule(car_num): for i, stint in enumerate(stints): driver = stint.driver if not driver: continue - - laps_possible = int(fuel_capacity / driver.cons_per_lap) - duration_sec = laps_possible * driver.avg_lap_time - + laps_possible = int(fuel_capacity / (driver.cons_per_lap or 1.0)) + duration_sec = laps_possible * (driver.avg_lap_time or 120.0) start_str = current_time.strftime('%H:%M') end_time = current_time + timedelta(seconds=duration_sec) - schedule.append({ 'id': stint.id, 'number': i + 1, @@ -90,12 +87,62 @@ def get_calculated_schedule(car_num): 'laps': laps_possible, 'fuel': round(fuel_capacity, 1), 'change_tires': stint.change_tires, - 'is_finish': (current_time + timedelta(seconds=duration_sec)) >= (config.start_time + timedelta(hours=config.total_duration_h)) + 'is_finish': end_time >= (config.start_time + timedelta(hours=config.total_duration_h)) }) current_time = end_time return schedule -# --- Routen --- +# --- Szenario Routen (Speichern/Laden) --- +@app.route('/save_scenario', methods=['POST']) +@login_required +def save_scenario(): + name = request.json.get('name', 'unnamed_scenario') + filename = f"{name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" + + data = { + 'config': { + 'start_time': RaceConfig.query.first().start_time.isoformat(), + 'total_duration_h': RaceConfig.query.first().total_duration_h + }, + 'stints': [] + } + + for s in Stint.query.all(): + data['stints'].append({ + 'car_number': s.car_number, + 'driver_id': s.driver_id, + 'order': s.order, + 'change_tires': s.change_tires + }) + + with open(os.path.join(SCENARIO_DIR, filename), 'w') as f: + json.dump(data, f) + return jsonify({'status': 'ok', 'filename': filename}) + +@app.route('/list_scenarios') +@login_required +def list_scenarios(): + files = [f for f in os.listdir(SCENARIO_DIR) if f.endswith('.json')] + return jsonify(sorted(files, reverse=True)) + +@app.route('/load_scenario', methods=['POST']) +@login_required +def load_scenario(): + filename = request.json.get('filename') + path = os.path.join(SCENARIO_DIR, filename) + if not os.path.exists(path): + return jsonify({'status': 'error', 'message': 'Datei nicht gefunden'}) + + with open(path, 'r') as f: + data = json.load(f) + + Stint.query.delete() + for s_data in data['stints']: + db.session.add(Stint(**s_data)) + db.session.commit() + return jsonify({'status': 'ok'}) + +# --- Standard Routen --- @app.route('/') @login_required def index(): @@ -121,8 +168,9 @@ def update_theme(): def update_stint_driver(): data = request.json s = db.session.get(Stint, data['stint_id']) - s.driver_id = data['driver_id'] - db.session.commit() + if s: + s.driver_id = data['driver_id'] + db.session.commit() return jsonify({'status': 'ok'}) @app.route('/reorder_stints', methods=['POST']) @@ -131,7 +179,7 @@ def reorder_stints(): order = request.json['order'] for idx, sid in enumerate(order): s = db.session.get(Stint, int(sid)) - s.order = idx + if s: s.order = idx db.session.commit() return jsonify({'status': 'ok'}) @@ -158,7 +206,7 @@ def driver_detail(id): @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': - u = User.query.filter_by(username=request.form.get('username')).first() + u = User.query.filter_by(username='mscaltenbach').first() if u and check_password_hash(u.password, request.form.get('password')): session.permanent = True login_user(u, remember=True) @@ -170,24 +218,43 @@ def logout(): logout_user() return redirect(url_for('login')) -if __name__ == '__main__': +def init_db(): with app.app_context(): db.create_all() - - # Manueller Fix: Spalte 'theme' hinzufügen, falls sie fehlt (SQLite-spezifisch) try: db.session.execute(text("ALTER TABLE user ADD COLUMN theme VARCHAR(20) DEFAULT 'light'")) db.session.commit() except Exception: - # Fehler tritt auf, wenn Spalte bereits existiert – das ist ok db.session.rollback() - if not User.query.filter_by(username='mscaltenbach').first(): - pw = generate_password_hash('admin123') - db.session.add(User(username='mscaltenbach', password=pw, theme='light')) + target_username = 'mscaltenbach' + target_password = 'SendIt123!' + admin_user = User.query.filter_by(username=target_username).first() + if not admin_user: + hashed_pw = generate_password_hash(target_password) + db.session.add(User(username=target_username, password=hashed_pw, theme='light')) + db.session.commit() if not RaceConfig.query.first(): - db.session.add(RaceConfig()) - - db.session.commit() + db.session.add(RaceConfig(start_time=datetime.now(), total_duration_h=24)) + db.session.commit() + + if not Driver.query.first(): + drivers = [ + Driver(name="Caltenbach", car_number=1, avg_lap_time=128.5, cons_per_lap=3.8), + Driver(name="Mueller", car_number=1, avg_lap_time=130.2, cons_per_lap=3.6), + Driver(name="Schmidt", car_number=2, avg_lap_time=129.1, cons_per_lap=3.7), + Driver(name="Weber", car_number=2, avg_lap_time=131.5, cons_per_lap=3.5) + ] + db.session.add_all(drivers) + db.session.commit() + d1, d2 = Driver.query.filter_by(car_number=1).first(), Driver.query.filter_by(car_number=2).first() + if d1 and d2: + for i in range(12): + db.session.add(Stint(car_number=1, driver_id=d1.id, order=i)) + db.session.add(Stint(car_number=2, driver_id=d2.id, order=i)) + db.session.commit() + +if __name__ == '__main__': + init_db() app.run(host='0.0.0.0', port=5000, debug=True) \ No newline at end of file diff --git a/app/templates/driver_detail.html b/app/templates/driver_detail.html index cbab630..c9bb210 100644 --- a/app/templates/driver_detail.html +++ b/app/templates/driver_detail.html @@ -1,50 +1,83 @@ - + {{ driver.name }} - Details - + - +
-
-

{{ driver.name }} #{{ driver.car_number }}

- Zurück + +
+
+

+ {{ driver.name }} #{{ driver.car_number }} +

+

Driver Profile & Schedule

+
+ Zurück zum Planner
-
-
-
Verbrauch/Runde
-
{{ driver.cons_per_lap }}L
+ +
+
+
Verbrauch / Runde
+
{{ driver.cons_per_lap }}L
-
-
Ø Rundenzeit
-
{{ driver.avg_lap_time }}s
+
+
Ø Rundenzeit
+
{{ driver.avg_lap_time }}s
-
-
Stints Gesamt
-
{{ stints|length }}
+
+
Anzahl Stints
+
{{ stints|length }}
-

Geplante Einsätze

-
+ +

+ + Einsatzplanung +

+ +
{% for s in stints %} -
+
-
{{ s.date }}
-
{{ s.start }} - {{ s.end }}
+
{{ s.date }}
+
{{ s.start }} — {{ s.end }}
-
Stint #{{ s.number }}
-
{{ s.laps }} Runden
+
Stint #{{ s.number }}
+
{{ s.laps }} Runden · {{ s.fuel }}L Start
{% else %} -
Keine Stints für diesen Fahrer geplant.
+
+

Keine Stints geplant

+
{% endfor %}
diff --git a/app/templates/index.html b/app/templates/index.html index 944aec6..206fe36 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -1,81 +1,82 @@ - + RACEPLANNER 2026 - - - + +
+ + + + +
+ + +
+ Logout +
+ -
{% for car_num in [1, 2] %} - {% set schedule = car1 if car_num == 1 else car2 %} -
-
-

CAR #{{ car_num }}

- PDF Export +
+
+

CAR #{{ car_num }}

+ PDF Export
+ {% set schedule = car1 if car_num == 1 else car2 %} {% for s in schedule %} -
-
-
-
STINT
-
#{{ s.number }}
+
+
+ +
+
+
{{ s.date }}
+
{{ s.start }}
-
- {{ s.date }} - {{ s.start }} — {{ s.end }} -
- {% for d in drivers %} - + {% endfor %} +
+ {{ s.laps }} Runden · {{ s.fuel }}L +
-
-
{{ s.fuel }}L
-
{{ s.laps }} Laps
- {% if s.is_finish %}Finish - {% elif s.change_tires %}Tires{% endif %} +
+
{{ s.end }}
+ {% if s.is_finish %}Finish{% endif %}
{% endfor %} @@ -85,32 +86,50 @@
-