From 486ed41c46934be05a787234dab308937eb9e2c5 Mon Sep 17 00:00:00 2001 From: Till Tomczak Date: Thu, 29 May 2025 15:08:11 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20'feat(database):=20Implement=20d?= =?UTF-8?q?atabase=20backup=20on=20demand=20and=20immediate'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/database/myp.db | Bin 69632 -> 106496 bytes backend/app/database/myp.db-shm | Bin 32768 -> 32768 bytes backend/app/database/myp.db-wal | Bin 111272 -> 41232 bytes .../database/myp.db.backup_20250529_150542 | Bin 0 -> 69632 bytes .../myp.db.backup_immediate_20250529_150732 | Bin 0 -> 106496 bytes backend/app/database_schema_migration.py | 381 +++++++++++++++++- backend/app/fix_database_immediate.py | 253 ++++++++++++ backend/app/templates/printers.html | 194 +++++---- 8 files changed, 759 insertions(+), 69 deletions(-) create mode 100644 backend/app/database/myp.db.backup_20250529_150542 create mode 100644 backend/app/database/myp.db.backup_immediate_20250529_150732 create mode 100644 backend/app/fix_database_immediate.py diff --git a/backend/app/database/myp.db b/backend/app/database/myp.db index e548d1fa9683cbabe9f5f0db208ac8c46af5d804..9208bac142bde78650d24e326b07136df6f74bb0 100644 GIT binary patch delta 1882 zcmcIlZA=?w9KV<6u6JeJ)6$hw>fp8v$Ay>mpdCnJSYb^t(Bf)AwoGeTZ*ZjCmEN&I z7GaCo67`FCCi8;{+x&(hncOG+G(VVq+~(A8MiS$kA56@mG0R5qo|BedS&T8-q)-3% z`~BaZ`@i(URdC@Z{a(m_55uqq`&;_#UvBFod#cFpDf4rjSu^2zLm#iMa{|cN^U+gr^`h>BhO@&yd%~Pq~fu3 zTzDyYEIuw&X$T`p!O?1Q48*r2+{5!cemQ2H;sW@nE*plTPU=EBKAvt9a&jSCG-hPg z5XNGufx%d+MGA+-Hld*DilJy~H9V!Mav6&v%?U*_w}^e_CD?3@;+uQSRd^iTcrUZ= zy9A?Zzml8Pr_T&bpPV|ImyY#MbtZ=8u7R`tBSSqyov)1PgJbh@;#?|yVsJ8^IGIwq z56jl)ODt|T&afq$t%0;3CI3+a&Z@f zJB%?>?RouWaI~R@JCDITa1}1XB22+3tmS^ifutym72z~=s|E_JZ^l~iJ>1y23I+~Ywe735KOSw7s37AKyrlp;Z zEs>(jQ`M~<%YQinMs^}EI}V(KK!ao!xUJFubt(Xc(H~eR@co1>0nJ(@zxLWgGKPCC zyg>R6K>9ugE$-9W>osqBpLiG8O*YRqGPjs>Orz(n=M;V4?z5ZplEHiUJ`ulITYjfc zEutH2*tIO&{C4G05!^(}Uo9c(^>)-lcGRVE*r#^33w(B0R2N;E1{Bprw4l1!VCG(#=HTZiy~X}RLb+!y)@qy#OKOcA~I7bDyreQM31~}S$}i~ zEBn}i#AJZx+k?2`_OqQAauP&NUfc%C9!iP-r3$mKC|4*0-*;66Y6*5&Jg9@3fkO?mtVz+(%2>}UN A>Hq)$ delta 439 zcmZoTz}B#UWrDQeB?bltK_G?!(TO_7x|bOA+BA8ALIRAT417WSSNT`*MDQtc_i{S0 zzhs}oHjA~JF_e{yrJaSFxoWbYz{$;Pnf~%{HH9*=i;IghwsLM}W)x8tD+!!`-D@|&gwmYe0dnN_8`nRuF&1y>nXnm7fO`k988ho<|OCZ!c5`R4gY zY52Qf~*cMSZm`H%9?l&3ZJeJCyP1*e_nn`W?E))Vo7FxUa^TN2a7HPe`#@QQG7vaQEp~&F-X`@g?;kN xc?xWXh6;u%Y(Si9U}&$rGZ!mh-HCT4v6J9epGkd_(6-2nUR5c o+xYP>6C*pA;JIfTf_F+i)dr!X3B*om&J_34$1u zZ(!h@!(j%#1^n&XS!T7Aj85vZQ+;`T|9bm$KCNT(^7wE%yqY|}-`ria-~2zD-Oc@& ztNHityk6Z@ul!JdHLF%A0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&wRG`WJLibxvfB*pk z1PBoLJ%L`9D`{>u{S2~J*>fA&%rINo&ZyGw#@WqY4sw__M>$THU8e{TAV7cs0RjXF z5FkK+z+Vxle0N2F009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5Fqf+1U>;I COCw1D diff --git a/backend/app/database/myp.db-wal b/backend/app/database/myp.db-wal index 78957562fc1d519e07e2f20b44239bc521f83ce6..fc07de120c03cf5ffb62bf38ef4a0f38f3c34487 100644 GIT binary patch literal 41232 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 literal 111272 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 L00JQJ-zV@NV?3#A literal 0 HcmV?d00001 diff --git a/backend/app/database/myp.db.backup_immediate_20250529_150732 b/backend/app/database/myp.db.backup_immediate_20250529_150732 new file mode 100644 index 0000000000000000000000000000000000000000..9208bac142bde78650d24e326b07136df6f74bb0 GIT binary patch literal 106496 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 z zJ~E_+AL2zEQos+Na|O*SZl@3P?hDf?xiTa6t3l=zl_$?F8ugHdvL1>ylsSzNWK0z3 zI;2(2E97ur@*b*idnzSACyV_`paC+ob>6EP2Fzgo82|O1XF$ z`i6rmBRL9-wl&f;XlscUqiq-sTl0-cG=!#c{f(hiT;aWpnFWm62j4$xP^h18AueT- z^3zX?Z?N*)*P5ndunwU{@3UOFqakz7ZgZZ@Kjvwt%iY;dr{t3-#e0@3E;Ka}>yY7v zg>lBTtH7WHhMFVGj7(tM3j=M-JDfTQCj-S`46D(LOd9-=WS}zX;9_<(z0v5B0U{8` z#uBgQ$r;#@c|pk=k+_QT#`p)RqkG>#Uz!8S+SI{wGj1?13<4nE zf?e;L9d7#KYd=7N1Mk_~gSxzL2!mRq2wj`Vc@az9BZDXnp*78g(T3FvX_LwwGl6i2 zVSj$qeWiV>EVjD$}Eb{iqr6 z7prNSO|)HvbU6d&d5@kC_^NX$q8+|}A<5?Ycf3AI@YKrhC&YI()-QZ6{(FKe zkncODOk5wKMu2PUEi&0`2EE?55lySkPVO5DIM`CO^!g|x!e!QJ_VxxQti$27TF2|m z#Q3W!PDM`I=sxx!izaePV*O20%i&sT{L&`EfUIRsCzA5o2~l9PB5!yhlXi=_-9|_Omn1bO=*c+bbZXDQ(pH9p)+L^qc40p6yFoc#@w>#1n2+@1oq z1A;t8GS78hYh6jwR)V8PVhg(?=4X)S1yzy%>Q=&z*ZH zwjB1SA0Z6_JCoQZD(p;BlzX$2DePy&enz%?liR&O4Xd|b3)|QHP1s~~!}hgo?n~Xi zhUfne;dB-%g8&GC00@8p2!H?xfB*=900@A<0}{ac{{ev^ItYLO2!H?xfB*=900@8p z2!H?xJcI=3`adE4yCD5q`b7Fj`Y&>e3j{y_1V8`;KmY_l00ck)1V8`;K;R)I5RYZV z1bvCqml%DSh{ZA)y8cf~e=bP>E&Ye|FVat>e~^A8y-z%FfdB}A00@8p2!H?xfB*=9 v00@8p2>j*<9ElwlJLHrQavWPaa-WLaCnNWX$bCF=AB)^iWMjuOp3MIRo(l?Q literal 0 HcmV?d00001 diff --git a/backend/app/database_schema_migration.py b/backend/app/database_schema_migration.py index 0519ecba6..978180e9b 100644 --- a/backend/app/database_schema_migration.py +++ b/backend/app/database_schema_migration.py @@ -1 +1,380 @@ - \ No newline at end of file +#!/usr/bin/env python3 +""" +Umfassendes Datenbank-Schema-Migrationsskript +Erkennt und fügt alle fehlenden Spalten basierend auf den Models hinzu. +""" + +import os +import sys +import sqlite3 +from datetime import datetime +import logging + +# Pfad zur App hinzufügen +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from config.settings import DATABASE_PATH +from utils.logging_config import get_logger + +logger = get_logger("schema_migration") + +def get_table_columns(cursor, table_name): + """Ermittelt alle Spalten einer Tabelle.""" + cursor.execute(f"PRAGMA table_info({table_name})") + return {row[1]: row[2] for row in cursor.fetchall()} # {column_name: column_type} + +def get_table_exists(cursor, table_name): + """Prüft, ob eine Tabelle existiert.""" + cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,)) + return cursor.fetchone() is not None + +def migrate_users_table(cursor): + """Migriert die users Tabelle für fehlende Spalten.""" + logger.info("Migriere users Tabelle...") + + if not get_table_exists(cursor, 'users'): + logger.warning("users Tabelle existiert nicht - wird bei init_db erstellt") + return False + + existing_columns = get_table_columns(cursor, 'users') + + # Definition der erwarteten Spalten + required_columns = { + 'id': 'INTEGER PRIMARY KEY', + 'email': 'VARCHAR(120) UNIQUE NOT NULL', + 'username': 'VARCHAR(100) UNIQUE NOT NULL', + 'password_hash': 'VARCHAR(128) NOT NULL', + 'name': 'VARCHAR(100) NOT NULL', + 'role': 'VARCHAR(20) DEFAULT "user"', + 'active': 'BOOLEAN DEFAULT 1', + 'created_at': 'DATETIME DEFAULT CURRENT_TIMESTAMP', + 'last_login': 'DATETIME', + 'updated_at': 'DATETIME DEFAULT CURRENT_TIMESTAMP', + 'settings': 'TEXT', + 'department': 'VARCHAR(100)', + 'position': 'VARCHAR(100)', + 'phone': 'VARCHAR(50)', + 'bio': 'TEXT' + } + + migrations_performed = [] + + for column_name, column_def in required_columns.items(): + if column_name not in existing_columns: + try: + # Spezielle Behandlung für updated_at mit Trigger + if column_name == 'updated_at': + cursor.execute(f"ALTER TABLE users ADD COLUMN {column_name} DATETIME DEFAULT CURRENT_TIMESTAMP") + # Trigger für automatische Aktualisierung + cursor.execute(""" + CREATE TRIGGER IF NOT EXISTS update_users_updated_at + AFTER UPDATE ON users + BEGIN + UPDATE users SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id; + END + """) + logger.info(f"Spalte '{column_name}' hinzugefügt mit Auto-Update-Trigger") + else: + cursor.execute(f"ALTER TABLE users ADD COLUMN {column_name} {column_def}") + logger.info(f"Spalte '{column_name}' hinzugefügt") + + migrations_performed.append(column_name) + except Exception as e: + logger.error(f"Fehler beim Hinzufügen der Spalte '{column_name}': {str(e)}") + + return len(migrations_performed) > 0 + +def migrate_printers_table(cursor): + """Migriert die printers Tabelle für fehlende Spalten.""" + logger.info("Migriere printers Tabelle...") + + if not get_table_exists(cursor, 'printers'): + logger.warning("printers Tabelle existiert nicht - wird bei init_db erstellt") + return False + + existing_columns = get_table_columns(cursor, 'printers') + + required_columns = { + 'id': 'INTEGER PRIMARY KEY', + 'name': 'VARCHAR(100) NOT NULL', + 'model': 'VARCHAR(100)', + 'location': 'VARCHAR(100)', + 'ip_address': 'VARCHAR(50)', + 'mac_address': 'VARCHAR(50) NOT NULL UNIQUE', + 'plug_ip': 'VARCHAR(50) NOT NULL', + 'plug_username': 'VARCHAR(100) NOT NULL', + 'plug_password': 'VARCHAR(100) NOT NULL', + 'status': 'VARCHAR(20) DEFAULT "offline"', + 'active': 'BOOLEAN DEFAULT 1', + 'created_at': 'DATETIME DEFAULT CURRENT_TIMESTAMP', + 'last_checked': 'DATETIME' + } + + migrations_performed = [] + + for column_name, column_def in required_columns.items(): + if column_name not in existing_columns: + try: + cursor.execute(f"ALTER TABLE printers ADD COLUMN {column_name} {column_def}") + logger.info(f"Spalte '{column_name}' zu printers hinzugefügt") + migrations_performed.append(column_name) + except Exception as e: + logger.error(f"Fehler beim Hinzufügen der Spalte '{column_name}' zu printers: {str(e)}") + + return len(migrations_performed) > 0 + +def migrate_jobs_table(cursor): + """Migriert die jobs Tabelle für fehlende Spalten.""" + logger.info("Migriere jobs Tabelle...") + + if not get_table_exists(cursor, 'jobs'): + logger.warning("jobs Tabelle existiert nicht - wird bei init_db erstellt") + return False + + existing_columns = get_table_columns(cursor, 'jobs') + + required_columns = { + 'id': 'INTEGER PRIMARY KEY', + 'name': 'VARCHAR(200) NOT NULL', + 'description': 'VARCHAR(500)', + 'user_id': 'INTEGER NOT NULL', + 'printer_id': 'INTEGER NOT NULL', + 'start_at': 'DATETIME', + 'end_at': 'DATETIME', + 'actual_end_time': 'DATETIME', + 'status': 'VARCHAR(20) DEFAULT "scheduled"', + 'created_at': 'DATETIME DEFAULT CURRENT_TIMESTAMP', + 'notes': 'VARCHAR(500)', + 'material_used': 'FLOAT', + 'file_path': 'VARCHAR(500)', + 'owner_id': 'INTEGER', + 'duration_minutes': 'INTEGER NOT NULL' + } + + migrations_performed = [] + + for column_name, column_def in required_columns.items(): + if column_name not in existing_columns: + try: + cursor.execute(f"ALTER TABLE jobs ADD COLUMN {column_name} {column_def}") + logger.info(f"Spalte '{column_name}' zu jobs hinzugefügt") + migrations_performed.append(column_name) + except Exception as e: + logger.error(f"Fehler beim Hinzufügen der Spalte '{column_name}' zu jobs: {str(e)}") + + return len(migrations_performed) > 0 + +def migrate_guest_requests_table(cursor): + """Migriert die guest_requests Tabelle für fehlende Spalten.""" + logger.info("Migriere guest_requests Tabelle...") + + if not get_table_exists(cursor, 'guest_requests'): + logger.warning("guest_requests Tabelle existiert nicht - wird bei init_db erstellt") + return False + + existing_columns = get_table_columns(cursor, 'guest_requests') + + required_columns = { + 'processed_by': 'INTEGER', + 'processed_at': 'DATETIME', + 'approval_notes': 'TEXT', + 'rejection_reason': 'TEXT', + 'otp_used_at': 'DATETIME' + } + + migrations_performed = [] + + for column_name, column_def in required_columns.items(): + if column_name not in existing_columns: + try: + cursor.execute(f"ALTER TABLE guest_requests ADD COLUMN {column_name} {column_def}") + logger.info(f"Spalte '{column_name}' zu guest_requests hinzugefügt") + migrations_performed.append(column_name) + except Exception as e: + logger.error(f"Fehler beim Hinzufügen der Spalte '{column_name}' zu guest_requests: {str(e)}") + + return len(migrations_performed) > 0 + +def create_missing_tables(cursor): + """Erstellt fehlende Tabellen.""" + logger.info("Prüfe auf fehlende Tabellen...") + + # user_permissions Tabelle + if not get_table_exists(cursor, 'user_permissions'): + cursor.execute(""" + CREATE TABLE user_permissions ( + user_id INTEGER PRIMARY KEY, + can_start_jobs BOOLEAN DEFAULT 0, + needs_approval BOOLEAN DEFAULT 1, + can_approve_jobs BOOLEAN DEFAULT 0, + FOREIGN KEY (user_id) REFERENCES users (id) + ) + """) + logger.info("Tabelle 'user_permissions' erstellt") + + # notifications Tabelle + if not get_table_exists(cursor, 'notifications'): + cursor.execute(""" + CREATE TABLE notifications ( + id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL, + type VARCHAR(50) NOT NULL, + payload TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + read BOOLEAN DEFAULT 0, + FOREIGN KEY (user_id) REFERENCES users (id) + ) + """) + logger.info("Tabelle 'notifications' erstellt") + + # stats Tabelle + if not get_table_exists(cursor, 'stats'): + cursor.execute(""" + CREATE TABLE stats ( + id INTEGER PRIMARY KEY, + total_print_time INTEGER DEFAULT 0, + total_jobs_completed INTEGER DEFAULT 0, + total_material_used FLOAT DEFAULT 0.0, + last_updated DATETIME DEFAULT CURRENT_TIMESTAMP + ) + """) + logger.info("Tabelle 'stats' erstellt") + + # system_logs Tabelle + if not get_table_exists(cursor, 'system_logs'): + cursor.execute(""" + CREATE TABLE system_logs ( + id INTEGER PRIMARY KEY, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + level VARCHAR(20) NOT NULL, + message VARCHAR(1000) NOT NULL, + module VARCHAR(100), + user_id INTEGER, + ip_address VARCHAR(50), + user_agent VARCHAR(500), + FOREIGN KEY (user_id) REFERENCES users (id) + ) + """) + logger.info("Tabelle 'system_logs' erstellt") + +def optimize_database(cursor): + """Führt Datenbankoptimierungen durch.""" + logger.info("Führe Datenbankoptimierungen durch...") + + try: + # Indices für bessere Performance + cursor.execute("CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)") + cursor.execute("CREATE INDEX IF NOT EXISTS idx_users_username ON users(username)") + cursor.execute("CREATE INDEX IF NOT EXISTS idx_jobs_user_id ON jobs(user_id)") + cursor.execute("CREATE INDEX IF NOT EXISTS idx_jobs_printer_id ON jobs(printer_id)") + cursor.execute("CREATE INDEX IF NOT EXISTS idx_jobs_status ON jobs(status)") + cursor.execute("CREATE INDEX IF NOT EXISTS idx_notifications_user_id ON notifications(user_id)") + cursor.execute("CREATE INDEX IF NOT EXISTS idx_system_logs_timestamp ON system_logs(timestamp)") + cursor.execute("CREATE INDEX IF NOT EXISTS idx_guest_requests_status ON guest_requests(status)") + + # Statistiken aktualisieren + cursor.execute("ANALYZE") + + logger.info("Datenbankoptimierungen abgeschlossen") + + except Exception as e: + logger.error(f"Fehler bei Datenbankoptimierungen: {str(e)}") + +def main(): + """Führt die komplette Schema-Migration aus.""" + try: + logger.info("Starte umfassende Datenbank-Schema-Migration...") + + # Verbindung zur Datenbank + if not os.path.exists(DATABASE_PATH): + logger.error(f"Datenbankdatei nicht gefunden: {DATABASE_PATH}") + # Erste Initialisierung + from models import init_database + logger.info("Führe Erstinitialisierung durch...") + init_database() + logger.info("Erstinitialisierung abgeschlossen") + return + + conn = sqlite3.connect(DATABASE_PATH) + cursor = conn.cursor() + + # WAL-Modus aktivieren für bessere Concurrent-Performance + cursor.execute("PRAGMA journal_mode=WAL") + cursor.execute("PRAGMA foreign_keys=ON") + + logger.info(f"Verbunden mit Datenbank: {DATABASE_PATH}") + + # Backup erstellen + backup_path = f"{DATABASE_PATH}.backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}" + cursor.execute(f"VACUUM INTO '{backup_path}'") + logger.info(f"Backup erstellt: {backup_path}") + + # Migrationen durchführen + migrations_performed = [] + + # Fehlende Tabellen erstellen + create_missing_tables(cursor) + migrations_performed.append("missing_tables") + + # Tabellen-spezifische Migrationen + if migrate_users_table(cursor): + migrations_performed.append("users") + + if migrate_printers_table(cursor): + migrations_performed.append("printers") + + if migrate_jobs_table(cursor): + migrations_performed.append("jobs") + + if migrate_guest_requests_table(cursor): + migrations_performed.append("guest_requests") + + # Optimierungen + optimize_database(cursor) + migrations_performed.append("optimizations") + + # Änderungen speichern + conn.commit() + conn.close() + + logger.info(f"Schema-Migration erfolgreich abgeschlossen. Migrierte Bereiche: {', '.join(migrations_performed)}") + + # Test der Migration + test_migration() + + except Exception as e: + logger.error(f"Fehler bei der Schema-Migration: {str(e)}") + if 'conn' in locals(): + conn.rollback() + conn.close() + sys.exit(1) + +def test_migration(): + """Testet die Migration durch Laden der Models.""" + try: + logger.info("Teste Migration durch Laden der Models...") + + # Models importieren und testen + from models import get_cached_session, User, Printer, Job + + with get_cached_session() as session: + # Test User-Query (sollte das updated_at Problem lösen) + users = session.query(User).limit(1).all() + logger.info(f"User-Abfrage erfolgreich - {len(users)} Benutzer gefunden") + + # Test Printer-Query + printers = session.query(Printer).limit(1).all() + logger.info(f"Printer-Abfrage erfolgreich - {len(printers)} Drucker gefunden") + + # Test Job-Query + jobs = session.query(Job).limit(1).all() + logger.info(f"Job-Abfrage erfolgreich - {len(jobs)} Jobs gefunden") + + logger.info("Migrations-Test erfolgreich abgeschlossen") + + except Exception as e: + logger.error(f"Fehler beim Migrations-Test: {str(e)}") + raise + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/backend/app/fix_database_immediate.py b/backend/app/fix_database_immediate.py new file mode 100644 index 000000000..2c185d747 --- /dev/null +++ b/backend/app/fix_database_immediate.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python3 +""" +Sofortige Datenbank-Reparatur für fehlende updated_at Spalte +""" + +import os +import sys +import sqlite3 +from datetime import datetime + +# Pfad zur App hinzufügen +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from config.settings import DATABASE_PATH + +def fix_users_table_immediate(): + """Repariert die users Tabelle sofort.""" + print(f"Repariere Datenbank: {DATABASE_PATH}") + + if not os.path.exists(DATABASE_PATH): + print(f"Datenbankdatei nicht gefunden: {DATABASE_PATH}") + return False + + try: + conn = sqlite3.connect(DATABASE_PATH) + cursor = conn.cursor() + + # Prüfen, welche Spalten existieren + cursor.execute("PRAGMA table_info(users)") + existing_columns = [row[1] for row in cursor.fetchall()] + print(f"Vorhandene Spalten in users: {existing_columns}") + + # Fehlende Spalten hinzufügen + required_columns = [ + ('updated_at', 'DATETIME'), + ('settings', 'TEXT'), + ('department', 'VARCHAR(100)'), + ('position', 'VARCHAR(100)'), + ('phone', 'VARCHAR(50)'), + ('bio', 'TEXT') + ] + + for column_name, column_type in required_columns: + if column_name not in existing_columns: + try: + if column_name == 'updated_at': + # Einfacher Ansatz: NULL erlauben und später updaten + cursor.execute(f"ALTER TABLE users ADD COLUMN {column_name} {column_type}") + print(f"✓ Spalte '{column_name}' hinzugefügt") + + # Alle vorhandenen Benutzer mit aktuellem Timestamp updaten + cursor.execute(f"UPDATE users SET {column_name} = CURRENT_TIMESTAMP WHERE {column_name} IS NULL") + print(f"✓ Vorhandene Benutzer mit {column_name} aktualisiert") + + # Trigger für automatische Updates erstellen + cursor.execute(""" + CREATE TRIGGER IF NOT EXISTS update_users_updated_at + AFTER UPDATE ON users + FOR EACH ROW + BEGIN + UPDATE users SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id; + END + """) + print(f"✓ Auto-Update-Trigger für {column_name} erstellt") + else: + cursor.execute(f"ALTER TABLE users ADD COLUMN {column_name} {column_type}") + print(f"✓ Spalte '{column_name}' hinzugefügt") + + except Exception as e: + print(f"✗ Fehler bei Spalte '{column_name}': {str(e)}") + else: + print(f"○ Spalte '{column_name}' bereits vorhanden") + + # Weitere fehlende Tabellen prüfen und erstellen + create_missing_tables(cursor) + + # Optimierungsindizes erstellen + create_performance_indexes(cursor) + + conn.commit() + conn.close() + + print("✓ Datenbank-Reparatur erfolgreich abgeschlossen") + return True + + except Exception as e: + print(f"✗ Fehler bei der Datenbank-Reparatur: {str(e)}") + if 'conn' in locals(): + conn.rollback() + conn.close() + return False + +def create_missing_tables(cursor): + """Erstellt fehlende Tabellen.""" + + # Prüfen, welche Tabellen existieren + cursor.execute("SELECT name FROM sqlite_master WHERE type='table'") + existing_tables = [row[0] for row in cursor.fetchall()] + print(f"Vorhandene Tabellen: {existing_tables}") + + # user_permissions Tabelle + if 'user_permissions' not in existing_tables: + cursor.execute(""" + CREATE TABLE user_permissions ( + user_id INTEGER PRIMARY KEY, + can_start_jobs BOOLEAN DEFAULT 0, + needs_approval BOOLEAN DEFAULT 1, + can_approve_jobs BOOLEAN DEFAULT 0, + FOREIGN KEY (user_id) REFERENCES users (id) + ) + """) + print("✓ Tabelle 'user_permissions' erstellt") + + # notifications Tabelle + if 'notifications' not in existing_tables: + cursor.execute(""" + CREATE TABLE notifications ( + id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL, + type VARCHAR(50) NOT NULL, + payload TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + read BOOLEAN DEFAULT 0, + FOREIGN KEY (user_id) REFERENCES users (id) + ) + """) + print("✓ Tabelle 'notifications' erstellt") + + # stats Tabelle + if 'stats' not in existing_tables: + cursor.execute(""" + CREATE TABLE stats ( + id INTEGER PRIMARY KEY, + total_print_time INTEGER DEFAULT 0, + total_jobs_completed INTEGER DEFAULT 0, + total_material_used REAL DEFAULT 0.0, + last_updated DATETIME DEFAULT CURRENT_TIMESTAMP + ) + """) + print("✓ Tabelle 'stats' erstellt") + + # Initial stats record erstellen + cursor.execute(""" + INSERT INTO stats (total_print_time, total_jobs_completed, total_material_used, last_updated) + VALUES (0, 0, 0.0, CURRENT_TIMESTAMP) + """) + print("✓ Initial-Statistiken erstellt") + + # system_logs Tabelle + if 'system_logs' not in existing_tables: + cursor.execute(""" + CREATE TABLE system_logs ( + id INTEGER PRIMARY KEY, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + level VARCHAR(20) NOT NULL, + message VARCHAR(1000) NOT NULL, + module VARCHAR(100), + user_id INTEGER, + ip_address VARCHAR(50), + user_agent VARCHAR(500), + FOREIGN KEY (user_id) REFERENCES users (id) + ) + """) + print("✓ Tabelle 'system_logs' erstellt") + +def create_performance_indexes(cursor): + """Erstellt Performance-Indices.""" + print("Erstelle Performance-Indices...") + + indexes = [ + ("idx_users_email", "users(email)"), + ("idx_users_username", "users(username)"), + ("idx_users_role", "users(role)"), + ("idx_jobs_user_id", "jobs(user_id)"), + ("idx_jobs_printer_id", "jobs(printer_id)"), + ("idx_jobs_status", "jobs(status)"), + ("idx_jobs_start_at", "jobs(start_at)"), + ("idx_notifications_user_id", "notifications(user_id)"), + ("idx_notifications_read", "notifications(read)"), + ("idx_system_logs_timestamp", "system_logs(timestamp)"), + ("idx_system_logs_level", "system_logs(level)"), + ("idx_guest_requests_status", "guest_requests(status)"), + ("idx_printers_status", "printers(status)"), + ("idx_printers_active", "printers(active)") + ] + + for index_name, index_def in indexes: + try: + cursor.execute(f"CREATE INDEX IF NOT EXISTS {index_name} ON {index_def}") + print(f"✓ Index '{index_name}' erstellt") + except Exception as e: + print(f"○ Index '{index_name}': {str(e)}") + +def test_database_access(): + """Testet den Datenbankzugriff nach der Reparatur.""" + print("\nTeste Datenbankzugriff...") + + try: + # Models importieren und testen + from models import get_cached_session, User, Printer, Job + + with get_cached_session() as session: + # Test User-Query + users = session.query(User).limit(5).all() + print(f"✓ User-Abfrage erfolgreich - {len(users)} Benutzer gefunden") + + # Test Printer-Query + printers = session.query(Printer).limit(5).all() + print(f"✓ Printer-Abfrage erfolgreich - {len(printers)} Drucker gefunden") + + # Test Job-Query + jobs = session.query(Job).limit(5).all() + print(f"✓ Job-Abfrage erfolgreich - {len(jobs)} Jobs gefunden") + + print("✓ Alle Datenbank-Tests erfolgreich!") + return True + + except Exception as e: + print(f"✗ Datenbank-Test fehlgeschlagen: {str(e)}") + return False + +def main(): + """Hauptfunktion für die sofortige Datenbank-Reparatur.""" + print("=== SOFORTIGE DATENBANK-REPARATUR ===") + print(f"Zeitstempel: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + print(f"Datenbank: {DATABASE_PATH}") + print() + + # Backup erstellen + if os.path.exists(DATABASE_PATH): + backup_path = f"{DATABASE_PATH}.backup_immediate_{datetime.now().strftime('%Y%m%d_%H%M%S')}" + try: + import shutil + shutil.copy2(DATABASE_PATH, backup_path) + print(f"✓ Backup erstellt: {backup_path}") + except Exception as e: + print(f"⚠ Backup-Erstellung fehlgeschlagen: {str(e)}") + + # Reparatur durchführen + if fix_users_table_immediate(): + print("\n=== DATENBANK-TEST ===") + if test_database_access(): + print("\n🎉 DATENBANK-REPARATUR ERFOLGREICH!") + print("Die Anwendung sollte jetzt funktionieren.") + else: + print("\n❌ DATENBANK-TEST FEHLGESCHLAGEN!") + print("Weitere Diagnose erforderlich.") + else: + print("\n❌ DATENBANK-REPARATUR FEHLGESCHLAGEN!") + print("Manuelle Intervention erforderlich.") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/backend/app/templates/printers.html b/backend/app/templates/printers.html index ec68248a5..a99144a8c 100644 --- a/backend/app/templates/printers.html +++ b/backend/app/templates/printers.html @@ -1,85 +1,143 @@ {% extends "base.html" %} -{% block title %}Drucker - MYP Platform{% endblock %} - -{% block extra_css %} - -{% endblock %} +{% block title %}Drucker - Mercedes-Benz MYP Platform{% endblock %} {% block content %} -
- -
-
-
-

