final touches on the perf monitor

This commit is contained in:
ChrisColeTech 2024-08-26 16:45:56 -04:00
parent 85429b04fc
commit 4c32ebe390
11 changed files with 13429 additions and 411 deletions

View File

@ -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

View File

@ -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")

View File

@ -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()

View File

@ -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;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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();
};

View File

@ -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`;
}
});

View File

@ -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);
}"
/>

View File

@ -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>

View File

@ -6,6 +6,7 @@ import time
import shared
import modules.config
import fooocus_version
from dependency_installer import *
import api.http_server
import modules.html
import modules.async_worker as worker