diff --git a/README.md b/README.md index f5da249..4f9cdea 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,48 @@ -# sqlite-analyzer -A web sqlite analyzer +# SQLite Analyzer + +This project is a simple web service that allows generating and displaying an ER model for a SQLite database. The ER model is created as a PNG image to visualize the relationships between tables in the database. + +## Requirements + +To run this project, you need: + +- Python 3 installed on your system. +- The Python libraries Flask and Graphviz. You can install them via pip: + + ```bash + pip install Flask graphviz + ``` + +## Usage + +1. Clone or download the project to your local computer. +2. Navigate to the project directory. +3. Start the application by running the `app.py` file: + + ```bash + python app.py + ``` + +4. Open a web browser and go to the URL [http://127.0.0.1:5000/](http://127.0.0.1:5000/). +5. Upload a SQLite database file (`.db` file extension). +6. The ER model will be automatically generated and displayed along with table data. + +## Features + +- **ER Model Generation**: The ER model is automatically generated from the uploaded SQLite database and displayed as a PNG image. +- **Display of Table Data**: The data of each table in the uploaded SQLite database is displayed to facilitate quick data inspection. + +## File Structure + +- `app.py`: The main application that creates the web service using Flask. +- `uploads/`: The directory where uploaded SQLite database files are stored. +- `templates/`: The directory containing HTML templates for the web pages. +- `static/`: The directory containing static files such as CSS or images. + +## Contributors + +- **Author**: fingadumbledore +## License + +This project is licensed under the [MIT License](https://opensource.org/licenses/MIT). + diff --git a/demo_tables/1000.db b/demo_tables/1000.db new file mode 100644 index 0000000..b56ba2f Binary files /dev/null and b/demo_tables/1000.db differ diff --git a/demo_tables/20x.db b/demo_tables/20x.db new file mode 100644 index 0000000..fbfec6e Binary files /dev/null and b/demo_tables/20x.db differ diff --git a/demo_tables/firma.db b/demo_tables/firma.db new file mode 100644 index 0000000..085fcb1 Binary files /dev/null and b/demo_tables/firma.db differ diff --git a/er_model.png b/er_model.png new file mode 100644 index 0000000..9290b1f Binary files /dev/null and b/er_model.png differ diff --git a/main.py b/main.py new file mode 100644 index 0000000..0049cfd --- /dev/null +++ b/main.py @@ -0,0 +1,116 @@ +from flask import Flask, render_template, request, send_file +import sqlite3 +import os +from graphviz import Digraph + +app = Flask(__name__) + +# Verzeichnis für hochgeladene Dateien +UPLOAD_FOLDER = 'uploads' +app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER + +def create_er_model(database_file, output_file='er_model.png'): + """Erstellt ein ER-Modell für die angegebene SQLite-Datenbank und speichert es als Bild.""" + conn = sqlite3.connect(database_file) + c = conn.cursor() + c.execute("SELECT name FROM sqlite_master WHERE type='table';") + tables = c.fetchall() + + # Graphviz-Diagramm erstellen + dot = Digraph() + + # Dictionary zur Speicherung der Fremdschlüsselbeziehungen + foreign_keys = {} + + # Für jede Tabelle Informationen über Spalten abrufen und dem Diagramm hinzufügen + for table in tables: + table_name = table[0] + dot.node(table_name, shape='rectangle', color='lightblue2', style='filled') + + # Spalten der Tabelle abrufen + c.execute(f"PRAGMA table_info({table_name});") + columns = c.fetchall() + + # Jede Spalte als Knoten im Diagramm hinzufügen + for column in columns: + column_name = column[1] + dot.node(f"{table_name}.{column_name}", label=column_name, shape='ellipse') + + # Verbindung von Tabelle zu Spalte erstellen + dot.edge(table_name, f"{table_name}.{column_name}") + + # Fremdschlüsselbeziehungen abrufen + c.execute(f"PRAGMA foreign_key_list({table_name});") + foreign_keys[table_name] = c.fetchall() + + # Fremdschlüsselbeziehungen als Kanten im Diagramm hinzufügen + for table, fks in foreign_keys.items(): + for fk in fks: + parent_table = fk[2] + parent_column = fk[3] + child_table = table + child_column = fk[4] + dot.edge(f"{parent_table}.{parent_column}", f"{child_table}.{child_column}", label='1..n') + + # ER-Modell als Graphviz-Dot-Datei speichern + dot.render(output_file, format='png', cleanup=True) + + # Verbindung schließen + conn.close() + + return output_file + +# Route für den Index +@app.route('/', methods=['GET', 'POST']) +def index(): + error = None + er_model = None + table_data = None + + if request.method == 'POST': + # Überprüfen, ob eine Datei hochgeladen wurde + if 'file' not in request.files: + error = 'Keine Datei hochgeladen' + else: + file = request.files['file'] + + # Überprüfen, ob eine Datei ausgewählt wurde + if file.filename == '': + error = 'Keine Datei ausgewählt' + + # Überprüfen, ob die Datei eine SQLite-Datenbank ist + elif file.filename.endswith('.db'): + # Datei speichern + file_path = os.path.join(app.config['UPLOAD_FOLDER'], file.filename) + file.save(file_path) + + # ER-Modell erstellen + er_model_file = create_er_model(file_path) + os.rename("er_model.png.png", "er_model.png") + er_model = er_model_file + + # Tabellendaten abrufen + conn = sqlite3.connect(file_path) + cursor = conn.cursor() + cursor.execute("SELECT name FROM sqlite_master WHERE type='table';") + tables = cursor.fetchall() + table_data = {} + for table in tables: + table_name = table[0] + cursor.execute(f"SELECT * FROM {table_name};") + table_data[table_name] = cursor.fetchall() + conn.close() + + else: + error = 'Die hochgeladene Datei muss eine SQLite-Datenbankdatei sein' + + # HTML-Seite mit den Daten anzeigen + return render_template('index.html', error=error, er_model=er_model, table_data=table_data) + +# Route zum Anzeigen des ER-Modells +@app.route('/er_model/') +def show_er_model(filename): + return send_file(os.path.abspath(filename), mimetype='image/png') + +if __name__ == '__main__': + app.run(debug=True) diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 0000000..fb3794a --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,197 @@ +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + background-color: #f9f9f9; + color: #333333; + transition: background-color 0.3s ease, color 0.3s ease; + position: relative; +} + +body.dark-mode { + background-color: #333333; + color: #ffffff; +} + +header { + background-color: #007bff; + color: #ffffff; + text-align: center; + padding: 20px 0; +} + +main { + max-width: 1200px; + margin: 0 auto; + padding: 20px; +} + +h1, h2 { + color: inherit; +} +body.dark-mode table { + background-color: #222222; + color: #ffffff; + } + + body.dark-mode th { + background-color: #333333; + } + + body.dark-mode tr:nth-child(even) { + background-color: #444444; + } + + body.dark-mode tr:nth-child(odd) { + background-color: #333333; + } + + body.dark-mode td { + border-color: #555555; + } + +table { + border-collapse: collapse; + width: 100%; + margin-bottom: 20px; + background-color: #ffffff; + border-radius: 10px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); +} + +th, td { + border: 1px solid #dddddd; + text-align: left; + padding: 8px; +} + +th { + background-color: #f2f2f2; +} + +img { + max-width: 100%; + height: auto; + margin-top: 20px; + border-radius: 10px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); +} + +form { + margin: 20px auto; + padding: 20px; + background-color: #ffffff; + border-radius: 10px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + animation: fadeIn 0.5s ease; + transition: max-height 0.5s ease; + max-height: 500px; + overflow: hidden; +} + +form.closed { + max-height: 0; + padding: 0; + margin: 0; + overflow: hidden; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +input[type="file"] { + display: block; + margin: 20px 0; + width: 100%; + padding: 10px; + border: 2px solid #007bff; + border-radius: 10px; + font-size: 16px; + color: #333333; +} + +input[type="submit"] { + display: none; +} + +.button { + display: inline-block; + background-color: #007bff; + color: #ffffff; + border: none; + padding: 10px 20px; + border-radius: 5px; + cursor: pointer; + font-size: 16px; + transition: background-color 0.3s ease; + margin-bottom: 10px; +} + +.button:hover { + background-color: #0056b3; +} + +.dark-mode-button { + position: absolute; + top: 10px; + right: 10px; + background-color: transparent; + font-size: 44px; + border: none; + cursor: pointer; +} + +.dark-mode-button img { + width: 20px; + height: auto; +} +#dropArea { + border: 2px dashed #007bff; + border-radius: 10px; + padding: 20px; + margin: 20px auto; + width: 80%; + text-align: center; + font-size: 1.2em; + cursor: pointer; +} + +#dropArea.highlight { + background-color: #f0f8ff; +} + +#sql_input{ + width: 100%; + height: 50%; + background-color: #d4d4d4; + color: #201d1d; + border-radius: 10px; + padding: 20px; + border: none; +} + +#execute_button{ + background-color:#007bff ; + display: inline-block; + border: none; + padding: 10px 20px; + border-radius: 5px; + cursor: pointer; + font-size: 16px; + transition: background-color 0.3s ease; + margin-bottom: 10px; + color: white; + +} + +#execute_button:hover { + background-color: #0056b3; +} \ No newline at end of file diff --git a/static/js/index.js b/static/js/index.js new file mode 100644 index 0000000..70d8950 --- /dev/null +++ b/static/js/index.js @@ -0,0 +1,43 @@ +function handleDragOver(event) { + event.preventDefault(); + event.dataTransfer.dropEffect = 'copy'; + document.getElementById('dropArea').classList.add('highlight'); +} + +function handleDragLeave(event) { + event.preventDefault(); + document.getElementById('dropArea').classList.remove('highlight'); +} + +function handleDrop(event) { + event.preventDefault(); + document.getElementById('dropArea').classList.remove('highlight'); + const files = event.dataTransfer.files; + handleFiles(files); +} + +function handleFiles(files) { + document.getElementById('uploadForm').file.files = files; + submitForm(); +} + +function submitForm() { + document.getElementById('uploadForm').submit(); +} + +function toggleForm() { + var form = document.getElementById('uploadForm'); + form.classList.toggle('closed'); + document.getElementById('toggleFormButton').textContent = form.classList.contains('closed') ? 'DB Hochladen' : 'ausblenden'; +} + +function toggleDarkMode() { + var body = document.body; + var button = document.querySelector('.dark-mode-button'); + body.classList.toggle('dark-mode'); + if (body.classList.contains('dark-mode')) { + button.textContent = '🌞'; + } else { + button.textContent = '🌙'; + } +} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..66cbfb3 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,66 @@ + + + + + + + SQL Analyzer + + +
+

