Add home dashboard and Mithril rendering support

- Create new home route with comprehensive dashboard statistics
- Implement Mithril rendering support with new `mithril_loader.html` template
- Add new routes for home and test pages in `app.py`
- Create `lib/mithril.py` with Mithril rendering and error handling utilities
- Update dashboard template to use new home route
- Add detailed dashboard view with timer and HTTP function statistics
This commit is contained in:
Peter Stockings
2025-02-20 23:35:46 +11:00
parent 24a0c0ffef
commit d4c0c0f262
9 changed files with 571 additions and 5 deletions

133
routes/home.py Normal file
View File

@@ -0,0 +1,133 @@
from flask import Blueprint, render_template, request
from flask_login import login_required, current_user
from extensions import db, htmx, environment
from jinja2_fragments import render_block
home = Blueprint('home', __name__)
@home.route('/')
@login_required
def index():
# Fetch user statistics
stats = db.execute("""
WITH timer_stats AS (
SELECT
COUNT(*) as total_timer_functions,
COUNT(*) FILTER (WHERE enabled = true) as active_timer_functions,
(SELECT COUNT(*) FROM timer_function_invocations tfi
JOIN timer_functions tf ON tf.id = tfi.timer_function_id
WHERE tf.user_id = %s) as timer_invocations,
(SELECT COUNT(*) FROM timer_function_invocations tfi
JOIN timer_functions tf ON tf.id = tfi.timer_function_id
WHERE tf.user_id = %s AND tfi.status = 'SUCCESS') as timer_successful_invocations,
MAX(last_run) as last_timer_invocation
FROM timer_functions
WHERE user_id = %s
),
http_stats AS (
SELECT
COUNT(*) as total_http_functions,
COUNT(*) FILTER (WHERE is_public = true) as public_http_functions,
SUM(invoked_count) as http_invocations,
(SELECT COUNT(*) FROM http_function_invocations hfi
JOIN http_functions hf ON hf.id = hfi.http_function_id
WHERE hf.user_id = %s AND hfi.status = 'SUCCESS') as http_successful_invocations,
(SELECT MAX(invocation_time)
FROM http_function_invocations hfi
JOIN http_functions hf ON hf.id = hfi.http_function_id
WHERE hf.user_id = %s) as last_http_invocation
FROM http_functions
WHERE user_id = %s
)
SELECT
*,
CASE
WHEN timer_invocations > 0 THEN
(timer_successful_invocations * 100.0 / timer_invocations)::numeric(5,1)
ELSE 0.0
END as timer_success_rate,
CASE
WHEN http_invocations > 0 THEN
(http_successful_invocations * 100.0 / http_invocations)::numeric(5,1)
ELSE 0.0
END as http_success_rate
FROM timer_stats, http_stats
""", [current_user.id, current_user.id, current_user.id, current_user.id, current_user.id, current_user.id], one=True)
# Get 24-hour distribution
hour_distribution = db.execute("""
WITH all_invocations AS (
SELECT date_trunc('hour', tfi.invocation_time) as hour_bucket
FROM timer_function_invocations tfi
JOIN timer_functions tf ON tf.id = tfi.timer_function_id
WHERE tf.user_id = %s
AND tfi.invocation_time > NOW() - INTERVAL '24 hours'
UNION ALL
SELECT date_trunc('hour', hfi.invocation_time) as hour_bucket
FROM http_function_invocations hfi
JOIN http_functions hf ON hf.id = hfi.http_function_id
WHERE hf.user_id = %s
AND hfi.invocation_time > NOW() - INTERVAL '24 hours'
)
SELECT
EXTRACT(HOUR FROM hour_bucket) as hour,
COUNT(*) as count
FROM all_invocations
GROUP BY hour
ORDER BY hour
""", [current_user.id, current_user.id])
# Get 7-day success rate trend
success_trend = db.execute("""
WITH daily_stats AS (
WITH timer_daily AS (
SELECT
date_trunc('day', tfi.invocation_time) as day,
COUNT(*) as total,
COUNT(*) FILTER (WHERE tfi.status = 'SUCCESS') as successes
FROM timer_function_invocations tfi
JOIN timer_functions tf ON tf.id = tfi.timer_function_id
WHERE tf.user_id = %s
AND tfi.invocation_time > NOW() - INTERVAL '7 days'
GROUP BY day
),
http_daily AS (
SELECT
date_trunc('day', hfi.invocation_time) as day,
COUNT(*) as total,
COUNT(*) FILTER (WHERE hfi.status = 'SUCCESS') as successes
FROM http_function_invocations hfi
JOIN http_functions hf ON hf.id = hfi.http_function_id
WHERE hf.user_id = %s
AND hfi.invocation_time > NOW() - INTERVAL '7 days'
GROUP BY day
)
SELECT
COALESCE(t.day, h.day) as day,
COALESCE(t.total, 0) + COALESCE(h.total, 0) as total,
COALESCE(t.successes, 0) + COALESCE(h.successes, 0) as successes
FROM timer_daily t
FULL OUTER JOIN http_daily h ON t.day = h.day
)
SELECT
to_char(day, 'Dy') as day_name,
CASE
WHEN total > 0 THEN
(successes * 100.0 / total)::float
ELSE 0.0
END as success_rate
FROM daily_stats
ORDER BY day DESC
LIMIT 7
""", [current_user.id, current_user.id])
if htmx:
return render_block(environment, 'dashboard/home.html', 'page',
stats=stats,
hour_distribution=hour_distribution,
success_trend=success_trend)
return render_template('dashboard/home.html',
stats=stats,
hour_distribution=hour_distribution,
success_trend=success_trend)

