Files
v2-Docker/v2_adminpanel/templates/resource_metrics.html
Claude Project Manager 0d7d888502 Initial commit
2025-07-05 17:51:16 +02:00

559 Zeilen
20 KiB
HTML

{% extends "base.html" %}
{% block title %}Resource Metriken{% endblock %}
{% block extra_css %}
<style>
/* Metric Cards */
.metric-card {
border: none;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: transform 0.2s ease;
height: 100%;
}
.metric-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0,0,0,0.15);
}
.metric-card .card-body {
text-align: center;
padding: 2rem;
}
.metric-value {
font-size: 3rem;
font-weight: bold;
line-height: 1;
margin: 0.5rem 0;
}
.metric-label {
font-size: 1rem;
color: #6c757d;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 0.5rem;
}
.metric-sublabel {
font-size: 0.875rem;
color: #6c757d;
}
/* Chart Cards */
.chart-card {
border: none;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
height: 100%;
}
.chart-card .card-header {
background-color: #f8f9fa;
border-bottom: 2px solid #e9ecef;
font-weight: 600;
}
/* Performance Table */
.performance-table {
font-size: 0.875rem;
}
.performance-table td {
vertical-align: middle;
padding: 0.75rem;
}
.performance-table .resource-link {
color: #007bff;
text-decoration: none;
transition: color 0.2s ease;
}
.performance-table .resource-link:hover {
color: #0056b3;
}
/* Progress Bars */
.progress-custom {
height: 22px;
border-radius: 11px;
font-size: 0.75rem;
font-weight: 600;
}
/* Status Badges */
.status-badge {
padding: 0.35rem 0.65rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 500;
}
/* Icon Badges */
.icon-badge {
width: 40px;
height: 40px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
font-size: 1.5rem;
margin-bottom: 1rem;
}
.icon-badge.blue { background-color: #e7f3ff; color: #0066cc; }
.icon-badge.green { background-color: #e8f5e9; color: #2e7d32; }
.icon-badge.orange { background-color: #fff3e0; color: #ef6c00; }
.icon-badge.red { background-color: #ffebee; color: #c62828; }
/* Trend Indicator */
.trend-indicator {
display: inline-flex;
align-items: center;
font-size: 0.875rem;
font-weight: 500;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
}
.trend-up {
background-color: #e8f5e9;
color: #2e7d32;
}
.trend-down {
background-color: #ffebee;
color: #c62828;
}
.trend-neutral {
background-color: #f5f5f5;
color: #616161;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid py-4">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="mb-0">Performance Dashboard</h1>
<p class="text-muted mb-0">Resource Pool Metriken und Analysen</p>
</div>
<div>
<a href="{{ url_for('resources.resource_report') }}" class="btn btn-info">
📄 Report generieren
</a>
<a href="{{ url_for('resources.resources') }}" class="btn btn-secondary">
← Zurück
</a>
</div>
</div>
<!-- Key Metrics -->
<div class="row g-4 mb-4">
<div class="col-lg-3 col-md-6">
<div class="card metric-card">
<div class="card-body">
<div class="icon-badge blue">
📊
</div>
<div class="metric-label">Ressourcen gesamt</div>
<div class="metric-value text-primary">{{ stats.total_resources or 0 }}</div>
<div class="metric-sublabel">Aktive Ressourcen</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6">
<div class="card metric-card">
<div class="card-body">
<div class="icon-badge green">
📈
</div>
<div class="metric-label">Ø Performance</div>
<div class="metric-value text-{{ 'success' if stats.avg_performance > 80 else ('warning' if stats.avg_performance > 60 else 'danger') }}">
{{ "%.1f"|format(stats.avg_performance or 0) }}%
</div>
<div class="metric-sublabel">Letzte 30 Tage</div>
{% if stats.performance_trend %}
<div class="mt-2">
<span class="trend-indicator trend-{{ stats.performance_trend }}">
{% if stats.performance_trend == 'up' %}
<i class="fas fa-arrow-up me-1"></i> Steigend
{% elif stats.performance_trend == 'down' %}
<i class="fas fa-arrow-down me-1"></i> Fallend
{% else %}
<i class="fas fa-minus me-1"></i> Stabil
{% endif %}
</span>
</div>
{% endif %}
</div>
</div>
</div>
<div class="col-lg-3 col-md-6">
<div class="card metric-card">
<div class="card-body">
<div class="icon-badge orange">
💰
</div>
<div class="metric-label">ROI</div>
<div class="metric-value text-{{ 'success' if stats.roi > 1 else 'danger' }}">
{{ "%.2f"|format(stats.roi) }}x
</div>
<div class="metric-sublabel">Revenue / Cost</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6">
<div class="card metric-card">
<div class="card-body">
<div class="icon-badge red">
⚠️
</div>
<div class="metric-label">Probleme</div>
<div class="metric-value text-{{ 'danger' if stats.total_issues > 10 else ('warning' if stats.total_issues > 5 else 'success') }}">
{{ stats.total_issues or 0 }}
</div>
<div class="metric-sublabel">Letzte 30 Tage</div>
</div>
</div>
</div>
</div>
<!-- Charts Row -->
<div class="row g-4 mb-4">
<div class="col-md-6">
<div class="card chart-card">
<div class="card-header">
<h5 class="mb-0">📊 Performance nach Ressourcentyp</h5>
</div>
<div class="card-body">
<canvas id="performanceByTypeChart"></canvas>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card chart-card">
<div class="card-header">
<h5 class="mb-0">🎯 Auslastung nach Typ</h5>
</div>
<div class="card-body">
<canvas id="utilizationChart"></canvas>
</div>
</div>
</div>
</div>
<!-- Performance Tables -->
<div class="row g-4">
<div class="col-md-6">
<div class="card chart-card">
<div class="card-header bg-success text-white">
<h5 class="mb-0">🏆 Top Performer</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table performance-table mb-0">
<thead class="table-light">
<tr>
<th>Ressource</th>
<th width="80">Typ</th>
<th width="140">Score</th>
<th width="80" class="text-center">ROI</th>
</tr>
</thead>
<tbody>
{% for resource in top_performers %}
<tr>
<td>
<div class="d-flex align-items-center">
<code class="me-2">{{ resource.resource_value }}</code>
<a href="{{ url_for('resources.resource_history', resource_id=resource.id) }}"
class="resource-link" title="Historie anzeigen">
<i class="fas fa-external-link-alt"></i>
</a>
</div>
</td>
<td>
<span class="badge bg-light text-dark">
{% if resource.resource_type == 'domain' %}🌐{% elif resource.resource_type == 'ipv4' %}🖥️{% else %}📱{% endif %}
{{ resource.resource_type|upper }}
</span>
</td>
<td>
<div class="progress progress-custom">
<div class="progress-bar bg-success"
style="width: {{ resource.avg_score }}%">
{{ "%.1f"|format(resource.avg_score) }}%
</div>
</div>
</td>
<td class="text-center">
<span class="badge bg-success">
{{ "%.2f"|format(resource.roi) }}x
</span>
</td>
</tr>
{% endfor %}
{% if not top_performers %}
<tr>
<td colspan="4" class="text-center text-muted py-4">
Keine Performance-Daten verfügbar
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card chart-card">
<div class="card-header bg-danger text-white">
<h5 class="mb-0">⚠️ Problematische Ressourcen</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table performance-table mb-0">
<thead class="table-light">
<tr>
<th>Ressource</th>
<th width="80">Typ</th>
<th width="100" class="text-center">Probleme</th>
<th width="120">Status</th>
</tr>
</thead>
<tbody>
{% for resource in problem_resources %}
<tr>
<td>
<div class="d-flex align-items-center">
<code class="me-2">{{ resource.resource_value }}</code>
<a href="{{ url_for('resources.resource_history', resource_id=resource.id) }}"
class="resource-link" title="Historie anzeigen">
<i class="fas fa-external-link-alt"></i>
</a>
</div>
</td>
<td>
<span class="badge bg-light text-dark">
{% if resource.resource_type == 'domain' %}🌐{% elif resource.resource_type == 'ipv4' %}🖥️{% else %}📱{% endif %}
{{ resource.resource_type|upper }}
</span>
</td>
<td class="text-center">
<span class="badge bg-danger">
{{ resource.total_issues }}
</span>
</td>
<td>
{% if resource.status == 'quarantine' %}
<span class="status-badge bg-warning text-dark">
⚠️ Quarantäne
</span>
{% elif resource.status == 'allocated' %}
<span class="status-badge bg-primary text-white">
🔗 Zugeteilt
</span>
{% else %}
<span class="status-badge bg-success text-white">
✅ Verfügbar
</span>
{% endif %}
</td>
</tr>
{% endfor %}
{% if not problem_resources %}
<tr>
<td colspan="4" class="text-center text-muted py-4">
Keine problematischen Ressourcen gefunden
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Trend Chart -->
<div class="card chart-card mt-4">
<div class="card-header">
<h5 class="mb-0">📈 30-Tage Performance Trend</h5>
</div>
<div class="card-body">
<canvas id="trendChart" height="100"></canvas>
</div>
</div>
</div>
<!-- Chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js"></script>
<script>
// Chart defaults
Chart.defaults.font.family = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
// Performance by Type Chart
const performanceCtx = document.getElementById('performanceByTypeChart').getContext('2d');
new Chart(performanceCtx, {
type: 'bar',
data: {
labels: {{ performance_by_type|map(attribute=0)|list|tojson }},
datasets: [{
label: 'Durchschnittliche Performance',
data: {{ performance_by_type|map(attribute=1)|list|tojson }},
backgroundColor: [
'rgba(33, 150, 243, 0.8)',
'rgba(156, 39, 176, 0.8)',
'rgba(76, 175, 80, 0.8)'
],
borderColor: [
'rgb(33, 150, 243)',
'rgb(156, 39, 176)',
'rgb(76, 175, 80)'
],
borderWidth: 2,
borderRadius: 8
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
max: 100,
ticks: {
callback: function(value) {
return value + '%';
}
}
}
}
}
});
// Utilization Chart
const utilizationCtx = document.getElementById('utilizationChart').getContext('2d');
new Chart(utilizationCtx, {
type: 'doughnut',
data: {
labels: {{ utilization_data|map(attribute='type')|list|tojson }},
datasets: [{
data: {{ utilization_data|map(attribute='allocated_percent')|list|tojson }},
backgroundColor: [
'rgba(33, 150, 243, 0.8)',
'rgba(156, 39, 176, 0.8)',
'rgba(76, 175, 80, 0.8)'
],
borderColor: '#fff',
borderWidth: 3
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: {
padding: 20,
usePointStyle: true
}
},
tooltip: {
callbacks: {
label: function(context) {
return context.label + ': ' + context.parsed + '% ausgelastet';
}
}
}
}
}
});
// Trend Chart
const trendCtx = document.getElementById('trendChart').getContext('2d');
new Chart(trendCtx, {
type: 'line',
data: {
labels: {{ daily_metrics|map(attribute='date')|list|tojson }},
datasets: [{
label: 'Performance Score',
data: {{ daily_metrics|map(attribute='performance')|list|tojson }},
borderColor: 'rgb(76, 175, 80)',
backgroundColor: 'rgba(76, 175, 80, 0.1)',
tension: 0.4,
borderWidth: 3,
pointRadius: 4,
pointHoverRadius: 6,
yAxisID: 'y',
}, {
label: 'Probleme',
data: {{ daily_metrics|map(attribute='issues')|list|tojson }},
borderColor: 'rgb(244, 67, 54)',
backgroundColor: 'rgba(244, 67, 54, 0.1)',
tension: 0.4,
borderWidth: 3,
pointRadius: 4,
pointHoverRadius: 6,
yAxisID: 'y1',
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'index',
intersect: false,
},
plugins: {
legend: {
position: 'top',
labels: {
padding: 20,
usePointStyle: true
}
}
},
scales: {
x: {
grid: {
display: false
}
},
y: {
type: 'linear',
display: true,
position: 'left',
title: {
display: true,
text: 'Performance %'
},
beginAtZero: true,
max: 100,
grid: {
color: 'rgba(0, 0, 0, 0.05)'
}
},
y1: {
type: 'linear',
display: true,
position: 'right',
title: {
display: true,
text: 'Anzahl Probleme'
},
beginAtZero: true,
grid: {
drawOnChartArea: false,
}
}
}
}
});
</script>
{% endblock %}