html front end component for resource monitor

This commit is contained in:
ChrisColeTech 2024-08-24 16:39:54 -04:00
parent 8921ac8d3d
commit 7722d2c305
14 changed files with 998 additions and 1 deletions

1
.gitignore vendored
View File

@ -54,4 +54,3 @@ user_path_config-deprecated.txt
/auth.json
.DS_Store
/.venv
web/

6
web/assets/css/fa-all.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,23 @@
/* fallback */
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url('material.woff2') format('woff2');
}
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
}

Binary file not shown.

250
web/assets/css/styles.css Normal file
View File

@ -0,0 +1,250 @@
#chart-button {
position: fixed !important;
transition: bottom 0.6s ease-in-out, bottom 0.6s ease-in-out,
right 0.6s ease-in-out;
background-color: #00000096 !important;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2) !important;
color: white !important;
border-radius: 10px !important;
width: 225px !important;
height: 0px !important;
z-index: 9998;
}
#chart-button.show {
height: 185px !important;
}
#chart-button.bottom-right {
bottom: 10px !important;
right: -450px !important;
}
#chart-button.bottom-left {
bottom: 10px !important;
right: calc(100vw + 30px + 225px) !important;
}
#chart-button.bottom-center {
bottom: -450px !important;
right: calc(50vw - 85px) !important;
}
#chart-button.top-right {
bottom: calc(100vh - 10px - 185px) !important;
right: -450px !important;
}
#chart-button.top-left {
bottom: calc(100vh - 10px - 185px) !important;
right: calc(100vw + 30px + 225px) !important;
}
#chart-button.top-center {
bottom: calc(100vh + 10px + 185px) !important;
right: calc(50vw - 85px) !important;
}
#chart-button.left-center {
right: calc(100vw + 30px + 225px) !important;
bottom: calc(50vh - 85px) !important;
}
#chart-button.right-center {
right: -450px !important;
bottom: calc(50vh - 85px) !important;
}
#chart-button.bottom-right.active {
right: 10px !important;
}
#chart-button.bottom-left.active {
right: calc(100vw - 30px - 225px) !important;
}
#chart-button.bottom-center.active {
bottom: calc(0vh + 10px) !important;
}
#chart-button.top-right.active {
right: 10px !important;
}
#chart-button.top-left.active {
right: calc(100vw - 30px - 225px) !important;
}
#chart-button.top-center.active {
bottom: calc(100vh - 10px - 185px) !important;
}
#chart-button.left-center.active {
right: calc(100vw - 30px - 225px) !important;
}
#chart-button.right-center.active {
right: 10px !important;
}
.chart-row {
padding: 5px 5px 0px 5px !important;
text-align: right !important;
margin-bottom: -10px !important;
display: flex !important;
justify-content: space-evenly !important;
z-index: 9999 !important;
position: relative !important;
align-items: center;
}
.chart-col {
flex: auto !important;
}
.left-col a {
width: 15px !important;
cursor: pointer !important;
border-radius: 4px !important;
display: inline-block !important;
text-align: center !important;
color: #fff !important;
text-decoration: none !important;
}
.left-col a:hover {
background-color: #fff !important;
color: #000 !important;
}
#chart-container {
width: 225px;
height: 145px;
padding: 5px !important;
}
#chart-container.line {
width: 225px;
padding: 5px !important;
}
canvas.bar {
height: 160px !important;
min-width: 200px !important;
}
canvas.line {
height: 145px !important;
min-width: 200px !important;
}
i {
color: #fff !important; /* Adjust color */
cursor: pointer !important; /* Change cursor to pointer on hover */
}
.toggle-resources-button:hover {
color: rgb(101, 101, 101) !important; /* Change color on hover */
}
#settingsMenu {
display: none; /* Hidden by default */
position: absolute !important;
transform: translateX(-50%) !important; /* Center alignment */
background: #000000 !important;
border: 0px solid #ddd !important;
padding: 0px !important;
border-radius: 6px !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !important;
z-index: 1000 !important;
opacity: 0 !important;
transform: scale(0.8) translateY(-20px) !important; /* Initial hidden state */
transition: opacity 1s ease, transform 0.3s ease !important;
}
#settingsMenu.show {
display: block !important; /* Show the menu */
opacity: 1 !important;
transform: scale(1) translateY(0) !important; /* Animate to visible state */
}
.position-menu {
display: grid !important;
grid-template-columns: repeat(3, 1fr) !important;
grid-template-rows: repeat(3, 1fr) !important;
grid-gap: 0px !important;
}
.position-btn > i {
color: #fff !important;
}
.position-btn {
color: #fff !important;
padding: 6px !important;
margin: 0px !important;
font-size: 12px !important;
cursor: pointer !important;
border: 0px solid #ccc !important;
background-color: transparent !important;
border-radius: 4px !important;
text-align: center !important;
}
.position-btn:hover {
background-color: #fff !important;
}
.position-btn:hover > i {
color: #000 !important;
}
.material-icons {
font-size: 14px !important;
}
.left-col {
text-align: left !important;
margin-right: 10px;
}
.left-col.text {
margin-bottom: 5px !important;
}
.settings-hr {
width: 0px;
margin-top: 0px !important;
margin-bottom: 0px !important;
transition: width 1s ease;
}
.settings-hr.show {
width: 100%;
}
#custom-legend {
display: flex !important;
text-align: center;
}
.custom-legend-item {
margin-bottom: 5px !important;
font-size: 14px !important;
flex: auto !important;
align-items: center;
}
.custom-legend-color {
display: inline-block !important;
width: 15px !important;
height: 15px !important;
margin-right: 10px !important;
vertical-align: middle !important;
}
.custom-legend-text {
display: inline !important;
vertical-align: middle !important;
}
#show_resource_monitor {
cursor: pointer !important;
}