48
routes/test.py Normal file
View File

@@ -0,0 +1,48 @@
from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify
from jinja2_fragments import render_block
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import current_user, login_required
from extensions import db, htmx, environment
from datetime import datetime, timezone, timedelta
from lib.mithril import Mithril
import json
test = Blueprint('test', __name__)
@test.route('/mithril/<int:function_id>')
@login_required
def mithril(function_id):
# Fetch the timer function to verify ownership
timer_function = db.execute("""
SELECT id, name, code, version_number
FROM timer_functions
WHERE id = %s AND user_id = %s
""", [function_id, current_user.id], one=True)
if not timer_function:
flash('Timer function not found', 'error')
return redirect(url_for('timer.overview'))
# Fetch all versions
versions = db.execute("""
SELECT version_number, script, versioned_at
FROM timer_function_versions
WHERE timer_function_id = %s
ORDER BY version_number DESC
""", [function_id])
# Convert datetime objects to ISO format strings
for version in versions:
version['versioned_at'] = version['versioned_at'].isoformat() if version['versioned_at'] else None
args = {
'user_id': current_user.id,
'function_id': function_id,
'timer_function': timer_function,
'versions': versions
}
return Mithril.render('DiffView', args)
#return render_template('mithril_loader.html', args=args)

View File

@@ -153,7 +153,7 @@ def overview():
timer_functions = db.execute("""
SELECT id, name, code, environment, trigger_type,
frequency_minutes, run_date, next_run,
last_run, enabled
last_run, enabled, invocation_count
FROM timer_functions
WHERE user_id = %s
ORDER BY id DESC
@@ -237,7 +237,7 @@ def edit(function_id):
timer_function = db.execute("""
SELECT id, name, code, environment, version_number, trigger_type,
frequency_minutes, run_date, next_run,
last_run, enabled
last_run, enabled, invocation_count
FROM timer_functions
WHERE id = %s AND user_id = %s
""", [function_id, current_user.id], one=True)
@@ -369,7 +369,7 @@ def toggle(function_id):
timer_functions = db.execute("""
SELECT id, name, code, environment, trigger_type,
frequency_minutes, run_date, next_run,
last_run, enabled
last_run, enabled, invocation_count
FROM timer_functions
WHERE user_id = %s
ORDER BY id DESC