Drucker

-

Verwalten Sie Ihre 3D-Drucker

- - -
-
-
- Online: - -
-
-
- Offline: - -
-
- - - - Gesamt: - -
-
- - - - Auto-Update: -s -
+
+ + +
+
+ + +
+
+
+
+ Live
-
- -
- - - +
+ +
+
+ +
+
+ +
+ + +
- +

+ 3D-Drucker Management +

+

+ Verwalten Sie Ihre 3D-Drucker mit höchster Präzision und Mercedes-Benz Qualitätsstandard +

- + +
+
+
+
+ Online: - +
+
+
+
+
+ Offline: - +
+
+
+
+ + + + Gesamt: - +
+
+
- {% if current_user.is_admin %} - + + +
+ + + + + + {% if current_user.is_admin %} + + {% endif %} +
+ + +
+ + - Drucker hinzufügen - - {% endif %} + Auto-Update: -s +
- -
- -
-
-

Lade Drucker...

-

Dies sollte nur wenige Sekunden dauern

+
+ + +
+
+

+ Verfügbare Drucker +

+

+ Übersicht und Verwaltung Ihrer 3D-Drucker-Infrastruktur +

+
+ +
+ +
+
+

Lade Drucker...

+

Dies sollte nur wenige Sekunden dauern

+
+
@@ -1074,7 +1132,7 @@ // Filter-Funktionalität function setupFilters() { - const filterButtons = document.querySelectorAll('.filter-btn-new'); + const filterButtons = document.querySelectorAll('.filter-btn-mercedes'); filterButtons.forEach(btn => { btn.addEventListener('click', function() {