feat: add services page and API endpoints for service cost breakdown

- Implemented new API endpoints for fetching global services and service tenants details.
- Created a new services.html page with a sidebar for navigation and a chart for service costs.
- Updated frontend JavaScript to handle service data loading and rendering.
- Enhanced the dashboard with provider filtering options for AWS and Azure.
- Added a segmented toggle for selecting the date range for service costs.
- Improved styling for the sidebar and data tables.
- Refactored common utility functions for better reusability across pages.
This commit is contained in:
ludal committed 2026-09-20 21:55:52 +02:00
1 parent 9e78dfa4f2
commit c39364eda4
12 files changed
+834 -114

No files matched your search

+54 -29
View File
@@ -5,6 +5,7 @@ let allTenants = [];
let currentMonths = [];
let currentProvidersTotals = { aws: [], azure: [] };
let monthlyChart = null;
let currentProvider = "all";
const selectedMonths = new Set();
async function loadOverview() {
@@ -60,35 +61,44 @@ function renderMonthlyChart() {
monthlyChart = null;
}
const datasets = [];
if (currentProvider === "all" || currentProvider === "aws") {
datasets.push({
label: "AWS",
data: currentProvidersTotals.aws,
backgroundColor: colorFor(awsColor),
borderColor: surface,
borderWidth: { top: 2, right: 0, bottom: 0, left: 0 },
borderSkipped: false,
borderRadius: { topLeft: 4, topRight: 4 },
stack: "total",
maxBarThickness: 24,
});
}
if (currentProvider === "all" || currentProvider === "azure") {
datasets.push({
label: "Azure",
data: currentProvidersTotals.azure,
backgroundColor: colorFor(azureColor),
borderColor: surface,
borderWidth: { top: 2, right: 0, bottom: 0, left: 0 },
borderSkipped: false,
// Arrondi uniquement quand Azure est seul affiché (filtre "Azure") —
// en vue empilée "Tous", il est au-dessus d'AWS et déjà arrondi par
// le borderRadius du dataset AWS en dessous.
borderRadius: currentProvider === "azure" ? { topLeft: 4, topRight: 4 } : undefined,
stack: "total",
maxBarThickness: 24,
});
}
document.getElementById("legend-aws").style.display = currentProvider === "azure" ? "none" : "";
document.getElementById("legend-azure").style.display = currentProvider === "aws" ? "none" : "";
const ctx = document.getElementById("monthly-chart").getContext("2d");
monthlyChart = new Chart(ctx, {
type: "bar",
data: {
labels,
datasets: [
{
label: "AWS",
data: currentProvidersTotals.aws,
backgroundColor: colorFor(awsColor),
borderColor: surface,
borderWidth: { top: 2, right: 0, bottom: 0, left: 0 },
borderSkipped: false,
borderRadius: { topLeft: 4, topRight: 4 },
stack: "total",
maxBarThickness: 24,
},
{
label: "Azure",
data: currentProvidersTotals.azure,
backgroundColor: colorFor(azureColor),
borderColor: surface,
borderWidth: { top: 2, right: 0, bottom: 0, left: 0 },
borderSkipped: false,
stack: "total",
maxBarThickness: 24,
},
],
},
data: { labels, datasets },
options: {
...chartBaseOptions(),
onHover: (evt, elements) => {
@@ -240,8 +250,9 @@ function renderTenantsTable(tenants) {
function filteredTenants() {
const q = document.getElementById("tenant-search").value.trim().toLowerCase();
if (!q) return allTenants;
return allTenants.filter((t) => t.name.toLowerCase().includes(q));
return allTenants.filter(
(t) => (currentProvider === "all" || t.provider === currentProvider) && (!q || t.name.toLowerCase().includes(q))
);
}
function setupSearch() {
@@ -250,6 +261,19 @@ function setupSearch() {
});
}
function setupProviderToggle() {
const buttons = document.querySelectorAll("#provider-toggle button");
buttons.forEach((btn) => {
btn.classList.toggle("active", btn.dataset.provider === currentProvider);
btn.addEventListener("click", () => {
currentProvider = btn.dataset.provider;
buttons.forEach((b) => b.classList.toggle("active", b === btn));
renderMonthlyChart();
renderTenantsTable(filteredTenants());
});
});
}
async function init() {
try {
const data = await loadOverview();
@@ -258,8 +282,9 @@ async function init() {
currentProvidersTotals = data.providers_totals;
renderKpis(data);
renderMonthlyChart();
renderTenantsTable(allTenants);
renderTenantsTable(filteredTenants());
setupSearch();
setupProviderToggle();
} catch (e) {
document.getElementById("error-banner").innerHTML = `
<div class="banner error"><strong>Erreur de chargement du dashboard</strong> : ${escapeHtml(e.message)}</div>`;