final touches on the perf monitor
This commit is contained in:
parent
85429b04fc
commit
4c32ebe390
|
|
@ -1,12 +1,13 @@
|
|||
from flask_restx import Api, Resource, fields, Namespace
|
||||
from flask import Flask, jsonify, render_template, send_from_directory, Blueprint, request, jsonify, make_response
|
||||
from flask import jsonify, request, make_response, Blueprint
|
||||
import psutil
|
||||
import GPUtil
|
||||
import time
|
||||
|
||||
# Create a Blueprint for the gpu_usage controller
|
||||
gpu_usage_bp = Blueprint('gpu_usage', __name__)
|
||||
gpu_usage_api = Api(gpu_usage_bp, version='1.0', title='gpu_usage API',
|
||||
description='API for managing gpu_usage')
|
||||
description='API for managing gpu_usage')
|
||||
|
||||
# Define a namespace for gpu_usage
|
||||
gpu_usage_ns = Namespace('gpu_usage', description='gpu usage operations')
|
||||
|
|
@ -18,25 +19,64 @@ gpu_model = gpu_usage_ns.model('gpu_usage', {
|
|||
'status': fields.String(description='Status of the gpu')
|
||||
})
|
||||
|
||||
|
||||
# Cache for system usage data
|
||||
cache = {
|
||||
'timestamp': 0,
|
||||
'data': {
|
||||
'cpu': 0,
|
||||
'memory': 0,
|
||||
'ram': 0,
|
||||
'gpu': 0,
|
||||
'vram': 0,
|
||||
'hdd': 0
|
||||
'hdd': 0,
|
||||
'temp': 0
|
||||
}
|
||||
}
|
||||
CACHE_DURATION = 1 # Cache duration in seconds
|
||||
|
||||
|
||||
@gpu_usage_ns.route('/')
|
||||
class GPUInfo(Resource):
|
||||
def get_cache(self, current_time):
|
||||
# Get CPU utilization
|
||||
cpu_percent = psutil.cpu_percent(interval=0)
|
||||
|
||||
# Get Memory utilization
|
||||
mem = psutil.virtual_memory()
|
||||
mem_percent = mem.percent
|
||||
|
||||
# Get GPU utilization (considering only the first GPU)
|
||||
gpus = GPUtil.getGPUs()
|
||||
gpu_percent = gpus[0].load * 100 if gpus else 0
|
||||
|
||||
# Get VRAM usage (considering only the first GPU)
|
||||
vram_usage = 0
|
||||
if gpus:
|
||||
used = gpus[0].memoryUsed
|
||||
total = gpus[0].memoryTotal
|
||||
vram_usage = (used / total) * 100
|
||||
|
||||
# Get HDD usage (assuming usage of the primary disk)
|
||||
hdd = psutil.disk_usage('/')
|
||||
hdd_percent = hdd.percent
|
||||
|
||||
# Get temperature (if available)
|
||||
temperature = gpus[0].temperature
|
||||
|
||||
# Update the cache
|
||||
cache['data'] = {
|
||||
'cpu': cpu_percent,
|
||||
'ram': mem_percent,
|
||||
'gpu': gpu_percent,
|
||||
'vram': vram_usage, # Convert bytes to MB
|
||||
'hdd': hdd_percent,
|
||||
'temp': temperature # Add temperature
|
||||
}
|
||||
cache['timestamp'] = current_time
|
||||
return cache
|
||||
|
||||
def get(self):
|
||||
if request.method == "OPTIONS": # CORS preflight
|
||||
return _build_cors_preflight_response()
|
||||
return _build_cors_preflight_response()
|
||||
|
||||
current_time = time.time()
|
||||
|
||||
|
|
@ -45,41 +85,11 @@ class GPUInfo(Resource):
|
|||
return _corsify_actual_response(jsonify(cache['data']))
|
||||
|
||||
try:
|
||||
# Get CPU utilization
|
||||
cpu_percent = psutil.cpu_percent(interval=0)
|
||||
|
||||
# Get Memory utilization
|
||||
mem = psutil.virtual_memory()
|
||||
mem_percent = mem.percent
|
||||
|
||||
# Get GPU utilization (considering only the first GPU)
|
||||
gpus = GPUtil.getGPUs()
|
||||
gpu_percent = gpus[0].load * 100 if gpus else 0
|
||||
|
||||
# Get VRAM usage (considering only the first GPU)
|
||||
vram_usage = 0
|
||||
if gpus:
|
||||
used = gpus[0].memoryUsed
|
||||
total = gpus[0].memoryTotal
|
||||
vram_usage = (used / total) * 100
|
||||
|
||||
# Get HDD usage (assuming usage of the primary disk)
|
||||
hdd = psutil.disk_usage('/')
|
||||
hdd_percent = hdd.percent
|
||||
|
||||
# Update the cache
|
||||
cache['data'] = {
|
||||
'cpu': cpu_percent,
|
||||
'memory': mem_percent,
|
||||
'gpu': gpu_percent,
|
||||
'vram': vram_usage, # Convert bytes to MB
|
||||
'hdd': hdd_percent
|
||||
}
|
||||
cache['timestamp'] = current_time
|
||||
self.get_cache(current_time)
|
||||
|
||||
return _corsify_actual_response(jsonify(cache['data']))
|
||||
except Exception as e:
|
||||
return _corsify_actual_response(jsonify({'error': str(e)}), 500)
|
||||
return _corsify_actual_response(jsonify({'error': str(e)}))
|
||||
|
||||
|
||||
def _build_cors_preflight_response():
|
||||
|
|
@ -92,4 +102,4 @@ def _build_cors_preflight_response():
|
|||
|
||||
def _corsify_actual_response(response):
|
||||
response.headers.add("Access-Control-Allow-Origin", "*")
|
||||
return response
|
||||
return response
|
||||
|
|
|
|||
|
|
@ -1,22 +1,13 @@
|
|||
from flask import Flask, send_from_directory, jsonify
|
||||
from flask import Flask, send_from_directory, jsonify, render_template
|
||||
from flask_restx import Api
|
||||
import threading
|
||||
import logging
|
||||
from flask_cors import CORS
|
||||
|
||||
# Adjusted import for fooocus_version and shared
|
||||
from api.controllers import register_blueprints
|
||||
import fooocus_version
|
||||
import shared
|
||||
import args_manager
|
||||
from .controllers import register_blueprints
|
||||
import os
|
||||
import gradio as gr
|
||||
import dependency_installer
|
||||
|
||||
|
||||
dependency_installer.check_flask_installed()
|
||||
dependency_installer.check_GPUtil_installed()
|
||||
dependency_installer.check_tkinter_installed()
|
||||
import shared
|
||||
|
||||
def load_page(filename):
|
||||
"""Load an HTML file as a string and return it"""
|
||||
|
|
@ -32,30 +23,14 @@ def addResourceMonitor():
|
|||
|
||||
return ceq
|
||||
|
||||
|
||||
# Cache for system usage data
|
||||
cache = {
|
||||
'timestamp': 0,
|
||||
'data': {
|
||||
'cpu': 0,
|
||||
'memory': 0,
|
||||
'gpu': 0,
|
||||
'vram': 0,
|
||||
'hdd': 0
|
||||
}
|
||||
}
|
||||
CACHE_DURATION = 1 # Cache duration in seconds
|
||||
|
||||
|
||||
|
||||
# Suppress the Flask development server warning
|
||||
log = logging.getLogger('werkzeug')
|
||||
log.setLevel(logging.ERROR) # Set level to ERROR to suppress warnings
|
||||
|
||||
title = f"Fooocus version: {fooocus_version.version}"
|
||||
app = Flask(title, static_folder='web', template_folder='web')
|
||||
title = f"Elegant Resource Monitor"
|
||||
app = Flask(title, static_folder='web/assets', template_folder='web/templates')
|
||||
app.config['CORS_HEADERS'] = 'Content-Type'
|
||||
api = Api(app, version='1.0', title=title, description='Fooocus REST API')
|
||||
api = Api(app, version='1.0', title=title, description='Elegant Resource Monitor REST API')
|
||||
|
||||
# Register blueprints (API endpoints)
|
||||
register_blueprints(app, api)
|
||||
|
|
@ -64,7 +39,6 @@ register_blueprints(app, api)
|
|||
CORS(app, resources={r"/*": {"origins": "*"}})
|
||||
|
||||
gradio_app = shared.gradio_root
|
||||
# Serve static files from the 'web' folder
|
||||
|
||||
@app.route('/<path:filename>')
|
||||
def serve_static(filename):
|
||||
|
|
@ -76,7 +50,6 @@ def config():
|
|||
'base_url': f"http://{str(args_manager.args.listen)}:5000"
|
||||
})
|
||||
|
||||
|
||||
def run_app():
|
||||
app.run(port=5000)
|
||||
|
||||
|
|
@ -84,8 +57,3 @@ def run_app():
|
|||
# Start Flask app in a separate thread
|
||||
thread = threading.Thread(target=run_app)
|
||||
thread.start()
|
||||
|
||||
print(
|
||||
f" * REST API Server Running at http://{str(args_manager.args.listen)}:5000 or {str(args_manager.args.listen)}:5000")
|
||||
print(
|
||||
f" * Open http://{str(args_manager.args.listen)}:5000 or {str(args_manager.args.listen)}:5000 in a browser to view REST endpoints")
|
||||
|
|
@ -1,16 +1,29 @@
|
|||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
import shutil
|
||||
import zipfile
|
||||
import importlib
|
||||
import urllib.request
|
||||
from modules.launch_util import run_pip
|
||||
import re
|
||||
import torch
|
||||
|
||||
|
||||
re_requirement = re.compile(r"\s*([-\w]+)\s*(?:==\s*([-+.\w]+))?\s*")
|
||||
|
||||
python = sys.executable
|
||||
default_command_live = (os.environ.get('LAUNCH_LIVE_OUTPUT') == "1")
|
||||
index_url = os.environ.get('INDEX_URL', "")
|
||||
|
||||
modules_path = os.path.dirname(os.path.realpath(__file__))
|
||||
script_path = os.path.dirname(modules_path)
|
||||
|
||||
|
||||
def detect_python_version():
|
||||
version = sys.version_info
|
||||
version_str = f"{version.major}.{version.minor}"
|
||||
is_embedded = hasattr(sys, '_base_executable') or (sys.base_prefix != sys.prefix and not hasattr(sys, 'real_prefix'))
|
||||
is_embedded = hasattr(sys, '_base_executable') or (
|
||||
sys.base_prefix != sys.prefix and not hasattr(sys, 'real_prefix'))
|
||||
return version_str, is_embedded
|
||||
|
||||
|
||||
|
|
@ -26,7 +39,7 @@ def check_tkinter_installed():
|
|||
if not tkinter_installed or (is_embedded and not tkinter_installed):
|
||||
install_tkinter(version_str)
|
||||
|
||||
|
||||
|
||||
def check_GPUtil_installed():
|
||||
if not torch.cuda.is_available():
|
||||
return False
|
||||
|
|
@ -37,15 +50,16 @@ def check_GPUtil_installed():
|
|||
except ImportError:
|
||||
import_GPUtil()
|
||||
return False
|
||||
|
||||
|
||||
|
||||
|
||||
def check_flask_installed():
|
||||
if not torch.cuda.is_available():
|
||||
return False
|
||||
|
||||
try:
|
||||
import flask
|
||||
import flask_restx
|
||||
import flask_cors
|
||||
return True
|
||||
except ImportError:
|
||||
import_flask()
|
||||
|
|
@ -67,19 +81,37 @@ def download_and_unzip_tkinter():
|
|||
|
||||
|
||||
def copy_tkinter_files(version_str):
|
||||
src_folder = os.path.join("tkinter-standalone", version_str, "python_embedded")
|
||||
number_only = version_str.replace(".","")
|
||||
src_folder = os.path.join("tkinter-standalone",
|
||||
version_str, "python_embedded")
|
||||
number_only = version_str.replace(".", "")
|
||||
python_zip = f"python{number_only}"
|
||||
python_zip_path = os.path.join(src_folder, f"{python_zip}.zip")
|
||||
with zipfile.ZipFile(python_zip_path, 'r') as zip_ref:
|
||||
zip_ref.extractall(os.path.join(src_folder, python_zip))
|
||||
|
||||
|
||||
if not os.path.exists(src_folder):
|
||||
print(f"Error: No tkinter files for Python {version_str}")
|
||||
return
|
||||
|
||||
# Define paths
|
||||
python_dir = os.path.dirname(sys.executable)
|
||||
pth_filename = f"{python_zip}._pth"
|
||||
pth_path_src = os.path.join(src_folder, pth_filename)
|
||||
pth_path_dest = os.path.join(python_dir, pth_filename)
|
||||
|
||||
# Copy the .pth file from python_dir to src_folder
|
||||
if os.path.exists(pth_path_dest):
|
||||
shutil.copy(pth_path_dest, pth_path_src)
|
||||
else:
|
||||
print(f"Error: {pth_filename} not found in {python_dir}")
|
||||
return
|
||||
|
||||
# Modify the .pth file
|
||||
with open(pth_path_src, 'a') as pth_file:
|
||||
pth_file.write(f'\n./{python_zip}\n')
|
||||
pth_file.write('./Scripts\n')
|
||||
pth_file.write('./DLLs\n')
|
||||
|
||||
print(f"Copying tkinter files from {src_folder} to {python_dir}...")
|
||||
shutil.copytree(src_folder, python_dir, dirs_exist_ok=True)
|
||||
|
||||
|
|
@ -91,26 +123,36 @@ def install_tkinter(version_str):
|
|||
download_and_unzip_tkinter()
|
||||
copy_tkinter_files(version_str)
|
||||
import_tkinter()
|
||||
|
||||
|
||||
|
||||
def import_tkinter():
|
||||
try:
|
||||
tkinter = importlib.import_module("tkinter")
|
||||
print("tkinter module loaded successfully.")
|
||||
return tkinter
|
||||
except ImportError:
|
||||
except ModuleNotFoundError as e:
|
||||
print(f"Module not found: {e}")
|
||||
except ImportError as e:
|
||||
print("Failed to import Tkinter after installation.")
|
||||
return None
|
||||
|
||||
|
||||
print(f"An error occurred: {e}")
|
||||
except Exception as e:
|
||||
print(f"An error occurred: {e}")
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def import_GPUtil():
|
||||
run_pip(f"install GPUtil")
|
||||
run_pip(f"install GPUtil",desc="GPU Utility for NVIDIA GPUs")
|
||||
|
||||
try:
|
||||
GPUtil = importlib.import_module("GPUtil", desc="GPU Performance Monitor" )
|
||||
GPUtil = importlib.import_module(
|
||||
"GPUtil")
|
||||
return GPUtil
|
||||
except ImportError:
|
||||
print("Failed to import GPUtil after installation.")
|
||||
return None
|
||||
|
||||
|
||||
|
||||
def import_flask():
|
||||
run_pip(f"install flask flask-restx flask-cors", desc="Flask Rest API")
|
||||
|
||||
|
|
@ -122,6 +164,50 @@ def import_flask():
|
|||
print("Failed to import flask after installation.")
|
||||
return None
|
||||
|
||||
|
||||
def run(command, desc=None, errdesc=None, custom_env=None, live: bool = default_command_live) -> str:
|
||||
if desc is not None:
|
||||
print(desc)
|
||||
|
||||
run_kwargs = {
|
||||
"args": command,
|
||||
"shell": True,
|
||||
"env": os.environ if custom_env is None else custom_env,
|
||||
"encoding": 'utf8',
|
||||
"errors": 'ignore',
|
||||
}
|
||||
|
||||
if not live:
|
||||
run_kwargs["stdout"] = run_kwargs["stderr"] = subprocess.PIPE
|
||||
|
||||
result = subprocess.run(**run_kwargs)
|
||||
|
||||
if result.returncode != 0:
|
||||
error_bits = [
|
||||
f"{errdesc or 'Error running command'}.",
|
||||
f"Command: {command}",
|
||||
f"Error code: {result.returncode}",
|
||||
]
|
||||
if result.stdout:
|
||||
error_bits.append(f"stdout: {result.stdout}")
|
||||
if result.stderr:
|
||||
error_bits.append(f"stderr: {result.stderr}")
|
||||
raise RuntimeError("\n".join(error_bits))
|
||||
|
||||
return (result.stdout or "")
|
||||
|
||||
|
||||
def run_pip(command, desc=None, live=default_command_live):
|
||||
try:
|
||||
index_url_line = f' --index-url {index_url}' if index_url != '' else ''
|
||||
return run(f'"{python}" -m pip {command} --prefer-binary{index_url_line}', desc=f"Installing {desc}",
|
||||
errdesc=f"Couldn't install {desc}", live=live)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print(f'CMD Failed {desc}: {command}')
|
||||
return None
|
||||
|
||||
|
||||
check_tkinter_installed()
|
||||
check_GPUtil_installed()
|
||||
check_flask_installed()
|
||||
check_flask_installed()
|
||||
|
|
|
|||
|
|
@ -1,101 +1,87 @@
|
|||
#chart-button {
|
||||
position: fixed !important;
|
||||
transition: bottom 0.6s ease-in-out, bottom 0.6s ease-in-out,
|
||||
right 0.6s ease-in-out;
|
||||
transition: bottom 0.6s ease-in-out, right 0.6s ease-in-out, height 0.4s ease,
|
||||
width 0.4s ease;
|
||||
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;
|
||||
bottom: 100px;
|
||||
height: 0px !important;
|
||||
}
|
||||
|
||||
#chart-button.bottom-right {
|
||||
bottom: 10px !important;
|
||||
right: -450px !important;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#chart-button.bottom-left {
|
||||
bottom: 10px !important;
|
||||
right: calc(100vw + 30px + 225px) !important;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#chart-button.bottom-center {
|
||||
bottom: -450px !important;
|
||||
right: calc(50vw - 85px) !important;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#chart-button.top-right {
|
||||
bottom: calc(100vh - 10px - 185px) !important;
|
||||
right: -450px !important;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#chart-button.top-left {
|
||||
bottom: calc(100vh - 10px - 185px) !important;
|
||||
right: calc(100vw + 30px + 225px) !important;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#chart-button.top-center {
|
||||
bottom: calc(100vh + 10px + 185px) !important;
|
||||
right: calc(50vw - 85px) !important;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#chart-button.left-center {
|
||||
right: calc(100vw + 30px + 225px) !important;
|
||||
bottom: calc(50vh - 85px) !important;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#chart-button.right-center {
|
||||
right: -450px !important;
|
||||
bottom: calc(50vh - 85px) !important;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#chart-button.center {
|
||||
margin: 0px;
|
||||
}
|
||||
#chart-button.bottom-right.active {
|
||||
right: 10px !important;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#chart-button.bottom-left.active {
|
||||
right: calc(100vw - 30px - 225px) !important;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#chart-button.bottom-center.active {
|
||||
bottom: calc(0vh + 10px) !important;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#chart-button.top-right.active {
|
||||
right: 10px !important;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#chart-button.top-left.active {
|
||||
right: calc(100vw - 30px - 225px) !important;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#chart-button.top-center.active {
|
||||
bottom: calc(100vh - 10px - 185px) !important;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#chart-button.left-center.active {
|
||||
right: calc(100vw - 30px - 225px) !important;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#chart-button.right-center.active {
|
||||
right: 10px !important;
|
||||
margin: 0px;
|
||||
}
|
||||
#chart-button.center.active {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.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;
|
||||
margin-bottom: -10px;
|
||||
}
|
||||
|
||||
.chart-col {
|
||||
|
|
@ -117,24 +103,34 @@
|
|||
color: #000 !important;
|
||||
}
|
||||
|
||||
#chart-container {
|
||||
width: 225px;
|
||||
height: 145px;
|
||||
#chart-container.bar.small {
|
||||
padding: 5px !important;
|
||||
height: 120px !important;
|
||||
}
|
||||
|
||||
#chart-container.line {
|
||||
width: 225px;
|
||||
#chart-container.bar.medium {
|
||||
padding: 5px !important;
|
||||
height: 200px !important;
|
||||
}
|
||||
|
||||
canvas.bar {
|
||||
height: 160px !important;
|
||||
min-width: 200px !important;
|
||||
#chart-container.bar.large {
|
||||
padding: 5px !important;
|
||||
height: 420px !important;
|
||||
}
|
||||
canvas.line {
|
||||
height: 145px !important;
|
||||
min-width: 200px !important;
|
||||
|
||||
#chart-container.line.small {
|
||||
padding: 5px !important;
|
||||
height: 107px !important;
|
||||
}
|
||||
|
||||
#chart-container.line.medium {
|
||||
padding: 5px !important;
|
||||
height: 220px !important;
|
||||
}
|
||||
|
||||
#chart-container.line.large {
|
||||
padding: 5px !important;
|
||||
height: 400px !important;
|
||||
}
|
||||
|
||||
i {
|
||||
|
|
@ -147,22 +143,21 @@ i {
|
|||
}
|
||||
|
||||
#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;
|
||||
transition: opacity 0.5s ease, transform 0.3s ease !important;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#settingsMenu.show {
|
||||
display: block !important; /* Show the menu */
|
||||
display: grid !important; /* Show the menu */
|
||||
opacity: 1 !important;
|
||||
transform: scale(1) translateY(0) !important; /* Animate to visible state */
|
||||
}
|
||||
|
|
@ -209,11 +204,20 @@ i {
|
|||
.left-col.text {
|
||||
margin-bottom: 5px !important;
|
||||
}
|
||||
.settings-row {
|
||||
display: inline-block;
|
||||
text-align: center !important;
|
||||
margin: 5px !important;
|
||||
}
|
||||
|
||||
.settings-col {
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
}
|
||||
.settings-hr {
|
||||
width: 0px;
|
||||
margin-top: 0px !important;
|
||||
margin-bottom: 0px !important;
|
||||
margin-bottom: 4px !important;
|
||||
transition: width 1s ease;
|
||||
}
|
||||
|
||||
|
|
@ -247,4 +251,14 @@ i {
|
|||
|
||||
#show_resource_monitor {
|
||||
cursor: pointer !important;
|
||||
z-index: 9991;
|
||||
margin: 5px;
|
||||
display: flex;
|
||||
font-size: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.resource-monitor-icon {
|
||||
margin: 5px;
|
||||
height: 14px;
|
||||
}
|
||||
|
|
|
|||
12529
web/assets/js/chart.js
12529
web/assets/js/chart.js
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,106 @@
|
|||
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);
|
||||
|
||||
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 styles = document.createElement("link");
|
||||
styles.href = "/file=web/assets/css/styles.css";
|
||||
styles.property = "stylesheet";
|
||||
styles.rel = "stylesheet";
|
||||
document.body.appendChild(styles);
|
||||
styles.onload = async function () {
|
||||
if (
|
||||
localStorage.getItem("lastClass") &&
|
||||
localStorage.getItem("lastInactiveClass")
|
||||
) {
|
||||
var lastClass = JSON.parse(localStorage.getItem("lastClass"));
|
||||
var lastInactiveClass = JSON.parse(
|
||||
localStorage.getItem("lastInactiveClass")
|
||||
);
|
||||
addCSS(lastInactiveClass.key, lastInactiveClass.values[0]);
|
||||
addCSS(lastClass.key, lastClass.values[0]);
|
||||
}
|
||||
|
||||
function getCSSRule(ruleName) {
|
||||
ruleName = ruleName.toLowerCase();
|
||||
var result = null;
|
||||
var find = Array.prototype.find;
|
||||
|
||||
Array.prototype.find.call(document.styleSheets, (styleSheet) => {
|
||||
try {
|
||||
if (styleSheet.cssRules) {
|
||||
result = find.call(styleSheet.cssRules, (cssRule) => {
|
||||
return (
|
||||
cssRule instanceof CSSStyleRule &&
|
||||
cssRule.selectorText.toLowerCase() == ruleName
|
||||
);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// Handle cross-origin or other access errors
|
||||
// console.info("Cannot access cssRules for stylesheet:", e);
|
||||
}
|
||||
return result != null;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function addCSS(selector, styles) {
|
||||
var rule = getCSSRule(selector);
|
||||
|
||||
for (var property in styles) {
|
||||
if (styles.hasOwnProperty(property)) {
|
||||
rule.style.setProperty(property, styles[property], "important");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadHtmlContent() {
|
||||
const response = await fetch(
|
||||
"/file=web/templates/perf-monitor/perf-monitor.html"
|
||||
);
|
||||
var resourceMonitorContent = document.getElementById(
|
||||
"perf-monitor-container"
|
||||
);
|
||||
resourceMonitorContent.innerHTML = await response.text();
|
||||
const chartButton = resourceMonitorContent.querySelector("#chart-button");
|
||||
const savedPosition =
|
||||
localStorage.getItem("perf-monitor-position") || "bottom-right";
|
||||
|
||||
if (chartButton) {
|
||||
// Set the savedPosition class on the #chart-button element
|
||||
chartButton.classList.add(savedPosition);
|
||||
}
|
||||
|
||||
var script = document.createElement("script");
|
||||
script.src = "/file=web/assets/js/perf-monitor.js";
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
await loadHtmlContent();
|
||||
};
|
||||
|
|
@ -1,10 +1,367 @@
|
|||
window.barChart = function () {
|
||||
checkForUpdates("active-chart", "bar");
|
||||
updateChartSize();
|
||||
};
|
||||
|
||||
window.lineChart = function () {
|
||||
checkForUpdates("active-chart", "line");
|
||||
updateChartSize();
|
||||
};
|
||||
|
||||
window.smallChart = function () {
|
||||
checkForUpdates("chart-size", "small");
|
||||
updateChartSize();
|
||||
};
|
||||
|
||||
window.mediumChart = function () {
|
||||
checkForUpdates("chart-size", "medium");
|
||||
updateChartSize();
|
||||
};
|
||||
|
||||
window.largeChart = function () {
|
||||
checkForUpdates("perf-monitor-position", "center");
|
||||
checkForUpdates("chart-size", "large");
|
||||
updateChartSize();
|
||||
};
|
||||
|
||||
function checkForUpdates(key, value) {
|
||||
var previous = localStorage.getItem(key);
|
||||
var updated = previous != value;
|
||||
localStorage.setItem("hasUpdates", updated);
|
||||
localStorage.setItem(key, value);
|
||||
}
|
||||
|
||||
const { hostname } = window.location; // Gets the host without port
|
||||
const baseUrl = `http://${hostname}:5000`; // Append the port 5000
|
||||
const apiUrl = `${baseUrl}/gpu_usage/`;
|
||||
const chartContainer = document.getElementById("chart-container");
|
||||
const chartWrapper = document.getElementById("chart-wrapper");
|
||||
|
||||
const perfMonitorContainer = $("#perf-monitor-container");
|
||||
const chartButton = $("#chart-button");
|
||||
const closeButton = $("#close-button");
|
||||
var styles = document.createElement("link");
|
||||
styles.href =
|
||||
"extensions/ComfyUI-Elegant-Resource-Monitor/assets/css/styles.css";
|
||||
styles.property = "stylesheet";
|
||||
styles.rel = "stylesheet";
|
||||
document.head.appendChild(styles);
|
||||
|
||||
// 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)",
|
||||
"rgb(159, 238, 209, 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)",
|
||||
"rgb(159, 238, 209)",
|
||||
];
|
||||
|
||||
// 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)}` + `${index == 5 ? "°" : "%"}`,
|
||||
});
|
||||
});
|
||||
|
||||
ctx.font = "8px Arial";
|
||||
ctx.fillStyle = "#FFFFFF";
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
|
||||
labelPositions.forEach((label) => {
|
||||
ctx.fillText(label.value, label.x, label.y);
|
||||
});
|
||||
|
||||
ctx.restore();
|
||||
},
|
||||
};
|
||||
|
||||
let currentChart = null; // Track the current chart instance
|
||||
const MAX_DATA_POINTS = 50; // Number of data points to keep
|
||||
function getSizes() {
|
||||
const size = localStorage.getItem("chart-size") ?? "small";
|
||||
const savedChart = localStorage.getItem("active-chart") ?? "bar";
|
||||
var sizes = {};
|
||||
if (savedChart == "bar") {
|
||||
sizes = {
|
||||
small: { height: "130", width: "180" },
|
||||
medium: { height: "220", width: "340" },
|
||||
large: { height: "440", width: "750" },
|
||||
};
|
||||
} else {
|
||||
sizes = {
|
||||
small: { height: "140", width: "200" },
|
||||
medium: { height: "255", width: "425" },
|
||||
large: { height: "450", width: "800" },
|
||||
};
|
||||
}
|
||||
return sizes;
|
||||
}
|
||||
|
||||
function updateButtonPosition() {
|
||||
const size = localStorage.getItem("chart-size") ?? "small";
|
||||
const sizes = getSizes();
|
||||
const sizeStyles = sizes[size];
|
||||
const buttonHeight = sizeStyles.height;
|
||||
const buttonWidth = sizeStyles.width;
|
||||
const viewportHeight = window.innerHeight;
|
||||
const viewportWidth = window.innerWidth;
|
||||
setButtonPosition(buttonHeight, buttonWidth, viewportHeight, viewportWidth);
|
||||
}
|
||||
|
||||
function updateChartSize() {
|
||||
const settingsMenu = document.getElementById("settingsMenu");
|
||||
settingsMenu.classList.remove("show"); // Hide the menu if visible
|
||||
const chartButton = document.getElementById("chart-button");
|
||||
const size = localStorage.getItem("chart-size") ?? "small";
|
||||
const savedChart = localStorage.getItem("active-chart") ?? "bar";
|
||||
const chartContainer = document.getElementById("chart-container");
|
||||
const sizes = getSizes();
|
||||
chartContainer.classList.remove("small", "medium", "large", "bar", "line");
|
||||
chartContainer.classList.add(size);
|
||||
chartContainer.classList.add(savedChart);
|
||||
|
||||
const sizeStyles = sizes[size];
|
||||
const buttonHeight = sizeStyles.height;
|
||||
const buttonWidth = sizeStyles.width;
|
||||
$(chartButton).each(function () {
|
||||
this.style.setProperty("height", `${buttonHeight}px`, "important");
|
||||
this.style.setProperty("width", `${buttonWidth}px`, "important");
|
||||
if (size === "large") {
|
||||
this.style.setProperty("background-color", ` #000000d6`, "important");
|
||||
} else {
|
||||
this.style.setProperty("background-color", ` #00000096`, "important");
|
||||
}
|
||||
});
|
||||
|
||||
updateButtonPosition();
|
||||
const hasUpdates = localStorage.getItem("hasUpdates") ?? "false";
|
||||
|
||||
if (hasUpdates === "true") {
|
||||
if (savedChart == "bar") {
|
||||
initializeBarChart();
|
||||
} else {
|
||||
initializeLineChart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setButtonPosition(
|
||||
buttonHeight,
|
||||
buttonWidth,
|
||||
viewportHeight,
|
||||
viewportWidth
|
||||
) {
|
||||
const positions = {
|
||||
"bottom-right": { bottom: "10px", right: "10px" },
|
||||
"bottom-left": {
|
||||
bottom: "10px",
|
||||
right: `${viewportWidth - buttonWidth - 10}px`,
|
||||
},
|
||||
"bottom-center": {
|
||||
bottom: "10px",
|
||||
right: `${(viewportWidth - buttonWidth) / 2}px`,
|
||||
},
|
||||
"top-right": {
|
||||
bottom: `${viewportHeight - buttonHeight - 10}px`,
|
||||
right: "10px",
|
||||
},
|
||||
"top-left": {
|
||||
bottom: `${viewportHeight - buttonHeight - 10}px`,
|
||||
right: `${viewportWidth - buttonWidth - 10}px`,
|
||||
},
|
||||
"top-center": {
|
||||
bottom: `${viewportHeight - buttonHeight - 10}px`,
|
||||
right: `${(viewportWidth - buttonWidth) / 2}px`,
|
||||
},
|
||||
"left-center": {
|
||||
bottom: `${(viewportHeight - buttonHeight) / 2}px`,
|
||||
right: `${viewportWidth - buttonWidth - 10}px`,
|
||||
},
|
||||
"right-center": {
|
||||
bottom: `${(viewportHeight - buttonHeight) / 2}px`,
|
||||
right: "10px",
|
||||
},
|
||||
center: {
|
||||
bottom: `${(viewportHeight - buttonHeight) / 2}px`,
|
||||
right: `${(viewportWidth - buttonWidth) / 2}px`,
|
||||
},
|
||||
};
|
||||
// Get the saved position
|
||||
const savedPosition =
|
||||
localStorage.getItem("perf-monitor-position") || "bottom-right";
|
||||
|
||||
const chartButton = document.getElementById("chart-button");
|
||||
const existingClasses = [
|
||||
"bottom-right",
|
||||
"bottom-left",
|
||||
"bottom-center",
|
||||
"top-right",
|
||||
"top-left",
|
||||
"top-center",
|
||||
"left-center",
|
||||
"right-center",
|
||||
"center",
|
||||
];
|
||||
existingClasses.forEach((cls) => {
|
||||
chartButton.classList.remove(cls);
|
||||
});
|
||||
chartButton.classList.add(savedPosition);
|
||||
|
||||
const active = `#chart-button.${savedPosition}.active`;
|
||||
const positionStyles = positions[savedPosition];
|
||||
|
||||
var lastClass = {
|
||||
key: active,
|
||||
values: [
|
||||
{
|
||||
bottom: positionStyles.bottom,
|
||||
right: positionStyles.right,
|
||||
},
|
||||
],
|
||||
};
|
||||
var lastClassString = JSON.stringify(lastClass);
|
||||
localStorage.setItem("lastClass", lastClassString);
|
||||
|
||||
updateCSS(active, positionStyles);
|
||||
|
||||
const inactive = `#chart-button.${savedPosition}`;
|
||||
const inactiveStyles = {
|
||||
buttonHeight: buttonHeight,
|
||||
buttonWidth: buttonWidth,
|
||||
viewportHeight: viewportHeight,
|
||||
viewportWidth: viewportWidth,
|
||||
};
|
||||
updateinActiveCSS(inactive, inactiveStyles, savedPosition);
|
||||
}
|
||||
function updateinActiveCSS(selector, styles, key) {
|
||||
var button = getCSSRule(selector);
|
||||
var style = {
|
||||
bottom: "auto",
|
||||
right: "auto",
|
||||
};
|
||||
|
||||
var buttonHeight = +styles.buttonHeight;
|
||||
var buttonWidth = +styles.buttonWidth;
|
||||
var viewportHeight = +styles.viewportHeight;
|
||||
var viewportWidth = +styles.viewportWidth;
|
||||
|
||||
switch (key) {
|
||||
case "bottom-right":
|
||||
style.bottom = "10px";
|
||||
style.right = `-${buttonWidth + 210}px`;
|
||||
break;
|
||||
case "bottom-left":
|
||||
style.bottom = "10px";
|
||||
style.right = `calc(100vw + ${buttonWidth + 210}px)`;
|
||||
break;
|
||||
|
||||
case "bottom-center":
|
||||
style.bottom = `-${buttonHeight + 210}px`;
|
||||
style.right = `${(viewportWidth - buttonWidth) / 2}px`;
|
||||
break;
|
||||
|
||||
case "top-right":
|
||||
style.bottom = `${viewportHeight - buttonHeight - 10}px`;
|
||||
style.right = `-${buttonWidth + 210}px`;
|
||||
break;
|
||||
|
||||
case "top-left":
|
||||
style.bottom = `${viewportHeight - buttonHeight - 10}px`;
|
||||
style.right = `calc(100vw + ${buttonWidth + 210}px)`;
|
||||
break;
|
||||
|
||||
case "top-center":
|
||||
style.bottom = `calc(100vh + 30px + ${buttonHeight + 210}px)`;
|
||||
style.right = `${(viewportWidth - buttonWidth) / 2}px`;
|
||||
break;
|
||||
|
||||
case "left-center":
|
||||
style.bottom = `${(viewportHeight - buttonHeight) / 2}px`;
|
||||
style.right = `calc(100vw + ${buttonWidth + 210}px)`;
|
||||
break;
|
||||
|
||||
case "right-center":
|
||||
style.bottom = `${(viewportHeight - buttonHeight) / 2}px`;
|
||||
style.right = `-${buttonWidth + 210}px`;
|
||||
break;
|
||||
|
||||
case "center":
|
||||
style.bottom = `calc(0vh - 30px - ${buttonHeight + 210}px)`;
|
||||
style.right = `${(viewportWidth - buttonWidth) / 2}px`;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
button.style.setProperty("bottom", style.bottom, "important");
|
||||
button.style.setProperty("right", style.right, "important");
|
||||
var lastClass = {
|
||||
key: selector,
|
||||
values: [
|
||||
{
|
||||
bottom: style.bottom,
|
||||
right: style.right,
|
||||
},
|
||||
],
|
||||
};
|
||||
var lastClassString = JSON.stringify(lastClass);
|
||||
localStorage.setItem("lastInactiveClass", lastClassString);
|
||||
}
|
||||
|
||||
function updateCSS(selector, styles) {
|
||||
var button = getCSSRule(selector);
|
||||
button.style.setProperty("bottom", styles.bottom, "important");
|
||||
button.style.setProperty("right", styles.right, "important");
|
||||
}
|
||||
|
||||
function getCSSRule(ruleName) {
|
||||
ruleName = ruleName.toLowerCase();
|
||||
var result = null;
|
||||
var find = Array.prototype.find;
|
||||
|
||||
Array.prototype.find.call(document.styleSheets, (styleSheet) => {
|
||||
try {
|
||||
if (styleSheet.cssRules) {
|
||||
result = find.call(styleSheet.cssRules, (cssRule) => {
|
||||
return (
|
||||
cssRule instanceof CSSStyleRule &&
|
||||
cssRule.selectorText.toLowerCase() == ruleName
|
||||
);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// Handle cross-origin or other access errors
|
||||
// console.info("Cannot access cssRules for stylesheet:", e);
|
||||
}
|
||||
return result != null;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
let intervalId; // Variable to store the interval ID
|
||||
// Function to start the interval
|
||||
|
|
@ -18,84 +375,13 @@ function stopInterval() {
|
|||
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 chartContainer = document.getElementById("chart-container");
|
||||
const existingCanvas = document.getElementById("usage-chart");
|
||||
const chartWrapper = document.getElementById("chart-wrapper");
|
||||
if (existingCanvas) {
|
||||
chartContainer.removeChild(existingCanvas);
|
||||
}
|
||||
|
|
@ -107,11 +393,12 @@ function initializeBarChart() {
|
|||
chartContainer.appendChild(newCanvas);
|
||||
|
||||
const ctx = newCanvas.getContext("2d");
|
||||
$(chartWrapper).hide();
|
||||
|
||||
currentChart = new Chart(ctx, {
|
||||
type: "bar",
|
||||
data: {
|
||||
labels: ["CPU", "RAM", "GPU", "VRAM", "HDD"],
|
||||
labels: ["CPU", "RAM", "GPU", "VRAM", "HDD", "TEMP"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Usage",
|
||||
|
|
@ -145,6 +432,7 @@ function initializeBarChart() {
|
|||
ticks: {
|
||||
color: "#ffffff",
|
||||
font: {
|
||||
size: 7,
|
||||
weight: 600,
|
||||
},
|
||||
align: "center",
|
||||
|
|
@ -167,6 +455,14 @@ function initializeBarChart() {
|
|||
font: {
|
||||
weight: 600,
|
||||
},
|
||||
// Specify the maximum number of ticks to show
|
||||
maxTicksLimit: 10,
|
||||
// Control the step size between ticks
|
||||
stepSize: 1,
|
||||
// Optional: Set font size and other style properties
|
||||
font: {
|
||||
size: 7,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -192,12 +488,15 @@ function initializeBarChart() {
|
|||
window.addEventListener("resize", () => {
|
||||
currentChart.resize();
|
||||
});
|
||||
$(chartWrapper).fadeIn(300);
|
||||
}
|
||||
|
||||
// Initialize the line chart
|
||||
function initializeLineChart() {
|
||||
localStorage.setItem("active-chart", "line");
|
||||
const existingCanvas = document.getElementById("usage-chart");
|
||||
const chartContainer = document.getElementById("chart-container");
|
||||
const chartWrapper = document.getElementById("chart-wrapper");
|
||||
if (existingCanvas) {
|
||||
chartContainer.removeChild(existingCanvas);
|
||||
}
|
||||
|
|
@ -207,6 +506,7 @@ function initializeLineChart() {
|
|||
newCanvas.id = "usage-chart";
|
||||
newCanvas.classList.add("line"); // Add the class directly to the canvas element
|
||||
chartContainer.appendChild(newCanvas);
|
||||
$(chartWrapper).hide();
|
||||
|
||||
const ctx = newCanvas.getContext("2d");
|
||||
|
||||
|
|
@ -226,8 +526,6 @@ function initializeLineChart() {
|
|||
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];
|
||||
|
|
@ -255,8 +553,6 @@ function initializeLineChart() {
|
|||
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];
|
||||
|
|
@ -284,8 +580,6 @@ function initializeLineChart() {
|
|||
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];
|
||||
|
|
@ -313,8 +607,6 @@ function initializeLineChart() {
|
|||
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];
|
||||
|
|
@ -342,7 +634,33 @@ function initializeLineChart() {
|
|||
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: "TEMP",
|
||||
data: [],
|
||||
borderColor: 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 borderColors[datasetIndex % borderColors.length];
|
||||
|
|
@ -389,6 +707,13 @@ function initializeLineChart() {
|
|||
beginAtZero: true,
|
||||
max: 100,
|
||||
ticks: {
|
||||
color: "#FFFFFF",
|
||||
crossAlign: "far",
|
||||
padding: 0,
|
||||
font: {
|
||||
weight: 600,
|
||||
size: 7,
|
||||
},
|
||||
callback: function (value, index, ticks) {
|
||||
return value + "%";
|
||||
},
|
||||
|
|
@ -410,29 +735,6 @@ function initializeLineChart() {
|
|||
},
|
||||
});
|
||||
|
||||
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
|
||||
|
|
@ -440,8 +742,33 @@ function initializeLineChart() {
|
|||
window.addEventListener("resize", () => {
|
||||
currentChart.resize();
|
||||
});
|
||||
$(chartWrapper).fadeIn(300);
|
||||
}
|
||||
|
||||
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`;
|
||||
legendText.style.fontSize = "10px";
|
||||
|
||||
legendItem.appendChild(legendText);
|
||||
legendContainer.appendChild(legendItem);
|
||||
});
|
||||
}
|
||||
async function updateUsage() {
|
||||
try {
|
||||
const response = await fetch(apiUrl);
|
||||
|
|
@ -453,25 +780,28 @@ async function updateUsage() {
|
|||
// Update data for bar chart
|
||||
currentChart.data.datasets[0].data = [
|
||||
data.cpu,
|
||||
data.memory,
|
||||
data.ram,
|
||||
data.gpu,
|
||||
data.vram,
|
||||
data.hdd,
|
||||
data.temp,
|
||||
];
|
||||
} 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[1].data.push(data.ram);
|
||||
currentChart.data.datasets[2].data.push(data.gpu);
|
||||
currentChart.data.datasets[3].data.push(data.vram);
|
||||
currentChart.data.datasets[4].data.push(data.hdd);
|
||||
currentChart.data.datasets[5].data.push(data.temp);
|
||||
|
||||
// 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
|
||||
}
|
||||
generateCustomLegend();
|
||||
}
|
||||
|
||||
// Update the chart with new data
|
||||
|
|
@ -500,6 +830,7 @@ document
|
|||
// 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
|
||||
showPerfMonitor();
|
||||
});
|
||||
|
||||
// Hide the settings menu when clicking outside
|
||||
|
|
@ -511,67 +842,89 @@ window.addEventListener("click", function (e) {
|
|||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll(".position-btn").forEach((button) => {
|
||||
document.querySelectorAll(".position-clickable").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);
|
||||
updateButtonPosition();
|
||||
|
||||
const settingsMenu = document.getElementById("settingsMenu");
|
||||
settingsMenu.classList.remove("show"); // Hide the menu if visible
|
||||
});
|
||||
});
|
||||
|
||||
// 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);
|
||||
updateButtonPosition();
|
||||
|
||||
setTimeout(() => {
|
||||
showPerfMonitor();
|
||||
if (savedChart == "bar") {
|
||||
initializeBarChart();
|
||||
} else {
|
||||
initializeLineChart();
|
||||
}
|
||||
}, 100);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function setMonitorPosition(position) {
|
||||
chartButton.addClass(position);
|
||||
const settingsMenu = document.getElementById("settingsMenu");
|
||||
settingsMenu.classList.remove("show"); // Hide the menu if visible
|
||||
return;
|
||||
}
|
||||
var shouldShowPerfMonitor = false;
|
||||
|
||||
window.showPerfMonitor = function () {
|
||||
// Set the initial position based on localStorage
|
||||
|
||||
updateChartSize();
|
||||
shouldShowPerfMonitor = !shouldShowPerfMonitor;
|
||||
localStorage.setItem("shouldShowPerfMonitor", shouldShowPerfMonitor);
|
||||
const chartButton = document.getElementById("chart-button");
|
||||
const show_resource_monitor = document.getElementById(
|
||||
"show_resource_monitor"
|
||||
);
|
||||
|
||||
if (shouldShowPerfMonitor === true) {
|
||||
const savedChart = localStorage.getItem("active-chart") ?? "bar";
|
||||
|
||||
setTimeout(() => {
|
||||
if (savedChart == "bar") {
|
||||
initializeBarChart();
|
||||
} else {
|
||||
initializeLineChart();
|
||||
}
|
||||
}, 100);
|
||||
|
||||
startInterval();
|
||||
$(show_resource_monitor).fadeOut();
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
stopInterval();
|
||||
}, 500);
|
||||
$(chartButton).each(function () {
|
||||
this.style.setProperty("height", `${0}px`, "important");
|
||||
});
|
||||
$(chartWrapper).hide();
|
||||
$(show_resource_monitor).fadeIn();
|
||||
}
|
||||
$(chartButton).toggleClass("active");
|
||||
};
|
||||
|
||||
document.getElementById("popupTrigger").addEventListener("click", function () {
|
||||
const menu = document.getElementById("settingsMenu");
|
||||
const menuRect = menu.getBoundingClientRect();
|
||||
const buttonRect = this.getBoundingClientRect();
|
||||
const viewportHeight = window.innerHeight;
|
||||
|
||||
if (menu.offsetTop < 0) {
|
||||
menu.style.position = "absolute"; // Ensure the menu is positioned absolutely
|
||||
menu.style.top = `29px`;
|
||||
}
|
||||
|
||||
// Default position: directly below the button
|
||||
let topPosition = buttonRect.bottom;
|
||||
|
||||
// Calculate if the menu will overflow the bottom of the viewport
|
||||
if (topPosition + menuRect.height > viewportHeight) {
|
||||
// Calculate how much the menu overflows the viewport
|
||||
const overflowAmount = topPosition + menuRect.height - viewportHeight;
|
||||
// Apply the calculated position
|
||||
menu.style.position = "absolute"; // Ensure the menu is positioned absolutely
|
||||
menu.style.top = `-${overflowAmount}px`;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,55 +2,8 @@
|
|||
<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();
|
||||
var script = document.createElement('script');
|
||||
script.src = '/file=web/assets/js/dependencies.js';
|
||||
document.body.appendChild(script);
|
||||
}"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,56 +1,75 @@
|
|||
<head>
|
||||
<link href="/file=/css/material-icon.css" rel="stylesheet" />
|
||||
</head>
|
||||
<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>
|
||||
<div class="settings-row"><div class="settings-col">Settings</div></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 class="settings-row">
|
||||
<div class="settings-row">
|
||||
<div class="settings-col">Layout:</div>
|
||||
<div class="settings-col">
|
||||
<a onclick="barChart()"> 1 </a> |
|
||||
<a onclick="lineChart()"> 2 </a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-row">
|
||||
<div class="settings-col">Size:</div>
|
||||
<div class="settings-col">
|
||||
<a onclick="smallChart()"> S </a> |
|
||||
<a onclick="mediumChart()"> M </a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-row">
|
||||
<div class="settings-col">Position</div>
|
||||
<div id="positionMenu" class="position-menu">
|
||||
<button class="position-btn position-clickable" id="top-left">
|
||||
<i class="material-icons">north_west</i>
|
||||
</button>
|
||||
<button class="position-btn position-clickable" id="top-center">
|
||||
<i class="material-icons">north</i>
|
||||
</button>
|
||||
<button class="position-btn position-clickable" id="top-right">
|
||||
<i class="material-icons">north_east</i>
|
||||
</button>
|
||||
<button class="position-btn position-clickable" id="left-center">
|
||||
<i class="material-icons">west</i>
|
||||
</button>
|
||||
<button class="position-btn" id="center" onclick="largeChart()">
|
||||
<i class="material-icons">radio_button_checked</i>
|
||||
</button>
|
||||
<button class="position-btn position-clickable" id="right-center">
|
||||
<i class="material-icons">east</i>
|
||||
</button>
|
||||
<button class="position-btn position-clickable" id="bottom-left">
|
||||
<i class="material-icons">south_west</i>
|
||||
</button>
|
||||
<button class="position-btn position-clickable" id="bottom-center">
|
||||
<i class="material-icons">south</i>
|
||||
</button>
|
||||
<button class="position-btn position-clickable" id="bottom-right">
|
||||
<i class="material-icons">south_east</i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-col">
|
||||
<i class="material-icons" id="close-button" onclick="showPerfMonitor()"
|
||||
>close</i
|
||||
>
|
||||
<i class="material-icons" id="close-button">close</i>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div id="chart-wrapper">
|
||||
<div id="chart-container">
|
||||
<canvas id="usage-chart" style="width: 100%; height: 100%"></canvas>
|
||||
</div>
|
||||
<div id="custom-legend"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/file=/js/perf-monitor.js"></script>
|
||||
|
|
|
|||
Loading…
Reference in New Issue