Initial commit
Dieser Commit ist enthalten in:
559
v2_adminpanel/templates/resource_metrics.html
Normale Datei
559
v2_adminpanel/templates/resource_metrics.html
Normale Datei
@ -0,0 +1,559 @@
|
||||
{% 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 %}
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren