Files
watcher-visio/watcher_visio/templates/index.html
2025-12-01 15:30:36 +03:00

866 lines
36 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends 'base.html' %}
{% load static mathfilters %}
{% block title %}System Analytics - Django + DaisyUI{% endblock %}
{% block imports %}
<script src="{% static 'js/chart.js' %}"></script>
<script src="{% static 'js/chartjs-plugin-datalabels.min.js' %}"></script>
{% endblock %}
{% block content %}
<!-- MAIN GRID -->
<div class="p-4 grid grid-cols-1 lg:grid-cols-2 gap-4">
<!-- CPU Utilization Card -->
<div class="card bg-base-100 shadow hover:shadow-md transition-shadow">
<div class="card-body p-4">
<div class="flex items-center justify-between mb-2">
<h2 class="card-title text-sm font-semibold">CPU Utilization</h2>
<div class="text-xs badge badge-ghost">{{ cpu_used }} / {{ cpu_used|add:cpu_free }} vCPU</div>
</div>
<div class="relative h-40 flex items-center justify-center">
<canvas id="cpuChart"></canvas>
<div class="absolute inset-0 flex items-center justify-center">
<div class="text-center">
<div class="text-2xl font-bold text-primary">{{ cpu_used_percentage|floatformat:1 }}%</div>
<div class="text-xs text-base-content/60">Used</div>
</div>
</div>
</div>
<div class="flex justify-between text-xs mt-2">
<span class="flex items-center gap-1">
<span class="w-2 h-2 rounded-full bg-primary"></span>
Used: {{ cpu_used }} vCPU
</span>
<span class="flex items-center gap-1">
<span class="w-2 h-2 rounded-full bg-base-300"></span>
Free: {{ cpu_free }} vCPU
</span>
</div>
</div>
</div>
<!-- RAM Utilization Card -->
<div class="card bg-base-100 shadow hover:shadow-md transition-shadow">
<div class="card-body p-4">
<div class="flex items-center justify-between mb-2">
<h2 class="card-title text-sm font-semibold">RAM Utilization</h2>
<div class="text-xs badge badge-ghost">{{ ram_used }} / {{ ram_used|add:ram_free }} GB</div>
</div>
<div class="relative h-40 flex items-center justify-center">
<canvas id="ramChart"></canvas>
<div class="absolute inset-0 flex items-center justify-center">
<div class="text-center">
<div class="text-2xl font-bold text-secondary">{{ ram_used_percentage|floatformat:1 }}%</div>
<div class="text-xs text-base-content/60">Used</div>
</div>
</div>
</div>
<div class="flex justify-between text-xs mt-2">
<span class="flex items-center gap-1">
<span class="w-2 h-2 rounded-full bg-secondary"></span>
Used: {{ ram_used }} GB
</span>
<span class="flex items-center gap-1">
<span class="w-2 h-2 rounded-full bg-base-300"></span>
Free: {{ ram_free }} GB
</span>
</div>
</div>
</div>
<!-- Instance Summary Card - IMPROVED -->
<div class="card bg-base-100 shadow hover:shadow-md transition-shadow col-span-1 lg:col-span-2">
<div class="card-body p-5">
<div class="flex items-center justify-between mb-4">
<h2 class="card-title text-lg font-semibold flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
</svg>
Instance Summary
</h2>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<!-- VM Statistics -->
<div class="bg-base-200 rounded-xl p-4 space-y-4">
<div class="flex items-center justify-between">
<div>
<div class="text-sm font-medium text-base-content/70">Total Instances</div>
<div class="text-3xl font-bold text-primary mt-1">{{ vm_count }}</div>
</div>
<div class="text-primary opacity-80">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
</svg>
</div>
</div>
<div class="space-y-2">
<div class="flex justify-between items-center">
<span class="text-xs text-base-content/60">Active</span>
<span class="font-semibold">{{ vm_active|default:"N/A" }}</span>
</div>
<div class="flex justify-between items-center">
<span class="text-xs text-base-content/60">Stopped</span>
<span class="font-semibold">{{ vm_stopped|default:"N/A" }}</span>
</div>
<div class="flex justify-between items-center">
<span class="text-xs text-base-content/60">Error</span>
<span class="font-semibold">{{ vm_error|default:"0" }}</span>
</div>
</div>
</div>
<!-- Top Flavor -->
<div class="bg-base-200 rounded-xl p-4 space-y-4">
<div class="flex items-center justify-between">
<div>
<div class="text-sm font-medium text-base-content/70">Most Used Flavor</div>
<div class="text-2xl font-bold text-secondary mt-1">{{ common_flavor }}</div>
</div>
<div class="text-secondary opacity-80">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
</div>
</div>
<div class="space-y-3">
<div class="flex items-center justify-between bg-base-300/50 p-2 rounded-lg">
<div>
<div class="text-xs text-base-content/60">Instances</div>
<div class="font-bold">{{ common_flavor_count }}</div>
</div>
<div class="text-right">
<div class="text-xs text-base-content/60">Share</div>
<div class="font-bold">
{{ common_flavor_count|div:vm_count|mul:100|floatformat:0 }}%
</div>
</div>
</div>
{% if second_common_flavor %}
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<span class="text-xs text-base-content/60">2nd:</span>
<span class="font-medium">{{ second_common_flavor.name }}</span>
</div>
<span class="text-xs badge badge-outline">{{ second_common_flavor.count }}</span>
</div>
{% endif %}
{% if third_common_flavor %}
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<span class="text-xs text-base-content/60">3rd:</span>
<span class="font-medium">{{ third_common_flavor.name }}</span>
</div>
<span class="text-xs badge badge-outline">{{ third_common_flavor.count }}</span>
</div>
{% endif %}
</div>
</div>
<!-- Resource Allocation -->
<div class="bg-base-200 rounded-xl p-4 space-y-4">
<div class="flex items-center justify-between">
<div>
<div class="text-sm font-medium text-base-content/70">Resource Allocation</div>
<div class="text-xs text-base-content/60">Provisioned vs Total</div>
</div>
<div class="text-warning opacity-80">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div>
<div class="space-y-3">
<!-- CPU Allocation -->
<div>
<div class="flex justify-between text-xs mb-1">
<div class="flex items-center gap-1">
<span class="w-2 h-2 rounded-full bg-primary"></span>
<span>CPU</span>
</div>
<div class="font-semibold">
{{ cpu_allocated }} / {{ cpu_total }} vCPU
</div>
</div>
{% with pct=cpu_allocated|div:cpu_total|mul:100 %}
<div class="relative pt-1">
<div class="flex mb-2 items-center justify-between">
<div class="text-right">
<span class="text-xs font-semibold inline-block">
{{ pct|floatformat:1 }}%
</span>
</div>
</div>
<div class="overflow-hidden h-2 mb-4 text-xs flex rounded bg-base-300">
<div style="width: {{ pct }}%" class="shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center bg-primary"></div>
</div>
<div class="flex justify-between text-xs">
<span>Allocated</span>
<span class="text-warning">OC: x{{ cpu_overcommit_ratio }}</span>
</div>
</div>
{% endwith %}
</div>
<!-- RAM Allocation -->
<div>
<div class="flex justify-between text-xs mb-1">
<div class="flex items-center gap-1">
<span class="w-2 h-2 rounded-full bg-secondary"></span>
<span>RAM</span>
</div>
<div class="font-semibold">
{{ ram_allocated }} / {{ ram_total }} GB
</div>
</div>
{% with pct=ram_allocated|div:ram_total|mul:100 %}
<div class="relative pt-1">
<div class="flex mb-2 items-center justify-between">
<div class="text-right">
<span class="text-xs font-semibold inline-block">
{{ pct|floatformat:1 }}%
</span>
</div>
</div>
<div class="overflow-hidden h-2 mb-4 text-xs flex rounded bg-base-300">
<div style="width: {{ pct }}%" class="shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center bg-secondary"></div>
</div>
<div class="flex justify-between text-xs">
<span>Allocated</span>
<span class="text-warning">OC: x{{ ram_overcommit_ratio }}</span>
</div>
</div>
{% endwith %}
</div>
</div>
</div>
</div>
<!-- Quick Stats Footer -->
<div class="mt-4 pt-4 border-t border-base-300">
<div class="flex flex-wrap gap-3">
<div class="flex items-center gap-2">
<div class="w-2 h-2 rounded-full bg-success"></div>
<span class="text-xs">Avg. CPU per VM:</span>
<span class="text-xs font-semibold">{{ avg_cpu_per_vm|floatformat:1 }} vCPU</span>
</div>
<div class="flex items-center gap-2">
<div class="w-2 h-2 rounded-full bg-info"></div>
<span class="text-xs">Avg. RAM per VM:</span>
<span class="text-xs font-semibold">{{ avg_ram_per_vm|floatformat:1 }} GB</span>
</div>
<div class="flex items-center gap-2">
<div class="w-2 h-2 rounded-full bg-warning"></div>
<span class="text-xs">Density:</span>
<span class="text-xs font-semibold">{{ vm_density|floatformat:1 }} VMs/host</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- AUDIT ANALYSIS SECTION -->
<div class="p-4">
<div class="card bg-base-100 shadow">
<div class="card-body p-4">
<!-- Selector Row -->
<div class="flex flex-col md:flex-row md:items-center gap-4">
<div class="flex-1">
<label class="label pb-1">
<span class="label-text font-medium">Available Audits</span>
<span class="label-text-alt text-xs">{{ audits|length }} available</span>
</label>
<select id="auditSelector" class="select select-bordered w-full">
{% for audit in audits %}
<option value="{{ audit.id }}"
data-cpu="{{ audit.cpu_weight }}"
data-ram="{{ audit.ram_weight }}"
data-scope="{{ audit.scope }}"
data-strategy="{{ audit.strategy }}"
data-goal="{{ audit.goal }}">
{{ audit.name }} ({{ audit.created_at|date:"m/d/y" }})
</option>
{% endfor %}
</select>
</div>
</div>
<!-- Full Parameters (Initially hidden, can be toggled) -->
<div id="auditDetails" class="mt-4 space-y-3 hidden">
<div class="divider text-xs">Audit Configuration</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<div class="text-sm font-medium mb-2">Resource Weights</div>
<div class="space-y-2">
<div class="flex items-center justify-between">
<span class="text-sm">CPU Priority:</span>
<span class="font-bold text-primary" id="detailCpu">1.0</span>
</div>
<div class="flex items-center justify-between">
<span class="text-sm">RAM Priority:</span>
<span class="font-bold text-secondary" id="detailRam">1.0</span>
</div>
</div>
</div>
<div>
<div class="text-sm font-medium mb-2">Configuration</div>
<div class="space-y-2">
<div class="flex items-center justify-between">
<span class="text-sm">Strategy:</span>
<span class="font-medium" id="detailStrategy">Balanced</span>
</div>
<div class="flex items-center justify-between">
<span class="text-sm">Scope:</span>
<span class="font-medium" id="detailScope">Full Cluster</span>
</div>
</div>
</div>
</div>
<div>
<div class="text-sm font-medium mb-2">Goal</div>
<div class="bg-base-200 rounded p-3 text-sm" id="detailGoal">
Optimize resource distribution
</div>
</div>
</div>
<!-- Simple Footer -->
<div class="flex justify-between items-center mt-4 pt-4 border-t border-base-300">
<div class="text-xs text-base-content/60" id="selectedAuditInfo">
Selected: {{ audits.0.name|default:"No audit selected" }}
</div>
<div class="flex gap-2">
<button class="btn btn-xs btn-ghost" onclick="toggleAuditDetails()">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Details
</button>
<button class="btn btn-xs btn-primary" onclick="loadSelectedAudit()">
Load Audit
</button>
</div>
</div>
</div>
</div>
</div>
<div class="p-4 space-y-4">
<!-- Charts Grid -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
<div class="card bg-base-100 shadow">
<div class="card-body p-4">
<div class="flex items-center justify-between mb-4">
<h3 class="font-semibold">Current CPU Utilization</h3>
<div class="badge badge-primary badge-outline">Live</div>
</div>
<div class="h-64">
<canvas id="cpuHostChart"></canvas>
</div>
<div class="text-center text-xs text-base-content/60 mt-2">
Real-time CPU usage across compute hosts
</div>
<!-- Mean baseline indicator -->
<div class="flex items-center justify-center gap-2 mt-2">
<div class="flex items-center gap-1">
<div class="w-3 h-0.5 bg-success"></div>
<span class="text-xs text-success">Mean: <span id="currentCpuMean">0</span>%</span>
</div>
</div>
</div>
</div>
<div class="card bg-base-100 shadow">
<div class="card-body p-4">
<div class="flex items-center justify-between mb-4">
<h3 class="font-semibold">Projected CPU Utilization</h3>
<div class="badge badge-warning badge-outline">Projection</div>
</div>
<div class="h-64">
<canvas id="cpuProjectedChart"></canvas>
</div>
<div class="text-center text-xs text-base-content/60 mt-2">
Estimated CPU usage after migrations
</div>
<!-- Mean and std deviation indicators -->
<div class="flex items-center justify-center gap-4 mt-2">
<div class="flex items-center gap-1">
<div class="w-3 h-0.5 bg-success"></div>
<span class="text-xs text-success">Mean: <span id="projectedCpuMean">0</span>%</span>
</div>
<div class="flex items-center gap-1">
<div class="w-3 h-0.5 bg-error/60"></div>
<span class="text-xs text-error/60">±1σ: <span id="projectedCpuStd">0</span>%</span>
</div>
</div>
</div>
</div>
</div>
<!-- Migration Table -->
<div class="card bg-base-100 shadow">
<div class="card-body p-4">
<div class="flex items-center justify-between mb-4">
<h3 class="font-semibold">Migration Actions</h3>
<div class="badge badge-neutral" id="migrationCount">0 migrations</div>
</div>
<div class="overflow-x-auto">
<table class="table table-zebra table-sm">
<thead>
<tr class="bg-base-200">
<th class="text-xs font-semibold">Instance</th>
<th class="text-xs font-semibold">Source Host</th>
<th class="text-xs font-semibold">Destination Host</th>
<th class="text-xs font-semibold">Flavor</th>
<th class="text-xs font-semibold">Impact</th>
</tr>
</thead>
<tbody id="migrationTableBody" class="text-sm">
<!-- Will be populated by JavaScript -->
<tr>
<td colspan="5" class="text-center py-8 text-base-content/40">
Select an audit to view migration actions
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
{% block script %}
<script>
// Update preview when selection changes
document.getElementById('auditSelector').addEventListener('change', function(e) {
const option = this.options[this.selectedIndex];
// Update details section if visible
document.getElementById('detailCpu').textContent = option.dataset.cpu || '1.0';
document.getElementById('detailRam').textContent = option.dataset.ram || '1.0';
document.getElementById('detailScope').textContent = option.dataset.scope || 'Full Cluster';
document.getElementById('detailStrategy').textContent = option.dataset.strategy || 'Balanced';
document.getElementById('detailGoal').textContent = option.dataset.goal || 'Optimize resource distribution';
document.getElementById('selectedAuditInfo').textContent = `Selected: ${option.text}`;
});
function toggleAuditDetails() {
const details = document.getElementById('auditDetails');
details.classList.toggle('hidden');
}
function loadSelectedAudit() {
const auditId = document.getElementById('auditSelector').value;
// Your existing load audit logic here
updateMigrationTable(auditId);
updateCPUCharts(auditId);
// Show toast notification
const toast = document.createElement('div');
toast.className = 'toast toast-top toast-end';
toast.innerHTML = `
<div class="alert alert-success">
<span>Audit loaded successfully</span>
</div>
`;
document.body.appendChild(toast);
setTimeout(() => toast.remove(), 3000);
}
// Initialize
document.addEventListener('DOMContentLoaded', function() {
const selector = document.getElementById('auditSelector');
if (selector.options.length > 0) {
selector.dispatchEvent(new Event('change'));
}
});
// Utility function to calculate mean and standard deviation
function calculateStats(data) {
if (!data || data.length === 0) return { mean: 0, std: 0 };
const mean = data.reduce((a, b) => a + b, 0) / data.length;
const variance = data.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / data.length;
const std = Math.sqrt(variance);
return { mean, std };
}
// Chart configuration
const chartConfig = {
cutout: "60%",
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: function(context) {
return `${context.label}: ${context.parsed} vCPU`;
}
}
}
},
animation: {
animateScale: true,
animateRotate: true
}
};
// Initialize CPU and RAM donut charts with mean baselines
const cpuCtx = document.getElementById("cpuChart").getContext('2d');
const ramCtx = document.getElementById("ramChart").getContext('2d');
// Calculate CPU mean for baseline
const cpuMean = {{ cpu_used_percentage }};
const cpuChart = new Chart(cpuCtx, {
type: "doughnut",
data: {
labels: ["Used", "Free"],
datasets: [{
data: [{{ cpu_used }}, {{ cpu_free }}],
backgroundColor: ["#3b82f6", "#e5e7eb"],
borderWidth: 2,
borderColor: "#ffffff"
}]
},
options: {
...chartConfig,
plugins: {
...chartConfig.plugins,
annotation: {
annotations: {
meanLine: {
type: 'line',
yMin: cpuMean,
yMax: cpuMean,
borderColor: '#10b981',
borderWidth: 2,
borderDash: [5, 5],
label: {
enabled: true,
content: `Mean: ${cpuMean.toFixed(1)}%`,
position: 'end',
backgroundColor: '#10b981',
color: 'white',
font: {
size: 10
}
}
}
}
}
}
}
});
// Calculate RAM mean for baseline
const ramMean = {{ ram_used_percentage }};
const ramChart = new Chart(ramCtx, {
type: "doughnut",
data: {
labels: ["Used", "Free"],
datasets: [{
data: [{{ ram_used }}, {{ ram_free }}],
backgroundColor: ["#f97316", "#e5e7eb"],
borderWidth: 2,
borderColor: "#ffffff"
}]
},
options: {
...chartConfig,
plugins: {
...chartConfig.plugins,
annotation: {
annotations: {
meanLine: {
type: 'line',
yMin: ramMean,
yMax: ramMean,
borderColor: '#10b981',
borderWidth: 2,
borderDash: [5, 5],
label: {
enabled: true,
content: `Mean: ${ramMean.toFixed(1)}%`,
position: 'end',
backgroundColor: '#10b981',
color: 'white',
font: {
size: 10
}
}
}
}
}
}
}
});
// Audit data (in production, this should come from Django context)
const auditData = {
{% for audit in audits %}
"{{ audit.id }}": {
name: "{{ audit.name }}",
migrations: {{ audit.migrations|safe }},
hostData: {
labels: {{ audit.host_labels|safe }},
current: {{ audit.cpu_current|safe }},
projected: {{ audit.cpu_projected|safe }}
}
}{% if not forloop.last %},{% endif %}
{% endfor %}
};
// Chart instances
let cpuHostChart = null;
let cpuProjectedChart = null;
// Update migration table
function updateMigrationTable(auditId) {
const tbody = document.getElementById('migrationTableBody');
const migrationCount = document.getElementById('migrationCount');
const data = auditData[auditId];
if (!data || data.migrations.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="5" class="text-center py-8 text-base-content/40">
No migration actions for this audit
</td>
</tr>
`;
migrationCount.textContent = '0 migrations';
return;
}
let html = '';
data.migrations.forEach(migration => {
const impact = migration.impact || 'Low';
const impactClass = {
'Low': 'badge-success',
'Medium': 'badge-warning',
'High': 'badge-error'
}[impact];
html += `
<tr>
<td class="font-medium">${migration.instanceName}</td>
<td><span class="badge badge-outline">${migration.source}</span></td>
<td><span class="badge badge-primary badge-outline">${migration.destination}</span></td>
<td><code class="text-xs">${migration.flavor}</code></td>
<td><span class="badge ${impactClass}">${impact}</span></td>
</tr>
`;
});
tbody.innerHTML = html;
migrationCount.textContent = `${data.migrations.length} migration${data.migrations.length !== 1 ? 's' : ''}`;
}
// Update CPU charts
function updateCPUCharts(auditId) {
const data = auditData[auditId];
if (!data) return;
const hostCtx = document.getElementById('cpuHostChart').getContext('2d');
const projCtx = document.getElementById('cpuProjectedChart').getContext('2d');
// Calculate statistics
const currentStats = calculateStats(data.hostData.current);
const projectedStats = calculateStats(data.hostData.projected);
// Update stats displays
document.getElementById('currentCpuMean').textContent = currentStats.mean.toFixed(1);
document.getElementById('projectedCpuMean').textContent = projectedStats.mean.toFixed(1);
document.getElementById('projectedCpuStd').textContent = projectedStats.std.toFixed(1);
// Create mean and std deviation arrays for projected chart
const meanLine = Array(data.hostData.labels.length).fill(projectedStats.mean);
const stdPlusOne = Array(data.hostData.labels.length).fill(projectedStats.mean + projectedStats.std);
const stdMinusOne = Array(data.hostData.labels.length).fill(projectedStats.mean - projectedStats.std);
// Destroy existing charts
if (cpuHostChart) cpuHostChart.destroy();
if (cpuProjectedChart) cpuProjectedChart.destroy();
// Create current CPU chart with mean baseline
cpuHostChart = new Chart(hostCtx, {
type: 'bar',
data: {
labels: data.hostData.labels,
datasets: [{
label: 'CPU Utilization (%)',
data: data.hostData.current,
backgroundColor: '#3b82f6',
borderRadius: 4,
borderSkipped: false
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
max: 100,
title: {
display: true,
text: 'Percentage (%)'
},
grid: {
drawBorder: false
}
},
x: {
grid: {
display: false
}
}
},
plugins: {
tooltip: {
callbacks: {
label: (ctx) => `${ctx.parsed.y}% CPU`
}
},
annotation: {
annotations: {
meanLine: {
type: 'line',
yMin: currentStats.mean,
yMax: currentStats.mean,
borderColor: '#10b981',
borderWidth: 2,
borderDash: [5, 5],
label: {
enabled: true,
content: `Mean: ${currentStats.mean.toFixed(1)}%`,
position: 'end',
backgroundColor: '#10b981',
color: 'white',
font: {
size: 10
}
}
}
}
}
}
}
});
// Create projected CPU chart with mean and std deviation baselines
cpuProjectedChart = new Chart(projCtx, {
type: 'bar',
data: {
labels: data.hostData.labels,
datasets: [
{
label: 'Projected CPU (%)',
data: data.hostData.projected,
backgroundColor: '#f97316',
borderRadius: 4,
borderSkipped: false
},
{
label: 'Mean',
data: meanLine,
type: 'line',
borderColor: '#10b981',
borderWidth: 2,
borderDash: [5, 5],
fill: false,
pointRadius: 0,
tension: 0
},
{
label: '+1σ',
data: stdPlusOne,
type: 'line',
borderColor: '#ef4444',
borderWidth: 1,
borderDash: [3, 3],
fill: false,
pointRadius: 0,
tension: 0
},
{
label: '-1σ',
data: stdMinusOne,
type: 'line',
borderColor: '#ef4444',
borderWidth: 1,
borderDash: [3, 3],
fill: false,
pointRadius: 0,
tension: 0
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
max: 100,
title: {
display: true,
text: 'Percentage (%)'
},
grid: {
drawBorder: false
}
},
x: {
grid: {
display: false
}
}
},
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: (ctx) => {
if (ctx.datasetIndex === 0) {
return `Projected: ${ctx.parsed.y}%`;
} else if (ctx.datasetIndex === 1) {
return `Mean: ${ctx.parsed.y.toFixed(1)}%`;
} else {
return `${ctx.datasetIndex === 2 ? '+1σ' : '-1σ'}: ${ctx.parsed.y.toFixed(1)}%`;
}
}
}
}
}
}
});
}
// Initialize
document.addEventListener('DOMContentLoaded', () => {
const initialAudit = "{{ audits.0.id }}";
updateMigrationTable(initialAudit);
updateCPUCharts(initialAudit);
});
</script>
{% endblock %}