559 Zeilen
20 KiB
HTML
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 %} |