mirror of
				https://github.com/arcan1s/ahriman.git
				synced 2025-10-31 05:43:41 +00:00 
			
		
		
		
	feat: add dashboard (#139)
This commit is contained in:
		| @ -36,7 +36,9 @@ | ||||
|  | ||||
|         <div class="container"> | ||||
|             <div id="toolbar" class="dropdown"> | ||||
|                 <a id="badge-status" tabindex="0" role="button" class="btn btn-outline-secondary" data-bs-toggle="popover" data-bs-trigger="focus" data-bs-content="no run data"><i class="bi bi-info-circle"></i></a> | ||||
|                 <button id="dashboard-button" type="button" class="btn btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#dashboard-modal"> | ||||
|                     <i class="bi bi-info-circle"></i> | ||||
|                 </button> | ||||
|  | ||||
|                 {% if not auth.enabled or auth.username is not none %} | ||||
|                     <button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false"> | ||||
| @ -152,6 +154,7 @@ | ||||
|  | ||||
|         {% include "build-status/alerts.jinja2" %} | ||||
|  | ||||
|         {% include "build-status/dashboard.jinja2" %} | ||||
|         {% include "build-status/package-add-modal.jinja2" %} | ||||
|         {% include "build-status/package-rebuild-modal.jinja2" %} | ||||
|         {% include "build-status/key-import-modal.jinja2" %} | ||||
|  | ||||
| @ -0,0 +1,76 @@ | ||||
| <div id="dashboard-modal" tabindex="-1" role="dialog" class="modal fade"> | ||||
|     <div class="modal-dialog modal-xl" role="document"> | ||||
|         <div class="modal-content"> | ||||
|              <div id="dashboard-modal-header" class="modal-header"> | ||||
|                 <h4 class="modal-title">System health</h4> | ||||
|                 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button> | ||||
|             </div> | ||||
|             <div class="modal-body"> | ||||
|                 <div class="form-group row mt-2"> | ||||
|                     <div class="col-4 col-lg-2" style="text-align: right">Repository name</div> | ||||
|                     <div id="dashboard-name" class="col-8 col-lg-3"></div> | ||||
|                     <div class="col-4 col-lg-2" style="text-align: right">Repository architecture</div> | ||||
|                     <div id="dashboard-architecture" class="col-8 col-lg-3"></div> | ||||
|                 </div> | ||||
|  | ||||
|                 <div class="form-group row mt-2"> | ||||
|                     <div class="col-4 col-lg-2" style="text-align: right">Current status</div> | ||||
|                     <div id="dashboard-status" class="col-8 col-lg-3"></div> | ||||
|                     <div class="col-4 col-lg-2" style="text-align: right">Updated at</div> | ||||
|                     <div id="dashboard-status-timestamp" class="col-8 col-lg-3"></div> | ||||
|                 </div> | ||||
|  | ||||
|                 <div id="dashboard-canvas" class="form-group row mt-2"> | ||||
|                     <div class="col-8 col-lg-6"> | ||||
|                         <canvas id="dashboard-packages-count-chart"></canvas> | ||||
|                     </div> | ||||
|                     <div class="col-8 col-lg-6"> | ||||
|                         <canvas id="dashboard-packages-statuses-chart"></canvas> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="modal-footer"> | ||||
|                 <button type="button" class="btn btn-primary" data-bs-dismiss="modal"><i class="bi bi-x"></i><span class="d-none d-sm-inline"> close</span></button> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
| <script> | ||||
|     const dashboardModal = document.getElementById("dashboard-modal"); | ||||
|     const dashboardModalHeader = document.getElementById("dashboard-modal-header"); | ||||
|  | ||||
|     const dashboardName = document.getElementById("dashboard-name"); | ||||
|     const dashboardArchitecture = document.getElementById("dashboard-architecture"); | ||||
|     const dashboardStatus = document.getElementById("dashboard-status"); | ||||
|     const dashboardStatusTimestamp = document.getElementById("dashboard-status-timestamp"); | ||||
|  | ||||
|     const dashboardCanvas = document.getElementById("dashboard-canvas"); | ||||
|     const dashboardPackagesStatusesChartCanvas = document.getElementById("dashboard-packages-statuses-chart"); | ||||
|     let dashboardPackagesStatusesChart = null; | ||||
|     const dashboardPackagesCountChartCanvas = document.getElementById("dashboard-packages-count-chart"); | ||||
|     let dashboardPackagesCountChart = null; | ||||
|  | ||||
|     ready(_ => { | ||||
|         dashboardPackagesStatusesChart = new Chart(dashboardPackagesStatusesChartCanvas, { | ||||
|             type: "pie", | ||||
|             data: {}, | ||||
|             options: { | ||||
|                 responsive: true, | ||||
|             }, | ||||
|         }); | ||||
|         dashboardPackagesCountChart = new Chart(dashboardPackagesCountChartCanvas, { | ||||
|             type: "bar", | ||||
|             data: {}, | ||||
|             options: { | ||||
|                 maintainAspectRatio: false, | ||||
|                 responsive: true, | ||||
|                 scales: { | ||||
|                     x: { | ||||
|                         stacked: true, | ||||
|                     }, | ||||
|                 }, | ||||
|             }, | ||||
|         }); | ||||
|     }); | ||||
| </script> | ||||
| @ -296,14 +296,6 @@ | ||||
|     } | ||||
|  | ||||
|     function loadPackage(packageBase, onFailure) { | ||||
|         const headerClass = status => { | ||||
|             if (status === "pending") return ["bg-warning"]; | ||||
|             if (status === "building") return ["bg-warning"]; | ||||
|             if (status === "failed") return ["bg-danger", "text-white"]; | ||||
|             if (status === "success") return ["bg-success", "text-white"]; | ||||
|             return ["bg-secondary", "text-white"]; | ||||
|         }; | ||||
|  | ||||
|         makeRequest( | ||||
|             `/api/v1/packages/${packageBase}`, | ||||
|             { | ||||
|  | ||||
| @ -7,7 +7,7 @@ | ||||
|     // so far bootstrap-table only operates with jquery elements | ||||
|     const table = $(document.getElementById("packages")); | ||||
|  | ||||
|     const statusBadge = document.getElementById("badge-status"); | ||||
|     const dashboardButton = document.getElementById("dashboard-button"); | ||||
|     const versionBadge = document.getElementById("badge-version"); | ||||
|  | ||||
|     function doPackageAction(uri, packages, repository, successText, failureText, data) { | ||||
| @ -141,14 +141,62 @@ | ||||
|             data => { | ||||
|                 versionBadge.innerHTML = `<i class="bi bi-github"></i> ahriman ${safe(data.version)}`; | ||||
|  | ||||
|                 statusBadge.classList.remove(...statusBadge.classList); | ||||
|                 statusBadge.classList.add("btn"); | ||||
|                 statusBadge.classList.add(badgeClass(data.status.status)); | ||||
|                 dashboardButton.classList.remove(...dashboardButton.classList); | ||||
|                 dashboardButton.classList.add("btn"); | ||||
|                 dashboardButton.classList.add(badgeClass(data.status.status)); | ||||
|  | ||||
|                 const popover = bootstrap.Popover.getOrCreateInstance(statusBadge); | ||||
|                 popover.dispose(); | ||||
|                 statusBadge.dataset.bsContent = `${data.status.status} at ${new Date(1000 * data.status.timestamp).toISOStringShort()}`; | ||||
|                 bootstrap.Popover.getOrCreateInstance(statusBadge); | ||||
|                 dashboardModalHeader.classList.remove(...dashboardModalHeader.classList); | ||||
|                 dashboardModalHeader.classList.add("modal-header"); | ||||
|                 headerClass(data.status.status).forEach(clz => dashboardModalHeader.classList.add(clz)); | ||||
|  | ||||
|                 dashboardName.textContent = data.repository; | ||||
|                 dashboardArchitecture.textContent = data.architecture; | ||||
|                 dashboardStatus.textContent = data.status.status; | ||||
|                 dashboardStatusTimestamp.textContent = new Date(1000 * data.status.timestamp).toISOStringShort(); | ||||
|  | ||||
|                 if (dashboardPackagesStatusesChart) { | ||||
|                     const labels = [ | ||||
|                         "unknown", | ||||
|                         "pending", | ||||
|                         "building", | ||||
|                         "failed", | ||||
|                         "success", | ||||
|                     ]; | ||||
|                     dashboardPackagesStatusesChart.config.data = { | ||||
|                         labels: labels, | ||||
|                         datasets: [{ | ||||
|                             label: "packages in status", | ||||
|                             data: labels.map(label => data.packages[label]), | ||||
|                             backgroundColor: [ | ||||
|                                 "rgb(55, 58, 60)", | ||||
|                                 "rgb(255, 117, 24)", | ||||
|                                 "rgb(255, 117, 24)", | ||||
|                                 "rgb(255, 0, 57)", | ||||
|                                 "rgb(63, 182, 24)",  // copy-paste from current style | ||||
|                             ], | ||||
|                         }], | ||||
|                     }; | ||||
|                     dashboardPackagesStatusesChart.update(); | ||||
|                 } | ||||
|  | ||||
|                 if (dashboardPackagesCountChart) { | ||||
|                     dashboardPackagesCountChart.config.data = { | ||||
|                         labels: ["packages"], | ||||
|                         datasets: [ | ||||
|                             { | ||||
|                                 label: "archives", | ||||
|                                 data: [data.stats.packages], | ||||
|                             }, | ||||
|                             { | ||||
|                                 label: "bases", | ||||
|                                 data: [data.stats.bases], | ||||
|                             }, | ||||
|                         ], | ||||
|                     }; | ||||
|                     dashboardPackagesCountChart.update(); | ||||
|                 } | ||||
|  | ||||
|                 dashboardCanvas.hidden = data.status.total > 0; | ||||
|             }, | ||||
|         ); | ||||
|     } | ||||
| @ -227,7 +275,6 @@ | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         bootstrap.Popover.getOrCreateInstance(statusBadge); | ||||
|         selectRepository(); | ||||
|     }); | ||||
| </script> | ||||
|  | ||||
| @ -58,6 +58,14 @@ | ||||
|         return value.includes(dataList[index].toLowerCase()); | ||||
|     } | ||||
|  | ||||
|     function headerClass(status) { | ||||
|         if (status === "pending") return ["bg-warning"]; | ||||
|         if (status === "building") return ["bg-warning"]; | ||||
|         if (status === "failed") return ["bg-danger", "text-white"]; | ||||
|         if (status === "success") return ["bg-success", "text-white"]; | ||||
|         return ["bg-secondary", "text-white"]; | ||||
|     } | ||||
|  | ||||
|     function listToTable(data) { | ||||
|         return Array.from(new Set(data)) | ||||
|             .sort() | ||||
|  | ||||
		Reference in New Issue
	
	Block a user