* Add SQL query executor
* Move mermaid.min.js to static asset * Create templates for sql logic
This commit is contained in:
33
app.py
33
app.py
@@ -469,21 +469,26 @@ def delete_exercise(exercise_id):
|
|||||||
db.exercises.delete_exercise(exercise_id)
|
db.exercises.delete_exercise(exercise_id)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
@app.route("/sql_explorer", methods=['GET'])
|
||||||
|
def sql_explorer():
|
||||||
|
if htmx:
|
||||||
|
return render_block(app.jinja_env, 'sql_explorer.html', 'content')
|
||||||
|
return render_template('sql_explorer.html')
|
||||||
|
|
||||||
|
@app.route("/sql_query", methods=['POST'])
|
||||||
|
def sql_query():
|
||||||
|
query = request.form.get('query')
|
||||||
|
(results, columns, error) = db.sql_explorer.execute_sql(query)
|
||||||
|
|
||||||
|
return render_template('partials/sql_explorer/sql_query.html', query=query, results=results, columns=columns, error=error)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ app.route("/sql_schema", methods=['GET'])
|
@ app.route("/sql_schema", methods=['GET'])
|
||||||
def get_sql_schema():
|
def sql_schema():
|
||||||
schema_info = db.sql_viewer.get_schema_info()
|
schema_info = db.sql_explorer.get_schema_info()
|
||||||
mermaid_code = db.sql_viewer.generate_mermaid_er(schema_info)
|
mermaid_code = db.sql_explorer.generate_mermaid_er(schema_info)
|
||||||
html_content = f'''
|
return render_template('partials/sql_explorer/schema.html', mermaid_code=mermaid_code)
|
||||||
<div class="overflow-auto" style="max-height: 80vh;">
|
|
||||||
<div class="mermaid" style="opacity: 0;"
|
|
||||||
_="on load
|
|
||||||
mermaid.init(undefined, this)
|
|
||||||
set me.style.opacity to '1'">
|
|
||||||
{mermaid_code}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
'''
|
|
||||||
return html_content
|
|
||||||
|
|
||||||
@app.teardown_appcontext
|
@app.teardown_appcontext
|
||||||
def closeConnection(exception):
|
def closeConnection(exception):
|
||||||
|
|||||||
4
db.py
4
db.py
@@ -10,7 +10,7 @@ from features.calendar import Calendar
|
|||||||
from features.exercises import Exercises
|
from features.exercises import Exercises
|
||||||
from features.stats import Stats
|
from features.stats import Stats
|
||||||
from features.workout import Workout
|
from features.workout import Workout
|
||||||
from features.sql_viewer import SQLViewer
|
from features.sql_explorer import SQLExplorer
|
||||||
from utils import count_prs_over_time, get_all_exercises_from_topsets, get_exercise_graph_model, get_stats_from_topsets, get_topsets_for_person, get_weekly_pr_graph_model, get_workout_counts, get_workouts
|
from utils import count_prs_over_time, get_all_exercises_from_topsets, get_exercise_graph_model, get_stats_from_topsets, get_topsets_for_person, get_weekly_pr_graph_model, get_workout_counts, get_workouts
|
||||||
|
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ class DataBase():
|
|||||||
self.stats = Stats(self.execute)
|
self.stats = Stats(self.execute)
|
||||||
self.workout = Workout(self.execute)
|
self.workout = Workout(self.execute)
|
||||||
self.exercises = Exercises(self.execute)
|
self.exercises = Exercises(self.execute)
|
||||||
self.sql_viewer = SQLViewer(self.execute)
|
self.sql_explorer = SQLExplorer(self.execute)
|
||||||
db_url = urlparse(os.environ['DATABASE_URL'])
|
db_url = urlparse(os.environ['DATABASE_URL'])
|
||||||
# if db_url is null then throw error
|
# if db_url is null then throw error
|
||||||
if not db_url:
|
if not db_url:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
class SQLViewer:
|
class SQLExplorer:
|
||||||
def __init__(self, db_connection_method):
|
def __init__(self, db_connection_method):
|
||||||
self.execute = db_connection_method
|
self.execute = db_connection_method
|
||||||
|
|
||||||
@@ -91,4 +91,21 @@ class SQLViewer:
|
|||||||
mermaid_lines.append(relation)
|
mermaid_lines.append(relation)
|
||||||
|
|
||||||
return "\n".join(mermaid_lines)
|
return "\n".join(mermaid_lines)
|
||||||
|
|
||||||
|
def execute_sql(self, query):
|
||||||
|
results = None
|
||||||
|
columns = []
|
||||||
|
error = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Use your custom execute method
|
||||||
|
results = self.execute(query)
|
||||||
|
if results:
|
||||||
|
# Extract column names from the keys of the first result
|
||||||
|
columns = list(results[0].keys())
|
||||||
|
except Exception as e:
|
||||||
|
error = str(e)
|
||||||
|
|
||||||
|
return (results, columns, error)
|
||||||
|
|
||||||
|
|
||||||
2029
static/js/mermaid.min.js
vendored
Normal file
2029
static/js/mermaid.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -17,7 +17,7 @@
|
|||||||
<script src="/static/js/hyperscript.min.js" defer></script>
|
<script src="/static/js/hyperscript.min.js" defer></script>
|
||||||
<script src="/static/js/sweetalert2@11.js" defer></script>
|
<script src="/static/js/sweetalert2@11.js" defer></script>
|
||||||
<!-- Mermaid -->
|
<!-- Mermaid -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
|
<script src="/static/js/mermaid.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// Initialize Mermaid with startOnLoad set to false
|
// Initialize Mermaid with startOnLoad set to false
|
||||||
mermaid.initialize({
|
mermaid.initialize({
|
||||||
@@ -123,6 +123,22 @@
|
|||||||
<span class="ml-3">Settings</span>
|
<span class="ml-3">Settings</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="space-y-2 pt-2">
|
||||||
|
<a hx-get="{{ url_for('sql_explorer') }}" hx-push-url="true" hx-target="#container"
|
||||||
|
class="text-base text-gray-900 font-normal rounded-lg hover:bg-gray-100 group transition duration-75 flex items-center p-2 cursor-pointer {{ is_selected_page(url_for('sql_explorer')) }} page-link"
|
||||||
|
_="on click add .hidden to #sidebar then remove .ml-64 from #main
|
||||||
|
on htmx:afterRequest go to the top of the body">
|
||||||
|
<svg class="w-6 h-6 text-gray-500 group-hover:text-gray-900 transition duration-75"
|
||||||
|
fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M2 5a2 2 0 012-2h12a2 2 0 012 2v1c0 1.105-1.343 2-3 2H5c-1.657 0-3-.895-3-2V5z" />
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M2 11v-1c0-1.105 1.343-2 3-2h10c1.657 0 3 .895 3 2v1c0 1.105-1.343 2-3 2H5c-1.657 0-3-.895-3-2zm0 4v-1c0-1.105 1.343-2 3-2h10c1.657 0 3 .895 3 2v1c0 1.105-1.343 2-3 2H5c-1.657 0-3-.895-3-2z"
|
||||||
|
clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
<span class="ml-3">SQL Explorer</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
7
templates/partials/sql_explorer/schema.html
Normal file
7
templates/partials/sql_explorer/schema.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<div class="overflow-auto" style="max-height: 80vh;">
|
||||||
|
<div class="mermaid" style="opacity: 0;" _="on load
|
||||||
|
mermaid.init(undefined, this)
|
||||||
|
set me.style.opacity to '1'">
|
||||||
|
{{ mermaid_code }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
37
templates/partials/sql_explorer/sql_query.html
Normal file
37
templates/partials/sql_explorer/sql_query.html
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<div id="sql-query">
|
||||||
|
{% if error %}
|
||||||
|
<div class="bg-red-200 text-red-800 p-3 rounded mb-4">
|
||||||
|
<strong>Error:</strong> {{ error }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form method="POST" hx-post="{{ url_for('sql_query') }}" hx-target="#sql-query">
|
||||||
|
<textarea name="query" rows="5" class="w-full p-2 border rounded mb-4"
|
||||||
|
placeholder="Enter your SQL query here..." required>{{ query }}</textarea>
|
||||||
|
<button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded">Execute</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="mt-6">
|
||||||
|
{% if results %}
|
||||||
|
<table class="min-w-full bg-white">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{% for col in columns %}
|
||||||
|
<th class="py-2 px-4 border-b">{{ col }}</th>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for row in results %}
|
||||||
|
<tr class="text-center">
|
||||||
|
{% for col in columns %}
|
||||||
|
<td class="py-2 px-4 border-b">{{ row[col] }}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -182,7 +182,6 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div hx-get="{{ url_for('get_sql_schema') }}" hx-trigger="load"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
18
templates/sql_explorer.html
Normal file
18
templates/sql_explorer.html
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="bg-white shadow rounded-lg p-4 sm:p-6 xl:p-8 mb-8">
|
||||||
|
<div class="mb-4 flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-xl font-bold text-gray-900 mb-2">SQL Explorer</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div hx-get="{{ url_for('sql_schema') }}" hx-trigger="load"></div>
|
||||||
|
|
||||||
|
{{ render_partial('partials/sql_explorer/sql_query.html') }}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user