BIN
web/assets/img/clearfix.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.0 KiB

20
web/assets/js/chart.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
web/assets/js/jquery-3.7.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,577 @@
const { hostname } = window.location; // Gets the host without port
const baseUrl = `http://${hostname}:5000`; // Append the port 5000
const apiUrl = `${baseUrl}/gpu_usage/`;
const perfMonitorContainer = $("#perf-monitor-container");
const chartButton = $("#chart-button");
const closeButton = $("#close-button");
let intervalId; // Variable to store the interval ID
// Function to start the interval
function startInterval() {
intervalId = setInterval(updateUsage, 500);
}
// Function to stop the interval
function stopInterval() {
if (intervalId) {
clearInterval(intervalId);
intervalId = null; // Optional: Reset intervalId to indicate no active interval
}
}
shouldShowPerfMonitor = false;
function showPerfMonitor() {
shouldShowPerfMonitor = !shouldShowPerfMonitor;
if (shouldShowPerfMonitor === true) {
startInterval();
} else {
stopInterval();
}
localStorage.setItem("shouldShowPerfMonitor", shouldShowPerfMonitor);
chartButton.toggleClass("active");
setTimeout(() => {
chartButton.toggleClass("show");
}, 300);
}
// Define your color palette
const colorPalette = [
"rgb(240, 193, 90, 0.2)",
"rgb(240, 142, 219, 0.2)",
"rgb(24, 90, 219, 0.2)",
"rgb(127, 161, 195, 0.2)",
"rgb(128, 239, 145, 0.2)",
"rgb(245, 245, 245, 0.2)",
"rgb(240, 142, 219, 0.2)",
];
const borderColors = [
"rgb(240, 193, 90)",
"rgb(240, 142, 219)",
"rgb(24, 90, 219)",
"rgb(127, 161, 195)",
"rgb(128, 239, 145)",
"rgb(245, 245, 245)",
"rgb(240, 142, 219)",
];
// Custom plugin to draw fixed labels in the middle of the chart area
const fixedLabelPlugin = {
id: "fixedLabelPlugin",
afterDatasetsDraw(chart) {
const { ctx, scales, data } = chart;
ctx.save();
const centerX = scales.x.left + scales.x.width / 2;
const labelPositions = [];
data.datasets[0].data.forEach((value, index) => {
const yPos = chart.getDatasetMeta(0).data[index].y;
// Store yPos for positioning labels
labelPositions.push({
x: centerX,
y: yPos,
value: `${value.toFixed(2)}%`,
});
});
ctx.font = "12px Arial";
ctx.fillStyle = "#FFFFFF";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
labelPositions.forEach((label) => {
ctx.fillText(label.value, label.x, label.y);
});
ctx.restore();
},
};
const chartContainer = document.getElementById("chart-container");
let currentChart = null; // Track the current chart instance
const MAX_DATA_POINTS = 50; // Number of data points to keep
// Initialize the bar chart
function initializeBarChart() {
localStorage.setItem("active-chart", "bar");
const existingCanvas = document.getElementById("usage-chart");
if (existingCanvas) {
chartContainer.removeChild(existingCanvas);
}
// Create a new canvas element
const newCanvas = document.createElement("canvas");
newCanvas.id = "usage-chart";
newCanvas.classList.add("bar"); // Add the class directly to the canvas element
chartContainer.appendChild(newCanvas);
const ctx = newCanvas.getContext("2d");
currentChart = new Chart(ctx, {
type: "bar",
data: {
labels: ["CPU", "RAM", "GPU", "VRAM", "HDD"],
datasets: [
{
label: "Usage",
data: [0, 0, 0, 0, 0],
barPercentage: 0.8, // Adjust space occupied by bars
categoryPercentage: 1, // Adjust space between bars
backgroundColor: function (context) {
const value = context.dataset.data[context.dataIndex];
return value > 90 ? "#D9534F" : colorPalette[context.dataIndex];
},
borderColor: function (context) {
const value = context.dataset.data[context.dataIndex];
return value > 90 ? "#D9534F" : borderColors[context.dataIndex];
},
borderWidth: 1.5,
},
],
},
options: {
indexAxis: "y", // Horizontal bars
scales: {
x: {
grid: {
display: false, // Hide all grid lines
},
border: {
display: false, // Hide all grid lines
},
beginAtZero: true,
max: 100,
ticks: {
color: "#ffffff",
font: {
weight: 600,
},
align: "center",
callback: function (value, index, ticks) {
return value + "%";
},
},
},
y: {
grid: {
display: false,
},
border: {
color: "#ffffff30",
width: 1, // Width of the axis border
},
ticks: {
color: "#FFFFFF",
crossAlign: "far",
font: {
weight: 600,
},
},
},
},
plugins: {
legend: {
display: false,
},
tooltip: {
enabled: false,
},
},
responsive: true,
maintainAspectRatio: false,
},
plugins: [fixedLabelPlugin], // Register the custom plugins
});
currentChart.options.animation = true;
const legendContainer = document.getElementById("custom-legend");
legendContainer.innerHTML = "";
document.getElementById("settingsMenu").classList.remove("show"); // Hide the menu
window.addEventListener("resize", () => {
currentChart.resize();
});
}
// Initialize the line chart
function initializeLineChart() {
localStorage.setItem("active-chart", "line");
const existingCanvas = document.getElementById("usage-chart");
if (existingCanvas) {
chartContainer.removeChild(existingCanvas);
}
// Create a new canvas element
const newCanvas = document.createElement("canvas");
newCanvas.id = "usage-chart";
newCanvas.classList.add("line"); // Add the class directly to the canvas element
chartContainer.appendChild(newCanvas);
const ctx = newCanvas.getContext("2d");
// ctx.width = "225px";
// ctx.height = "125px";
currentChart = new Chart(ctx, {
type: "line",
data: {
labels: [],
datasets: [
{
label: "CPU",
data: [],
borderColor: function (context) {
const dataset = context.dataset;
const datasetIndex = context.datasetIndex;
const shouldUseRed = dataset.data.some((value) => value > 90);
if (shouldUseRed) {
generateCustomLegend();
return "#D9534F"; // Return red color if any value exceeds 90
}
return borderColors[datasetIndex % borderColors.length];
},
borderWidth: 1.5,
backgroundColor: function (context) {
const dataset = context.dataset;
const datasetIndex = context.datasetIndex;
const shouldUseRed = dataset.data.some((value) => value > 90);
if (shouldUseRed) {
return "#D9534F"; // Return red color if any value exceeds 90
}
return colorPalette[datasetIndex % borderColors.length];
},
fill: false,
tension: 0.1,
},
{
label: "RAM",
data: [],
borderColor: function (context) {
const dataset = context.dataset;
const datasetIndex = context.datasetIndex;
const shouldUseRed = dataset.data.some((value) => value > 90);
if (shouldUseRed) {
generateCustomLegend();
return "#D9534F"; // Return red color if any value exceeds 90
}
return borderColors[datasetIndex % borderColors.length];
},
borderWidth: 1.5,
backgroundColor: function (context) {
const dataset = context.dataset;
const datasetIndex = context.datasetIndex;
const shouldUseRed = dataset.data.some((value) => value > 90);
if (shouldUseRed) {
return "#D9534F"; // Return red color if any value exceeds 90
}
return colorPalette[datasetIndex % borderColors.length];
},
fill: false,
tension: 0.1,
},
{
label: "GPU",
data: [],
borderColor: function (context) {
const dataset = context.dataset;
const datasetIndex = context.datasetIndex;
const shouldUseRed = dataset.data.some((value) => value > 90);
if (shouldUseRed) {
generateCustomLegend();
return "#D9534F"; // Return red color if any value exceeds 90
}
return borderColors[datasetIndex % borderColors.length];
},
borderWidth: 1.5,
backgroundColor: function (context) {
const dataset = context.dataset;
const datasetIndex = context.datasetIndex;
const shouldUseRed = dataset.data.some((value) => value > 90);
if (shouldUseRed) {
return "#D9534F"; // Return red color if any value exceeds 90
}
return colorPalette[datasetIndex % borderColors.length];
},
fill: false,
tension: 0.1,
},
{
label: "VRAM",
data: [],
borderColor: function (context) {
const dataset = context.dataset;
const datasetIndex = context.datasetIndex;
const shouldUseRed = dataset.data.some((value) => value > 90);
if (shouldUseRed) {
generateCustomLegend();
return "#D9534F"; // Return red color if any value exceeds 90
}
return borderColors[datasetIndex % borderColors.length];
},
borderWidth: 1.5,
backgroundColor: function (context) {
const dataset = context.dataset;
const datasetIndex = context.datasetIndex;
const shouldUseRed = dataset.data.some((value) => value > 90);
if (shouldUseRed) {
return "#D9534F"; // Return red color if any value exceeds 90
}
return colorPalette[datasetIndex % borderColors.length];
},
fill: false,
tension: 0.1,
},
{
label: "HDD",
data: [],
borderColor: function (context) {
const dataset = context.dataset;
const datasetIndex = context.datasetIndex;
const shouldUseRed = dataset.data.some((value) => value > 90);
if (shouldUseRed) {
generateCustomLegend();
return "#D9534F"; // Return red color if any value exceeds 90
}
return borderColors[datasetIndex % borderColors.length];
},
borderWidth: 1.5,
backgroundColor: function (context) {
const dataset = context.dataset;
const datasetIndex = context.datasetIndex;
const shouldUseRed = dataset.data.some((value) => value > 90);
if (shouldUseRed) {
return "#D9534F"; // Return red color if any value exceeds 90
}
return colorPalette[datasetIndex % borderColors.length];
},
fill: false,
tension: 0.1,
},
],
},
options: {
animation: {
enabled: false,
tension: {
duration: 1000,
easing: "linear",
from: 1,
to: 0,
loop: true,
},
},
elements: {
point: {
radius: 0,
},
},
scales: {
x: {
ticks: {
display: false,
},
},
y: {
beginAtZero: true,
max: 100,
ticks: {
callback: function (value, index, ticks) {
return value + "%";
},
},
},
},
responsive: true,
plugins: {
legend: {
display: true,
labels: {
generateLabels: false,
},
},
title: {
display: false,
},
},
},
});
function generateCustomLegend() {
const legendContainer = document.getElementById("custom-legend");
legendContainer.innerHTML = "";
currentChart.data.datasets.forEach((dataset, index) => {
const legendItem = document.createElement("div");
legendItem.className = "custom-legend-item";
// Create text element
const legendText = document.createElement("span");
legendText.className = "custom-legend-text";
legendText.textContent = dataset.label;
const shouldUseRed = dataset.data.some((value) => value > 90);
legendText.style.color = shouldUseRed
? "#D9534F"
: `${borderColors[index]}`;
legendText.style.fontWeight = shouldUseRed ? "700" : `400`;
legendItem.appendChild(legendText);
legendContainer.appendChild(legendItem);
});
}
currentChart.options.animation = false;
generateCustomLegend();
document.getElementById("settingsMenu").classList.remove("show"); // Hide the menu
window.addEventListener("resize", () => {
currentChart.resize();
});
}
async function updateUsage() {
try {
const response = await fetch(apiUrl);
const data = await response.json();
const timestamp = new Date();
if (currentChart) {
if (currentChart.config.type === "bar") {
// Update data for bar chart
currentChart.data.datasets[0].data = [
data.cpu,
data.memory,
data.gpu,
data.vram,
data.hdd,
];
} else if (currentChart.config.type === "line") {
// Update data for line chart
currentChart.data.labels.push(timestamp);
currentChart.data.datasets[0].data.push(data.cpu);
currentChart.data.datasets[1].data.push(data.memory);
currentChart.data.datasets[2].data.push(data.gpu);
currentChart.data.datasets[3].data.push(data.vram);
currentChart.data.datasets[4].data.push(data.hdd);
// Prune old data if the number of points exceeds the limit
if (currentChart.data.labels.length > MAX_DATA_POINTS) {
currentChart.data.labels.shift(); // Remove the oldest label
currentChart.data.datasets.forEach((dataset) => dataset.data.shift()); // Remove the oldest data points
}
}
// Update the chart with new data
currentChart.update();
}
} catch (error) {
console.error("Failed to fetch usage data.", error);
}
}
// Show or hide the settings menu when the settings icon is clicked
document
.getElementById("popupTrigger")
.addEventListener("click", function (event) {
const settingsMenu = document.getElementById("settingsMenu");
settingsMenu.classList.toggle("show"); // Toggle the 'show' class for animation
setTimeout(() => {
const settingsMenuHr = document.getElementById("settings-hr");
settingsMenuHr.classList.add("show"); // Toggle the 'show' class for animation
}, 300);
event.stopPropagation();
});
// Hide the settings menu when the close button is clicked
document.getElementById("close-button").addEventListener("click", function () {
document.getElementById("settingsMenu").classList.remove("show"); // Hide the menu
});
// Hide the settings menu when clicking outside
window.addEventListener("click", function (e) {
const settingsMenu = document.getElementById("settingsMenu");
const trigger = document.getElementById("popupTrigger");
if (!settingsMenu.contains(e.target) && e.target !== trigger) {
settingsMenu.classList.remove("show"); // Hide the menu if clicking outside
}
});
document.querySelectorAll(".position-btn").forEach((button) => {
button.addEventListener("click", function () {
const position = this.id;
// Remove all possible position classes
const chartButton = document.getElementById("chart-button");
chartButton.classList.remove(
"bottom-right",
"bottom-left",
"top-right",
"top-left",
"top-center",
"bottom-center",
"left-center",
"right-center"
);
setMonitorPosition(position);
// Save the position to localStorage
localStorage.setItem("perf-monitor-position", position);
});
});
// Set the initial position based on localStorage
const perfMonitordisplayed = JSON.parse(
localStorage.getItem("shouldShowPerfMonitor")
);
const savedPosition =
localStorage.getItem("perf-monitor-position") ?? "bottom-right";
setMonitorPosition(savedPosition);
const savedChart = localStorage.getItem("active-chart") ?? "bar";
if (perfMonitordisplayed == true) {
// Remove previous position classes
const chartButton = document.getElementById("chart-button");
chartButton.classList.remove(
"bottom-right",
"bottom-left",
"top-right",
"top-left",
"top-center",
"bottom-center",
"left-center",
"right-center"
);
setMonitorPosition(savedPosition);
setTimeout(() => {
showPerfMonitor();
if (savedChart == "bar") {
initializeBarChart();
} else {
initializeLineChart();
}
}, 100);
}
function setMonitorPosition(position) {
chartButton.addClass(position);
const settingsMenu = document.getElementById("settingsMenu");
settingsMenu.classList.remove("show"); // Hide the menu if visible
return;
}

