implement server side rendering for account page,
remove chartjs module
This commit is contained in:
@@ -3,8 +3,6 @@
|
||||
<head>
|
||||
{{template "head" .}}
|
||||
<script src="scripts/account.js" type="module"></script>
|
||||
<script src="modules/chart.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<link rel="modulepreload" href="scripts/utils.js">
|
||||
<link rel="modulepreload" href="scripts/dialog.js">
|
||||
<style>
|
||||
@@ -56,7 +54,23 @@
|
||||
</section>
|
||||
<section class="w3-card w3-padding">
|
||||
<h3>Cluster Resources</h3>
|
||||
<div id="resource-container"></div>
|
||||
<div id="resource-container">
|
||||
{{range .account.Resources}}
|
||||
{{if .Display}}
|
||||
{{if eq .Type "numeric"}}
|
||||
{{template "resource-chart" .}}
|
||||
{{end}}
|
||||
{{if eq .Type "storage"}}
|
||||
{{template "resource-chart" .}}
|
||||
{{end}}
|
||||
{{if eq .Type "list"}}
|
||||
{{range .Resources}}
|
||||
{{template "resource-chart" .}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
|
File diff suppressed because one or more lines are too long
@@ -1,230 +1,14 @@
|
||||
import { dialog } from "./dialog.js";
|
||||
import { requestAPI, setAppearance } from "./utils.js";
|
||||
|
||||
class ResourceChart extends HTMLElement {
|
||||
constructor () {
|
||||
super();
|
||||
this.attachShadow({ mode: "open" });
|
||||
this.shadowRoot.innerHTML = `
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
font-family: monospace;
|
||||
}
|
||||
figure {
|
||||
margin: 0;
|
||||
}
|
||||
div {
|
||||
max-width: 400px;
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
figcaption {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
<style id="responsive-style" media="not all">
|
||||
figure {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
div {
|
||||
max-height: 1lh;
|
||||
}
|
||||
figcaption {
|
||||
margin: 0;
|
||||
margin-left: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1ch;
|
||||
font-size: small;
|
||||
}
|
||||
</style>
|
||||
<figure>
|
||||
<div>
|
||||
<canvas></canvas>
|
||||
</div>
|
||||
<figcaption></figcaption>
|
||||
</figure>
|
||||
`;
|
||||
this.responsiveStyle = this.shadowRoot.querySelector("#responsive-style");
|
||||
this.canvas = this.shadowRoot.querySelector("canvas");
|
||||
this.caption = this.shadowRoot.querySelector("figcaption");
|
||||
}
|
||||
|
||||
set data (data) {
|
||||
for (const line of data.title) {
|
||||
this.caption.innerHTML += `<span>${line}</span>`;
|
||||
}
|
||||
|
||||
this.canvas.role = "img";
|
||||
this.canvas.ariaLabel = data.ariaLabel;
|
||||
|
||||
const chartData = {
|
||||
type: "pie",
|
||||
data: data.data,
|
||||
options: {
|
||||
plugins: {
|
||||
title: {
|
||||
display: false
|
||||
},
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true
|
||||
}
|
||||
},
|
||||
interaction: {
|
||||
mode: "nearest"
|
||||
},
|
||||
onHover: function (e, activeElements) {
|
||||
if (window.innerWidth <= data.breakpoint) {
|
||||
updateTooltipShow(e.chart, false);
|
||||
}
|
||||
else {
|
||||
updateTooltipShow(e.chart, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.chart = new window.Chart(this.canvas, chartData);
|
||||
|
||||
if (data.breakpoint) {
|
||||
this.responsiveStyle.media = `screen and (width <= ${data.breakpoint}px)`;
|
||||
}
|
||||
else {
|
||||
this.responsiveStyle.media = "not all";
|
||||
}
|
||||
}
|
||||
|
||||
get data () {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// this is a really bad way to do this, but chartjs api does not expose many ways to dynamically set hover and tooltip options
|
||||
function updateTooltipShow (chart, enabled) {
|
||||
chart.options.plugins.tooltip.enabled = enabled;
|
||||
chart.options.interaction.mode = enabled ? "nearest" : null;
|
||||
chart.update();
|
||||
}
|
||||
|
||||
customElements.define("resource-chart", ResourceChart);
|
||||
|
||||
window.addEventListener("DOMContentLoaded", init);
|
||||
|
||||
const prefixes = {
|
||||
1024: [
|
||||
"",
|
||||
"Ki",
|
||||
"Mi",
|
||||
"Gi",
|
||||
"Ti"
|
||||
],
|
||||
1000: [
|
||||
"",
|
||||
"K",
|
||||
"M",
|
||||
"G",
|
||||
"T"
|
||||
]
|
||||
};
|
||||
|
||||
async function init () {
|
||||
setAppearance();
|
||||
|
||||
let resources = requestAPI("/user/dynamic/resources");
|
||||
let meta = requestAPI("/global/config/resources");
|
||||
|
||||
resources = await resources;
|
||||
meta = (await meta).resources;
|
||||
|
||||
populateResources("#resource-container", meta, resources);
|
||||
|
||||
document.querySelector("#change-password").addEventListener("click", handlePasswordChangeForm);
|
||||
}
|
||||
|
||||
function populateResources (containerID, meta, resources) {
|
||||
if (resources instanceof Object) {
|
||||
const container = document.querySelector(containerID);
|
||||
Object.keys(meta).forEach((resourceType) => {
|
||||
if (meta[resourceType].display) {
|
||||
if (meta[resourceType].type === "list") {
|
||||
resources[resourceType].total.forEach((listResource) => {
|
||||
createResourceUsageChart(container, listResource.name, listResource.avail, listResource.used, listResource.max, null);
|
||||
});
|
||||
}
|
||||
else {
|
||||
createResourceUsageChart(container, meta[resourceType].name, resources[resourceType].total.avail, resources[resourceType].total.used, resources[resourceType].total.max, meta[resourceType]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function createResourceUsageChart (container, resourceName, resourceAvail, resourceUsed, resourceMax, resourceUnitData) {
|
||||
const chart = document.createElement("resource-chart");
|
||||
container.append(chart);
|
||||
const maxStr = parseNumber(resourceMax, resourceUnitData);
|
||||
const usedStr = parseNumber(resourceUsed, resourceUnitData);
|
||||
const usedRatio = resourceUsed / resourceMax;
|
||||
const R = Math.min(usedRatio * 510, 255);
|
||||
const G = Math.min((1 - usedRatio) * 510, 255);
|
||||
const usedColor = `rgb(${R}, ${G}, 0)`;
|
||||
chart.data = {
|
||||
title: [resourceName, `Used ${usedStr} of ${maxStr}`],
|
||||
ariaLabel: `${resourceName} used ${usedStr} of ${maxStr}`,
|
||||
data: {
|
||||
labels: [
|
||||
"Used",
|
||||
"Available"
|
||||
],
|
||||
datasets: [{
|
||||
label: resourceName,
|
||||
data: [resourceUsed, resourceAvail],
|
||||
backgroundColor: [
|
||||
usedColor,
|
||||
"rgb(140, 140, 140)"
|
||||
],
|
||||
borderWidth: 0,
|
||||
hoverOffset: 4
|
||||
}]
|
||||
},
|
||||
breakpoint: 680
|
||||
};
|
||||
chart.style = "margin: 10px;";
|
||||
}
|
||||
|
||||
function parseNumber (value, unitData) {
|
||||
if (!unitData) {
|
||||
return `${value}`;
|
||||
}
|
||||
const compact = unitData.compact;
|
||||
const multiplier = unitData.multiplier;
|
||||
const base = unitData.base;
|
||||
const unit = unitData.unit;
|
||||
value = multiplier * value;
|
||||
if (value <= 0) {
|
||||
return `0 ${unit}`;
|
||||
}
|
||||
else if (compact) {
|
||||
const exponent = Math.floor(Math.log(value) / Math.log(base));
|
||||
value = value / base ** exponent;
|
||||
const unitPrefix = prefixes[base][exponent];
|
||||
return `${value} ${unitPrefix}${unit}`;
|
||||
}
|
||||
else {
|
||||
return `${value} ${unit}`;
|
||||
}
|
||||
}
|
||||
|
||||
function handlePasswordChangeForm () {
|
||||
const body = `
|
||||
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
||||
|
47
web/templates/resource-chart.tmpl
Normal file
47
web/templates/resource-chart.tmpl
Normal file
@@ -0,0 +1,47 @@
|
||||
{{define "resource-chart"}}
|
||||
<resource-chart>
|
||||
<template shadowrootmode="open">
|
||||
<link rel="stylesheet" href="modules/w3.css">
|
||||
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
font-family: monospace;
|
||||
}
|
||||
#container{
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
padding: 10px 10px 10px 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
progress {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
height: 1em;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
#caption {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
<div id="container">
|
||||
<progress value="{{.Used}}" max="{{.Max}}"></progress>
|
||||
<p id="caption">
|
||||
<span>{{.Name}}</span>
|
||||
{{if eq .Type "list"}}
|
||||
<span>{{.Avail}} {{.Prefix}}{{.Unit}} Avaliable</span>
|
||||
{{else}}
|
||||
<span>{{printf "%.2f" .Avail}} {{.Prefix}}{{.Unit}} Avaliable</span>
|
||||
{{end}}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</resource-chart>
|
||||
{{end}}-
|
Reference in New Issue
Block a user