Improve look of anayltics page (home)
This commit is contained in:
@@ -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)
|
||||
@@ -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 %}
|
||||
Reference in New Issue
Block a user