Files
function/templates/dashboard/home.html
2025-12-02 20:02:32 +11:00

309 lines
15 KiB
HTML

{% extends 'dashboard.html' %}
{% block page %}
<script src="/static/js/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 dark:text-white">Dashboard Overview</h1>
</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 dark:bg-gray-800 rounded-xl shadow-sm p-6 border border-gray-100 dark:border-gray-700">
<div class="flex items-center justify-between mb-4">
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400">Total Functions</h3>
<div class="p-2 bg-indigo-50 dark:bg-indigo-900/20 rounded-lg">
<svg class="w-6 h-6 text-indigo-600 dark:text-indigo-400" 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>
<div class="flex items-baseline">
<p class="text-2xl font-bold text-gray-900 dark:text-white">{{ stats.total_timer_functions +
stats.total_http_functions
}}</p>
<p class="ml-2 text-sm text-gray-500 dark:text-gray-400">
({{ stats.total_http_functions }} HTTP, {{ stats.total_timer_functions }} Timer)
</p>
</div>
</div>
<!-- Total Invocations -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-6 border border-gray-100 dark:border-gray-700">
<div class="flex items-center justify-between mb-4">
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400">Total Invocations</h3>
<div class="p-2 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
<svg class="w-6 h-6 text-blue-600 dark:text-blue-400" 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 dark:text-white">{{ stats.timer_invocations +
stats.http_invocations }}</p>
<p class="ml-2 text-sm text-gray-500 dark:text-gray-400">All time</p>
</div>
</div>
<!-- Success Rate -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-6 border border-gray-100 dark:border-gray-700">
<div class="flex items-center justify-between mb-4">
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400">Overall Success Rate</h3>
<div class="p-2 bg-green-50 dark:bg-green-900/20 rounded-lg">
<svg class="w-6 h-6 text-green-600 dark:text-green-400" 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 dark:text-white">{{ success_rate }}%</p>
<p class="ml-2 text-sm text-gray-500 dark:text-gray-400">
{{ total_success }}/{{ total_invocations }}
</p>
</div>
</div>
<!-- Avg Execution Time -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-6 border border-gray-100 dark:border-gray-700">
<div class="flex items-center justify-between mb-4">
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400">Avg Execution Time</h3>
<div class="p-2 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg">
<svg class="w-6 h-6 text-yellow-600 dark:text-yellow-400" 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>
{% 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 dark:text-white">{{ "%.2f"|format(avg_time) }}ms</p>
</div>
</div>
</div>
<!-- Charts Section -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
<!-- Invocation History Chart -->
<div
class="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-6 border border-gray-100 dark:border-gray-700 lg:col-span-2">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">24-Hour Activity</h3>
<div class="h-64">
<canvas id="activityChart"></canvas>
</div>
</div>
<!-- Top Functions -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm p-6 border border-gray-100 dark:border-gray-700">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white 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 dark:text-gray-300">{{ func.name }}</span>
</div>
<span class="text-sm text-gray-500 dark:text-gray-400">{{ func.invocation_count }} calls</span>
</div>
{% else %}
<p class="text-sm text-gray-500 dark:text-gray-400">No functions found.</p>
{% endfor %}
</div>
<div class="mt-6 pt-6 border-t border-gray-100 dark:border-gray-700">
<h4 class="text-sm font-medium text-gray-900 dark:text-white mb-2">Success Trend (7 Days)</h4>
<div class="h-32">
<canvas id="trendChart"></canvas>
</div>
</div>
</div>
</div>
<!-- Recent Activity Table -->
<div
class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100 dark:border-gray-700 flex justify-between items-center">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">Recent Activity</h3>
<div class="flex space-x-2">
<a href="{{ url_for('http.overview') }}"
class="text-sm text-indigo-600 dark:text-indigo-400 hover:text-indigo-800 dark:hover:text-indigo-300 font-medium">View
HTTP</a>
<span class="text-gray-300 dark:text-gray-600">|</span>
<a href="{{ url_for('timer.overview') }}"
class="text-sm text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 font-medium">View
Timers</a>
</div>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead class="bg-gray-50 dark:bg-gray-900/50">
<tr>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Function</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Type
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Status</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Duration</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Time
</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
{% for activity in recent_activity %}
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">{{
activity.name }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full {% if activity.type == 'HTTP' %}bg-indigo-100 text-indigo-800 dark:bg-indigo-900/30 dark:text-indigo-300{% else %}bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300{% 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 dark:bg-green-900/30 dark:text-green-300">Success</span>
{% else %}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300">{{
activity.status }}</span>
{% endif %}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">{{
"%.2f"|format(activity.execution_time or 0) }}ms</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">{{
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 dark:text-gray-400">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,
color: document.documentElement.classList.contains('dark') ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'
},
ticks: {
color: document.documentElement.classList.contains('dark') ? '#9ca3af' : '#6b7280'
}
},
x: {
grid: { display: false },
ticks: {
color: document.documentElement.classList.contains('dark') ? '#9ca3af' : '#6b7280'
}
}
}
}
});
// 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 }
}
}
});
// Update charts on theme change
window.addEventListener('themeChanged', (e) => {
const isDark = e.detail.theme === 'dark';
const textColor = isDark ? '#9ca3af' : '#6b7280';
const gridColor = isDark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
// Update Activity Chart
const activityChart = Chart.getChart('activityChart');
if (activityChart) {
activityChart.options.scales.x.ticks.color = textColor;
activityChart.options.scales.y.ticks.color = textColor;
activityChart.options.scales.y.grid.color = gridColor;
activityChart.update();
}
});
</script>
{% endblock %}