866 lines
36 KiB
HTML
866 lines
36 KiB
HTML
{% 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 %} |