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:
1 parent
9e78dfa4f2
commit
c39364eda4
12 files changed
+834
-114
No files matched your search
+54
-29
@@ -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>`;
|
||||
|
||||
Reference in new issue
Block a user