0
web/templates/index.html Normal file
View File

View File

@ -0,0 +1,56 @@
<div id="perf-monitor-container"></div>
<img
src="/file=web/assets/img/clearfix.png"
onload="{
var footer = document.querySelector('footer');
var link = document.createElement('a');
// Add multiple classes correctly using the spread operator
link.classList.add('built-with', 'svelte-1ax1toq');
link.id = 'show_resource_monitor';
link.text = 'Resource Monitor';
link.onclick = function() { showPerfMonitor(); }; // Use function reference instead of string
var linkImg = document.createElement('img')
linkImg.src = '/file=web/assets/img/monitor.svg';
linkImg.classList.add('svelte-1ax1toq')
link.appendChild(linkImg);
footer.appendChild(link);
var script = document.createElement('script');
script.src = '/file=web/assets/js/jquery-3.7.1.min.js';
document.body.appendChild(script);
var script = document.createElement('script');
script.src = '/file=web/assets/js/chart.js';
document.body.appendChild(script);
async function loadHtmlContent() {
const response = await fetch('/file=web/templates/perf-monitor/perf-monitor.html');
document.getElementById('perf-monitor-container').innerHTML = await response.text();
var styles = document.createElement('link');
styles.href = '/file=web/assets/css/styles.css';
styles.property = 'stylesheet'
styles.rel = 'stylesheet'
document.body.appendChild(styles);
var fa = document.createElement('link');
fa.href = '/file=web/assets/css/material-icon.css';
fa.property = 'stylesheet'
fa.rel = 'stylesheet'
document.body.appendChild(fa);
var script = document.createElement('script');
script.src = '/file=web/assets/js/chartjs-plugin-datalabels.js';
document.body.appendChild(script);
var script = document.createElement('script');
script.src = '/file=web/assets/js/perf-monitor.js';
document.body.appendChild(script);
}
loadHtmlContent();
}"
/>