SQLITE Analyzer

+
+ +
+
+ Drag & Drop Datei hierhin oder klicken, um hochzuladen. +
+
+ +
+ +
+ + {% if error %} +

{{ error }}

+ {% endif %} + + {% if table_data %} + {% for table_name, data in table_data.items() %} +

{{ table_name }}

+ + + {% for column_name in data[0] %} + + {% endfor %} + + {% for row in data %} + + {% for value in row %} + + {% endfor %} + + {% endfor %} +
{{ column_name }}
{{ value }}
+ {% endfor %} + + {% endif %} + + {% if er_model %} +

ER-Modell

+ ER-Modell + {% endif %} + + +
+
+ © fingadumbledore 2024 Version 0.1 +
+ + +
+ + + diff --git a/test.py b/test.py new file mode 100644 index 0000000..fe83909 --- /dev/null +++ b/test.py @@ -0,0 +1,124 @@ +from flask import Flask, render_template, request, send_file +import sqlite3 +import os +from graphviz import Digraph +import threading +from werkzeug.serving import WSGIRequestHandler, ThreadedMixIn +from werkzeug.debug import DebuggedApplication + +app = Flask(__name__) + +# Verzeichnis für hochgeladene Dateien +UPLOAD_FOLDER = 'uploads' +app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER + +class ThreadedWSGIRequestHandler(WSGIRequestHandler, ThreadedMixIn): + pass + +def create_er_model(database_file, output_file='er_model.png'): + """Erstellt ein ER-Modell für die angegebene SQLite-Datenbank und speichert es als Bild.""" + conn = sqlite3.connect(database_file) + c = conn.cursor() + c.execute("SELECT name FROM sqlite_master WHERE type='table';") + tables = c.fetchall() + + # Graphviz-Diagramm erstellen + dot = Digraph() + + # Dictionary zur Speicherung der Fremdschlüsselbeziehungen + foreign_keys = {} + + # Für jede Tabelle Informationen über Spalten abrufen und dem Diagramm hinzufügen + for table in tables: + table_name = table[0] + dot.node(table_name, shape='rectangle', color='lightblue2', style='filled') + + # Spalten der Tabelle abrufen + c.execute(f"PRAGMA table_info({table_name});") + columns = c.fetchall() + + # Jede Spalte als Knoten im Diagramm hinzufügen + for column in columns: + column_name = column[1] + dot.node(f"{table_name}.{column_name}", label=column_name, shape='ellipse') + + # Verbindung von Tabelle zu Spalte erstellen + dot.edge(table_name, f"{table_name}.{column_name}") + + # Fremdschlüsselbeziehungen abrufen + c.execute(f"PRAGMA foreign_key_list({table_name});") + foreign_keys[table_name] = c.fetchall() + + # Fremdschlüsselbeziehungen als Kanten im Diagramm hinzufügen + for table, fks in foreign_keys.items(): + for fk in fks: + parent_table = fk[2] + parent_column = fk[3] + child_table = table + child_column = fk[4] + dot.edge(f"{parent_table}.{parent_column}", f"{child_table}.{child_column}", label='1..n') + + # ER-Modell als Graphviz-Dot-Datei speichern + dot.render(output_file, format='png', cleanup=True) + + # Verbindung schließen + conn.close() + + return output_file + +def create_er_model_threaded(database_file, output_file='er_model.png'): + """Funktion zum Erstellen des ER-Modells in einem separaten Thread.""" + threading.Thread(target=create_er_model, args=(database_file, output_file)).start() + +# Route für den Index +@app.route('/', methods=['GET', 'POST']) +def index(): + error = None + er_model = None + table_data = None + + if request.method == 'POST': + # Überprüfen, ob eine Datei hochgeladen wurde + if 'file' not in request.files: + error = 'Keine Datei hochgeladen' + else: + file = request.files['file'] + + # Überprüfen, ob eine Datei ausgewählt wurde + if file.filename == '': + error = 'Keine Datei ausgewählt' + + # Überprüfen, ob die Datei eine SQLite-Datenbank ist + elif file.filename.endswith('.db'): + # Datei speichern + file_path = os.path.join(app.config['UPLOAD_FOLDER'], file.filename) + file.save(file_path) + + # ER-Modell erstellen im separaten Thread + create_er_model_threaded(file_path) + + # Tabellendaten abrufen + conn = sqlite3.connect(file_path) + cursor = conn.cursor() + cursor.execute("SELECT name FROM sqlite_master WHERE type='table';") + tables = cursor.fetchall() + table_data = {} + for table in tables: + table_name = table[0] + cursor.execute(f"SELECT * FROM {table_name};") + table_data[table_name] = cursor.fetchall() + conn.close() + + else: + error = 'Die hochgeladene Datei muss eine SQLite-Datenbankdatei sein' + + # HTML-Seite mit den Daten anzeigen + return render_template('index.html', error=error, er_model=er_model, table_data=table_data) + +# Route zum Anzeigen des ER-Modells +@app.route('/er_model/') +def show_er_model(filename): + return send_file(os.path.abspath(filename), mimetype='image/png') + +if __name__ == '__main__': + app.run(debug=True, threaded=True, request_handler=ThreadedWSGIRequestHandler) diff --git a/uploads/1000.db b/uploads/1000.db new file mode 100644 index 0000000..b56ba2f Binary files /dev/null and b/uploads/1000.db differ diff --git a/uploads/20x.db b/uploads/20x.db new file mode 100644 index 0000000..fbfec6e Binary files /dev/null and b/uploads/20x.db differ diff --git a/uploads/example.db b/uploads/example.db new file mode 100644 index 0000000..78e276d Binary files /dev/null and b/uploads/example.db differ diff --git a/uploads/firma.db b/uploads/firma.db new file mode 100644 index 0000000..085fcb1 Binary files /dev/null and b/uploads/firma.db differ