Improve look of anayltics page (home)

This commit is contained in:
Peter Stockings
2025-11-19 21:15:41 +11:00
parent 2af2cdef0c
commit 7e5df46997
2 changed files with 287 additions and 316 deletions

View File

@@ -1,5 +1,7 @@
from flask import Blueprint, render_template, request
from flask_login import login_required, current_user
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
@@ -132,12 +134,74 @@ def index():
LIMIT 7
""", [current_user.id, current_user.id])
# Top 5 Most Invoked Functions
top_functions = db.execute("""
WITH all_functions AS (
SELECT
tf.name,
'Timer' as type,
COUNT(tfi.id) as invocation_count
FROM timer_functions tf
JOIN timer_function_invocations tfi ON tf.id = tfi.timer_function_id
WHERE tf.user_id = %s
GROUP BY tf.name
UNION ALL
SELECT
hf.name,
'HTTP' as type,
COUNT(hfi.id) as invocation_count
FROM http_functions hf
JOIN http_function_invocations hfi ON hf.id = hfi.http_function_id
WHERE hf.user_id = %s
GROUP BY hf.name
)
SELECT * FROM all_functions
ORDER BY invocation_count DESC
LIMIT 5
""", [current_user.id, current_user.id])
# Recent Activity (Last 10)
recent_activity = db.execute("""
WITH all_activity AS (
SELECT
tf.name,
'Timer' as type,
tfi.status,
tfi.invocation_time,
tfi.execution_time
FROM timer_function_invocations tfi
JOIN timer_functions tf ON tf.id = tfi.timer_function_id
WHERE tf.user_id = %s
UNION ALL
SELECT
hf.name,
'HTTP' as type,
hfi.status,
hfi.invocation_time,
hfi.execution_time
FROM http_function_invocations hfi
JOIN http_functions hf ON hf.id = hfi.http_function_id
WHERE hf.user_id = %s
)
SELECT * FROM all_activity
ORDER BY invocation_time DESC
LIMIT 10
""", [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)
success_trend=success_trend,
top_functions=top_functions,
recent_activity=recent_activity)
return render_template('dashboard/home.html',
stats=stats,
hour_distribution=hour_distribution,
success_trend=success_trend)
success_trend=success_trend,
top_functions=top_functions,
recent_activity=recent_activity)

View File

@@ -1,353 +1,260 @@
{% extends 'dashboard.html' %}
{% block page %}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="mb-8">
<h1 class="text-2xl font-bold text-gray-900">Dashboard Overview</h1>
</div>
<!-- Timer Functions Stats -->
<div class="mb-4 sm:mb-8">
<h2 class="text-lg font-semibold text-gray-700 mb-2 sm:mb-4">Timer Functions</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-6">
<!-- Total Timer Functions Card -->
<div class="bg-white border border-gray-200 rounded-lg shadow-sm p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-blue-100">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-blue-600" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="ml-4">
<h2 class="text-sm font-medium text-gray-500">Total Timer Functions</h2>
<p class="text-2xl font-semibold text-gray-900">{{ stats.total_timer_functions }}</p>
</div>
<!-- Key Metrics Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<!-- Total Functions -->
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
<div class="flex items-center justify-between mb-4">
<h3 class="text-sm font-medium text-gray-500">Total Functions</h3>
<div class="p-2 bg-indigo-50 rounded-lg">
<svg class="w-6 h-6 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z">
</path>
</svg>
</div>
</div>
<!-- Active Timer Functions Card -->
<div class="bg-white border border-gray-200 rounded-lg shadow-sm p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-green-100">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-green-600" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z" />
</svg>
</div>
<div class="ml-4">
<h2 class="text-sm font-medium text-gray-500">Active Timer Functions</h2>
<p class="text-2xl font-semibold text-gray-900">{{ stats.active_timer_functions }}</p>
</div>
</div>
</div>
<!-- Timer Function Invocations Card -->
<div class="bg-white border border-gray-200 rounded-lg shadow-sm p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-purple-100">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-purple-600" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<div class="ml-4">
<h2 class="text-sm font-medium text-gray-500">Timer Invocations</h2>
<p class="text-2xl font-semibold text-gray-900">{{ stats.timer_invocations }}</p>
</div>
</div>
</div>
<!-- Timer Success Rate Card -->
<div class="bg-white border border-gray-200 rounded-lg shadow-sm p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-emerald-100">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-emerald-600" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="ml-4">
<h2 class="text-sm font-medium text-gray-500">Success Rate</h2>
<div class="flex items-baseline">
<p class="text-2xl font-semibold text-gray-900">{{ stats.timer_success_rate }}%</p>
<p class="ml-2 text-sm text-gray-500">({{ stats.timer_successful_invocations }}/{{
stats.timer_invocations }})</p>
</div>
</div>
</div>
</div>
<!-- Failed Invocations -->
<div class="bg-white border border-gray-200 rounded-lg shadow-sm p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-red-100">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-red-600" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
<div class="ml-4">
<h2 class="text-sm font-medium text-gray-500">Failed Invocations</h2>
<p class="text-2xl font-semibold text-gray-900">{{ stats.timer_failed_invocations }}</p>
</div>
</div>
</div>
<!-- Average Execution Time -->
<div class="bg-white border border-gray-200 rounded-lg shadow-sm p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-yellow-100">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-yellow-600" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
<div class="ml-4">
<h2 class="text-sm font-medium text-gray-500">Avg. Execution Time</h2>
<p class="text-2xl font-semibold text-gray-900">
{{ "%.2f"|format(stats.avg_timer_execution_time|default(0)) }}s
</p>
</div>
</div>
<div class="flex items-baseline">
<p class="text-2xl font-bold text-gray-900">{{ stats.total_timer_functions + stats.total_http_functions
}}</p>
<p class="ml-2 text-sm text-gray-500">
({{ stats.total_http_functions }} HTTP, {{ stats.total_timer_functions }} Timer)
</p>
</div>
</div>
</div>
<!-- HTTP Functions Stats -->
<div class="mb-4 sm:mb-8">
<h2 class="text-lg font-semibold text-gray-700 mb-2 sm:mb-4">HTTP Functions</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-6">
<!-- Total HTTP Functions Card -->
<div class="bg-white border border-gray-200 rounded-lg shadow-sm p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-indigo-100">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-indigo-600" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
</svg>
</div>
<div class="ml-4">
<h2 class="text-sm font-medium text-gray-500">Total HTTP Functions</h2>
<p class="text-2xl font-semibold text-gray-900">{{ stats.total_http_functions }}</p>
</div>
<!-- Total Invocations -->
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
<div class="flex items-center justify-between mb-4">
<h3 class="text-sm font-medium text-gray-500">Total Invocations</h3>
<div class="p-2 bg-blue-50 rounded-lg">
<svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M13 10V3L4 14h7v7l9-11h-7z"></path>
</svg>
</div>
</div>
<div class="flex items-baseline">
<p class="text-2xl font-bold text-gray-900">{{ stats.timer_invocations + stats.http_invocations }}</p>
<p class="ml-2 text-sm text-gray-500">All time</p>
</div>
</div>
<!-- Public HTTP Functions Card -->
<div class="bg-white border border-gray-200 rounded-lg shadow-sm p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-yellow-100">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-yellow-600" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064" />
</svg>
</div>
<div class="ml-4">
<h2 class="text-sm font-medium text-gray-500">Public HTTP Functions</h2>
<p class="text-2xl font-semibold text-gray-900">{{ stats.public_http_functions }}</p>
</div>
<!-- Success Rate -->
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
<div class="flex items-center justify-between mb-4">
<h3 class="text-sm font-medium text-gray-500">Overall Success Rate</h3>
<div class="p-2 bg-green-50 rounded-lg">
<svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
</div>
{% set total_invocations = stats.timer_invocations + stats.http_invocations %}
{% set total_success = stats.timer_successful_invocations + stats.http_successful_invocations %}
{% set success_rate = (total_success / total_invocations * 100)|round(1) if total_invocations > 0 else 0 %}
<div class="flex items-baseline">
<p class="text-2xl font-bold text-gray-900">{{ success_rate }}%</p>
<p class="ml-2 text-sm text-gray-500">
{{ total_success }}/{{ total_invocations }}
</p>
</div>
</div>
<!-- HTTP Function Invocations Card -->
<div class="bg-white border border-gray-200 rounded-lg shadow-sm p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-red-100">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-red-600" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<div class="ml-4">
<h2 class="text-sm font-medium text-gray-500">HTTP Invocations</h2>
<p class="text-2xl font-semibold text-gray-900">{{ stats.http_invocations }}</p>
</div>
<!-- Avg Execution Time -->
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
<div class="flex items-center justify-between mb-4">
<h3 class="text-sm font-medium text-gray-500">Avg Execution Time</h3>
<div class="p-2 bg-yellow-50 rounded-lg">
<svg class="w-6 h-6 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
</div>
<!-- HTTP Success Rate Card -->
<div class="bg-white border border-gray-200 rounded-lg shadow-sm p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-emerald-100">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-emerald-600" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="ml-4">
<h2 class="text-sm font-medium text-gray-500">Success Rate</h2>
<div class="flex items-baseline">
<p class="text-2xl font-semibold text-gray-900">{{ stats.http_success_rate }}%</p>
<p class="ml-2 text-sm text-gray-500">({{ stats.http_successful_invocations }}/{{
stats.http_invocations }})</p>
</div>
</div>
</div>
</div>
<!-- Failed Invocations -->
<div class="bg-white border border-gray-200 rounded-lg shadow-sm p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-red-100">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-red-600" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
<div class="ml-4">
<h2 class="text-sm font-medium text-gray-500">Failed Invocations</h2>
<p class="text-2xl font-semibold text-gray-900">{{ stats.http_failed_invocations }}</p>
</div>
</div>
</div>
<!-- Average Execution Time -->
<div class="bg-white border border-gray-200 rounded-lg shadow-sm p-6">
<div class="flex items-center">
<div class="p-3 rounded-full bg-yellow-100">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-yellow-600" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
<div class="ml-4">
<h2 class="text-sm font-medium text-gray-500">Avg. Execution Time</h2>
<p class="text-2xl font-semibold text-gray-900">
{{ "%.2f"|format(stats.avg_http_execution_time|default(0)) }}s
</p>
</div>
</div>
{% set avg_time = ((stats.avg_timer_execution_time or 0) + (stats.avg_http_execution_time or 0)) / 2 %}
<div class="flex items-baseline">
<p class="text-2xl font-bold text-gray-900">{{ "%.2f"|format(avg_time) }}s</p>
</div>
</div>
</div>
<!-- Charts Section -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-3 sm:gap-6 mb-4 sm:mb-8">
<!-- 24-Hour Distribution -->
<div class="bg-white border border-gray-200 rounded-lg shadow-sm p-4 sm:p-6">
<h3 class="text-base sm:text-lg font-medium text-gray-900 mb-2 sm:mb-4">24-Hour Invocation Distribution</h3>
<div class="relative h-48 sm:h-64">
<svg class="w-full h-full" viewBox="0 0 400 200" preserveAspectRatio="none">
<!-- Y-axis -->
<line x1="40" y1="20" x2="40" y2="180" stroke="#E5E7EB" stroke-width="1" />
<!-- X-axis -->
<line x1="40" y1="180" x2="380" y2="180" stroke="#E5E7EB" stroke-width="1" />
{% set max_count = namespace(value=1) %}
{% for hour in hour_distribution %}
{% if hour.count > max_count.value %}
{% set max_count.value = hour.count %}
{% endif %}
{% endfor %}
<!-- Hour bars -->
{% for hour in hour_distribution %}
{% set x = 40 + (hour.hour * 14) %}
{% set height = (hour.count / max_count.value) * 160 %}
{% set y = 180 - height %}
<rect x="{{ x }}" y="{{ y }}" width="12" height="{{ height }}" fill="#60A5FA" opacity="0.8">
<title>{{ hour.hour }}:00 - {{ hour.count }} invocations</title>
</rect>
{% endfor %}
<!-- Hour labels (every 6 hours) -->
{% for h in [0, 6, 12, 18, 23] %}
<text x="{{ 40 + (h * 14) }}" y="195" text-anchor="middle" class="text-xs text-gray-500">{{ h
}}h</text>
{% endfor %}
</svg>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
<!-- Invocation History Chart -->
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100 lg:col-span-2">
<h3 class="text-lg font-semibold text-gray-900 mb-4">24-Hour Activity</h3>
<div class="h-64">
<canvas id="activityChart"></canvas>
</div>
</div>
<!-- 7-Day Success Rate Trend -->
<div class="bg-white border border-gray-200 rounded-lg shadow-sm p-4 sm:p-6">
<h3 class="text-base sm:text-lg font-medium text-gray-900 mb-2 sm:mb-4">7-Day Success Rate Trend</h3>
<div class="relative h-48 sm:h-64">
<svg class="w-full h-full" viewBox="0 0 400 200" preserveAspectRatio="none">
<!-- Y-axis -->
<line x1="40" y1="20" x2="40" y2="180" stroke="#E5E7EB" stroke-width="1" />
<!-- X-axis -->
<line x1="40" y1="180" x2="380" y2="180" stroke="#E5E7EB" stroke-width="1" />
<!-- Success rate line -->
{% set path = namespace(d='M') %}
{% for day in success_trend %}
{% set x = 380 - (loop.index0 * 48) %}
{% set y = 180 - (day.success_rate * 1.6) %}
{% if loop.first %}
{% set path.d = path.d ~ x ~ "," ~ y %}
{% else %}
{% set path.d = path.d ~ " L" ~ x ~ "," ~ y %}
{% endif %}
{% endfor %}
<path d="{{ path.d }}" stroke="#10B981" stroke-width="2" fill="none" />
<!-- Data points -->
{% for day in success_trend %}
{% set x = 380 - (loop.index0 * 48) %}
{% set y = 180 - (day.success_rate * 1.6) %}
<circle cx="{{ x }}" cy="{{ y }}" r="4" fill="#10B981">
<title>{{ day.day_name }}: {{ day.success_rate }}% success rate</title>
</circle>
<!-- Day labels -->
<text x="{{ x }}" y="195" text-anchor="middle" class="text-xs text-gray-500">{{ day.day_name
}}</text>
{% endfor %}
<!-- Y-axis labels -->
{% for percent in [0, 25, 50, 75, 100] %}
<text x="35" y="{{ 180 - (percent * 1.6) }}" text-anchor="end" class="text-xs text-gray-500">{{
percent }}%</text>
{% endfor %}
</svg>
<!-- Top Functions -->
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Top Functions</h3>
<div class="space-y-4">
{% for func in top_functions %}
<div class="flex items-center justify-between">
<div class="flex items-center">
<div
class="w-2 h-2 rounded-full {% if func.type == 'HTTP' %}bg-indigo-500{% else %}bg-blue-500{% endif %} mr-2">
</div>
<span class="text-sm font-medium text-gray-700">{{ func.name }}</span>
</div>
<span class="text-sm text-gray-500">{{ func.invocation_count }} calls</span>
</div>
{% else %}
<p class="text-sm text-gray-500">No functions found.</p>
{% endfor %}
</div>
<div class="mt-6 pt-6 border-t border-gray-100">
<h4 class="text-sm font-medium text-gray-900 mb-2">Success Trend (7 Days)</h4>
<div class="h-32">
<canvas id="trendChart"></canvas>
</div>
</div>
</div>
</div>
<!-- Last Activity -->
<div class="bg-white border border-gray-200 rounded-lg shadow-sm p-4 sm:p-6">
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between space-y-4 sm:space-y-0">
<div>
<h2 class="text-base sm:text-lg font-medium text-gray-900">Last Activity</h2>
<div class="mt-1 space-y-1">
{% if stats.last_timer_invocation %}
<p class="text-sm text-gray-500">
Last Timer Invocation: {{ stats.last_timer_invocation.strftime('%Y-%m-%d %H:%M') }}
</p>
{% endif %}
{% if stats.last_http_invocation %}
<p class="text-sm text-gray-500">
Last HTTP Invocation: {{ stats.last_http_invocation.strftime('%Y-%m-%d %H:%M') }}
</p>
{% endif %}
</div>
</div>
<div class="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-4">
<a href="{{ url_for('timer.overview') }}"
class="inline-flex items-center justify-center px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors duration-200">
View Timer Functions
</a>
<!-- Recent Activity Table -->
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100 flex justify-between items-center">
<h3 class="text-lg font-semibold text-gray-900">Recent Activity</h3>
<div class="flex space-x-2">
<a href="{{ url_for('http.overview') }}"
class="inline-flex items-center justify-center px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white font-medium rounded-lg transition-colors duration-200">
View HTTP Functions
</a>
class="text-sm text-indigo-600 hover:text-indigo-800 font-medium">View HTTP</a>
<span class="text-gray-300">|</span>
<a href="{{ url_for('timer.overview') }}"
class="text-sm text-blue-600 hover:text-blue-800 font-medium">View Timers</a>
</div>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Function</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Status</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Duration</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Time
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
{% for activity in recent_activity %}
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{{ activity.name }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full {% if activity.type == 'HTTP' %}bg-indigo-100 text-indigo-800{% else %}bg-blue-100 text-blue-800{% endif %}">
{{ activity.type }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
{% if activity.status == 'SUCCESS' %}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">Success</span>
{% else %}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">{{
activity.status }}</span>
{% endif %}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{
"%.2f"|format(activity.execution_time or 0) }}s</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{
activity.invocation_time.strftime('%Y-%m-%d %H:%M:%S') }}</td>
</tr>
{% else %}
<tr>
<td colspan="5" class="px-6 py-4 text-center text-sm text-gray-500">No recent activity found.
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<script>
// Activity Chart
const activityCtx = document.getElementById('activityChart').getContext('2d');
new Chart(activityCtx, {
type: 'bar',
data: {
labels: {{ hour_distribution | map(attribute = 'hour') | list | tojson }},
datasets: [{
label: 'Invocations',
data: {{ hour_distribution | map(attribute = 'count') | list | tojson }},
backgroundColor: 'rgba(79, 70, 229, 0.6)',
borderColor: 'rgb(79, 70, 229)',
borderWidth: 1,
borderRadius: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
},
scales: {
y: { beginAtZero: true, grid: { display: false } },
x: { grid: { display: false } }
}
}
});
// Trend Chart
const trendCtx = document.getElementById('trendChart').getContext('2d');
new Chart(trendCtx, {
type: 'line',
data: {
labels: {{ success_trend | map(attribute = 'day_name') | list | tojson }},
datasets: [{
label: 'Success Rate',
data: {{ success_trend | map(attribute = 'success_rate') | list | tojson }},
borderColor: 'rgb(16, 185, 129)',
backgroundColor: 'rgba(16, 185, 129, 0.1)',
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
},
scales: {
y: {
beginAtZero: true,
max: 100,
display: false
},
x: {
display: false
}
},
elements: {
point: { radius: 0, hitRadius: 10 }
}
}
});
</script>
{% endblock %}