View File

@ -0,0 +1,56 @@
<div id="chart-button">
<div class="chart-row">
<div class="left-col">
<i class="material-icons" id="popupTrigger">settings</i>
<div id="settingsMenu" class="settings-menu">
<div class="left-col text">
Layout : <a onclick="initializeBarChart()"> 1</a> |
<a onclick="initializeLineChart()"> 2</a>
</div>
<hr id="settings-hr" class="settings-hr" />
<span>Position:</span>
<div id="positionMenu" class="position-menu">
<button class="position-btn" id="top-left">
<i class="material-icons">north_west</i>
</button>
<button class="position-btn" id="top-center">
<i class="material-icons">north</i>
</button>
<button class="position-btn" id="top-right">
<i class="material-icons">north_east</i>
</button>
<button class="position-btn" id="left-center">
<i class="material-icons">west</i>
</button>
<button class="position-btn" id="center">
<i class="material-icons">radio_button_checked</i>
</button>
<button class="position-btn" id="right-center">
<i class="material-icons">east</i>
</button>
<button class="position-btn" id="bottom-left">
<i class="material-icons">south_west</i>
</button>
<button class="position-btn" id="bottom-center">
<i class="material-icons">south</i>
</button>
<button class="position-btn" id="bottom-right">
<i class="material-icons">south_east</i>
</button>
</div>
</div>
</div>
<div class="chart-col">
<i class="material-icons" id="close-button" onclick="showPerfMonitor()"
>close</i
>
</div>
</div>
<div>
<div id="chart-container">
<canvas id="usage-chart" style="width: 100%; height: 100%"></canvas>
</div>
<div id="custom-legend"></div>
</div>
</div>