Merge eb934c9db1 into ae05379cc9
This commit is contained in:
commit
bcae8a68eb
|
|
@ -53,3 +53,4 @@ user_path_config-deprecated.txt
|
|||
/.coverage*
|
||||
/auth.json
|
||||
.DS_Store
|
||||
/.venv
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
import os
|
||||
import importlib
|
||||
from flask import Blueprint
|
||||
from flask_restx import Namespace
|
||||
|
||||
|
||||
def register_blueprints(app, api):
|
||||
"""Register all Blueprints to the Flask app automatically."""
|
||||
controllers_dir = os.path.dirname(__file__)
|
||||
for filename in os.listdir(controllers_dir):
|
||||
if filename.endswith('_controller.py') and filename != '__init__.py':
|
||||
module_name = filename[:-3] # Remove ".py"
|
||||
module = importlib.import_module(
|
||||
f'.{module_name}', package=__package__)
|
||||
for attribute_name in dir(module):
|
||||
attribute = getattr(module, attribute_name)
|
||||
if isinstance(attribute, Namespace):
|
||||
api.add_namespace(attribute)
|
||||
|
||||
if isinstance(attribute, Blueprint):
|
||||
app.register_blueprint(
|
||||
attribute)
|
||||
print(f"Registered blueprint: {attribute_name}")
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
from flask_restx import Api, Resource, fields, Namespace
|
||||
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')
|
||||
|
||||
# Define a namespace for gpu_usage
|
||||
gpu_usage_ns = Namespace('gpu_usage', description='gpu usage operations')
|
||||
|
||||
# Define the model for a gpu
|
||||
gpu_model = gpu_usage_ns.model('gpu_usage', {
|
||||
'id': fields.Integer(required=True, description='The unique identifier of the gpu'),
|
||||
'description': fields.String(required=True, description='Description of the gpu'),
|
||||
'status': fields.String(description='Status of the gpu')
|
||||
})
|
||||
|
||||
# Cache for system usage data
|
||||
cache = {
|
||||
'timestamp': 0,
|
||||
'data': {
|
||||
'cpu': 0,
|
||||
'ram': 0,
|
||||
'gpu': 0,
|
||||
'vram': 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()
|
||||
|
||||
current_time = time.time()
|
||||
|
||||
# Check if the cache is still valid
|
||||
if current_time - cache['timestamp'] < CACHE_DURATION:
|
||||
return _corsify_actual_response(jsonify(cache['data']))
|
||||
|
||||
try:
|
||||
self.get_cache(current_time)
|
||||
|
||||
return _corsify_actual_response(jsonify(cache['data']))
|
||||
except Exception as e:
|
||||
return _corsify_actual_response(jsonify({'error': str(e)}))
|
||||
|
||||
|
||||
def _build_cors_preflight_response():
|
||||
response = make_response()
|
||||
response.headers.add("Access-Control-Allow-Origin", "*")
|
||||
response.headers.add("Access-Control-Allow-Headers", "*")
|
||||
response.headers.add("Access-Control-Allow-Methods", "*")
|
||||
return response
|
||||
|
||||
|
||||
def _corsify_actual_response(response):
|
||||
response.headers.add("Access-Control-Allow-Origin", "*")
|
||||
return response
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
import pickle
|
||||
import os
|
||||
from flask import Blueprint, request, jsonify, make_response
|
||||
from flask_restx import Api, Resource, fields, Namespace
|
||||
|
||||
# Create a Blueprint for the jobs controller
|
||||
jobs_bp = Blueprint('jobs', __name__)
|
||||
jobs_api = Api(jobs_bp, version='1.0', title='Jobs API',
|
||||
description='API for managing jobs')
|
||||
|
||||
# Define a namespace for jobs
|
||||
jobs_ns = Namespace('jobs', description='Job operations')
|
||||
|
||||
# Define the model for a job
|
||||
job_model = jobs_ns.model('Job', {
|
||||
'id': fields.Integer(required=True, description='The unique identifier of the job'),
|
||||
'description': fields.String(required=True, description='Description of the job'),
|
||||
'status': fields.String(description='Status of the job')
|
||||
})
|
||||
|
||||
|
||||
# File to persist data
|
||||
DATA_FILE = 'jobs.pkl'
|
||||
|
||||
|
||||
def load_jobs():
|
||||
if os.path.exists(DATA_FILE):
|
||||
with open(DATA_FILE, 'rb') as file:
|
||||
return pickle.load(file)
|
||||
else:
|
||||
# Create an empty file if it doesn't exist
|
||||
with open(DATA_FILE, 'wb') as file:
|
||||
pickle.dump({}, file)
|
||||
return {}
|
||||
|
||||
|
||||
def save_jobs(jobs):
|
||||
with open(DATA_FILE, 'wb') as file:
|
||||
pickle.dump(jobs, file)
|
||||
|
||||
|
||||
# Load initial data
|
||||
jobs_store = load_jobs()
|
||||
|
||||
|
||||
@jobs_ns.route('/')
|
||||
class JobList(Resource):
|
||||
def get(self):
|
||||
"""List all jobs"""
|
||||
jobs_store = load_jobs()
|
||||
return _corsify_actual_response(jsonify(list(jobs_store.values())))
|
||||
|
||||
@jobs_ns.expect(job_model)
|
||||
def post(self):
|
||||
"""Create a new job"""
|
||||
if request.method == "OPTIONS": # CORS preflight
|
||||
return _build_cors_preflight_response()
|
||||
|
||||
job = request.json
|
||||
job_id = job['id']
|
||||
if job_id in jobs_store:
|
||||
return {'message': 'Job already exists'}, 400
|
||||
jobs_store[job_id] = job
|
||||
save_jobs(jobs_store) # Save to file
|
||||
return _corsify_actual_response(jsonify(job))
|
||||
|
||||
|
||||
@jobs_ns.route('/<int:job_id>')
|
||||
class JobItem(Resource):
|
||||
def get(self, job_id):
|
||||
"""Get a job by ID"""
|
||||
job = jobs_store.get(job_id)
|
||||
if job is None:
|
||||
return {'message': 'Job not found'}, 404
|
||||
return _corsify_actual_response(jsonify(job))
|
||||
|
||||
@jobs_ns.expect(job_model)
|
||||
def put(self, job_id):
|
||||
"""Update a job by ID"""
|
||||
if request.method == "OPTIONS": # CORS preflight
|
||||
return _build_cors_preflight_response()
|
||||
job = request.json
|
||||
if job_id not in jobs_store:
|
||||
return {'message': 'Job not found'}, 404
|
||||
jobs_store[job_id] = job
|
||||
save_jobs(jobs_store) # Save to file
|
||||
return _corsify_actual_response(jsonify(job))
|
||||
|
||||
def delete(self, job_id):
|
||||
"""Delete a job by ID"""
|
||||
if request.method == "OPTIONS": # CORS preflight
|
||||
return _build_cors_preflight_response()
|
||||
if job_id not in jobs_store:
|
||||
return {'message': 'Job not found'}, 404
|
||||
del jobs_store[job_id]
|
||||
save_jobs(jobs_store) # Save to file
|
||||
return _corsify_actual_response(jsonify({'message': 'Job deleted'}))
|
||||
|
||||
|
||||
def _build_cors_preflight_response():
|
||||
response = make_response()
|
||||
response.headers.add("Access-Control-Allow-Origin", "*")
|
||||
response.headers.add("Access-Control-Allow-Headers", "*")
|
||||
response.headers.add("Access-Control-Allow-Methods", "*")
|
||||
return response
|
||||
|
||||
|
||||
def _corsify_actual_response(response):
|
||||
response.headers.add("Access-Control-Allow-Origin", "*")
|
||||
return response
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
import json
|
||||
import os
|
||||
from flask import Blueprint, jsonify, request
|
||||
from flask_restx import Api, Resource, fields, Namespace
|
||||
|
||||
# Create a Blueprint for the settings controller
|
||||
settings_bp = Blueprint('settings', __name__)
|
||||
settings_api = Api(settings_bp, version='1.0', title='Settings API',
|
||||
description='API for managing settings')
|
||||
|
||||
# Define a namespace for settings
|
||||
settings_ns = Namespace('settings', description='Settings operations')
|
||||
|
||||
# Define the model for settings
|
||||
settings_model = settings_ns.model('Setting', {
|
||||
'key': fields.String(required=True, description='The key of the setting'),
|
||||
'value': fields.String(required=True, description='The value of the setting')
|
||||
})
|
||||
|
||||
# File to persist settings data
|
||||
SETTINGS_FILE = 'settings.json'
|
||||
|
||||
|
||||
def load_settings():
|
||||
if os.path.exists(SETTINGS_FILE):
|
||||
with open(SETTINGS_FILE, 'r') as file:
|
||||
return json.load(file)
|
||||
return {}
|
||||
|
||||
|
||||
def save_settings(settings):
|
||||
with open(SETTINGS_FILE, 'w') as file:
|
||||
json.dump(settings, file, indent=4)
|
||||
|
||||
|
||||
# Load initial data
|
||||
settings_store = load_settings()
|
||||
|
||||
|
||||
@settings_ns.route('/')
|
||||
class SettingsList(Resource):
|
||||
def get(self):
|
||||
"""List all settings"""
|
||||
return jsonify({'settings': list(settings_store.values())})
|
||||
|
||||
@settings_ns.expect(settings_model)
|
||||
def post(self):
|
||||
"""Create or update a setting"""
|
||||
setting = request.json
|
||||
key = setting['key']
|
||||
settings_store[key] = setting
|
||||
save_settings(settings_store) # Save to file
|
||||
return jsonify(setting)
|
||||
|
||||
|
||||
@settings_ns.route('/<string:key>')
|
||||
class SettingItem(Resource):
|
||||
def get(self, key):
|
||||
"""Get a setting by key"""
|
||||
setting = settings_store.get(key)
|
||||
if setting is None:
|
||||
return {'message': 'Setting not found'}, 404
|
||||
return jsonify(setting)
|
||||
|
||||
def delete(self, key):
|
||||
"""Delete a setting by key"""
|
||||
if key not in settings_store:
|
||||
return {'message': 'Setting not found'}, 404
|
||||
del settings_store[key]
|
||||
save_settings(settings_store) # Save to file
|
||||
return {'message': 'Setting deleted'}
|
||||
|
|
@ -0,0 +1,214 @@
|
|||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
import shutil
|
||||
import zipfile
|
||||
import importlib
|
||||
import urllib.request
|
||||
import re
|
||||
|
||||
|
||||
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'))
|
||||
return version_str, is_embedded
|
||||
|
||||
|
||||
def check_tkinter_installed():
|
||||
version_str, is_embedded = detect_python_version()
|
||||
print(f"Detected Python version: {version_str}")
|
||||
print(f"Is Embedded Python: {is_embedded}")
|
||||
try:
|
||||
import tkinter
|
||||
tkinter_installed = True
|
||||
except ImportError:
|
||||
tkinter_installed = False
|
||||
if not tkinter_installed or (is_embedded and not tkinter_installed):
|
||||
install_tkinter(version_str)
|
||||
|
||||
|
||||
def check_GPUtil_installed():
|
||||
|
||||
try:
|
||||
import GPUtil
|
||||
import psutil
|
||||
return True
|
||||
except ImportError:
|
||||
import_GPUtil()
|
||||
return False
|
||||
|
||||
|
||||
def check_flask_installed():
|
||||
|
||||
try:
|
||||
import flask
|
||||
import flask_restx
|
||||
import flask_cors
|
||||
|
||||
import flask_socketio
|
||||
return True
|
||||
except ImportError:
|
||||
import_flask()
|
||||
return False
|
||||
|
||||
|
||||
def download_and_unzip_tkinter():
|
||||
url = "https://github.com/ChrisColeTech/tkinter-standalone/releases/download/1.0.0/tkinter-standalone.zip"
|
||||
zip_path = "tkinter-standalone.zip"
|
||||
print(f"Downloading {url}...")
|
||||
urllib.request.urlretrieve(url, zip_path)
|
||||
|
||||
print("Unzipping tkinter-standalone.zip...")
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||
zip_ref.extractall("tkinter-standalone")
|
||||
|
||||
os.remove(zip_path)
|
||||
print("Download and extraction complete.")
|
||||
|
||||
|
||||
def copy_tkinter_files(version_str):
|
||||
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)
|
||||
|
||||
print("Tkinter files copied successfully.")
|
||||
shutil.rmtree("tkinter-standalone", ignore_errors=True)
|
||||
|
||||
|
||||
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 ModuleNotFoundError as e:
|
||||
print(f"Module not found: {e}")
|
||||
except ImportError as e:
|
||||
print("Failed to import Tkinter after installation.")
|
||||
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 psutil", desc="GPU Utility for NVIDIA GPUs")
|
||||
|
||||
try:
|
||||
GPUtil = importlib.import_module(
|
||||
"GPUtil")
|
||||
psutil = importlib.import_module(
|
||||
"psutil")
|
||||
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 flask_socketio", desc="Flask Rest API")
|
||||
|
||||
try:
|
||||
flask = importlib.import_module("flask")
|
||||
restx = importlib.import_module("flask-restx")
|
||||
flask_socketio = importlib.import_module("flask_socketio")
|
||||
return restx
|
||||
except ImportError:
|
||||
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()
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
import gradio as gr
|
||||
import os
|
||||
from .http_server import *
|
||||
|
||||
def addResourceMonitor():
|
||||
ceq = None
|
||||
with gr.Row():
|
||||
ceq = gr.HTML(load_page('templates/perf-monitor/index.html'))
|
||||
|
||||
return ceq
|
||||
|
||||
def load_page(filename):
|
||||
"""Load an HTML file as a string and return it"""
|
||||
file_path = os.path.join("web", filename)
|
||||
with open(file_path, 'r') as file:
|
||||
content = file.read()
|
||||
return content
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
from .dependency_installer import *
|
||||
from flask import Flask, send_from_directory, render_template
|
||||
from flask_socketio import SocketIO, emit
|
||||
from flask_restx import Api
|
||||
import logging
|
||||
import time
|
||||
from .controllers import register_blueprints
|
||||
import psutil
|
||||
import GPUtil
|
||||
import threading
|
||||
|
||||
# Initialize Flask app
|
||||
title = f"Resource Monitor"
|
||||
app = Flask(title, static_folder='web/assets', template_folder='web/templates')
|
||||
app.config['CORS_HEADERS'] = 'Content-Type'
|
||||
|
||||
# Initialize Flask-RESTx API
|
||||
api = Api(app, version='1.0', title=title, description='API for system resource monitoring')
|
||||
|
||||
# Register blueprints (API endpoints)
|
||||
register_blueprints(app, api)
|
||||
|
||||
# Initialize SocketIO with the Flask app
|
||||
socketio = SocketIO(app, cors_allowed_origins="*")
|
||||
|
||||
# Cache for system usage data
|
||||
cache = {
|
||||
'timestamp': 0,
|
||||
'data': {
|
||||
'cpu': 0,
|
||||
'ram': 0,
|
||||
'gpu': 0,
|
||||
'vram': 0,
|
||||
'hdd': 0,
|
||||
'temp': 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
|
||||
|
||||
def get_cache(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 if gpus else 0
|
||||
|
||||
# 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
|
||||
|
||||
@app.route('/')
|
||||
def home():
|
||||
return render_template('index.html')
|
||||
|
||||
@app.route('/<path:filename>')
|
||||
def serve_static(filename):
|
||||
return send_from_directory('web', filename)
|
||||
|
||||
@socketio.on('connect')
|
||||
def handle_connect():
|
||||
# Emit initial data
|
||||
current_time = time.time()
|
||||
get_cache(current_time)
|
||||
emit('data_update', cache['data'])
|
||||
|
||||
@socketio.on('disconnect')
|
||||
def handle_disconnect():
|
||||
pass
|
||||
|
||||
def background_thread():
|
||||
while True:
|
||||
current_time = time.time()
|
||||
get_cache(current_time)
|
||||
socketio.emit('data_update', cache['data'])
|
||||
time.sleep(.5)
|
||||
|
||||
def run_app():
|
||||
time.sleep(1) # Sleep for a short while to let the server start
|
||||
# Start the background thread for emitting data
|
||||
socketio.start_background_task(target=background_thread)
|
||||
# Run the Flask app with SocketIO
|
||||
socketio.run(app, port=5000)
|
||||
|
||||
# Start Flask app in a separate thread
|
||||
thread = threading.Thread(target=run_app)
|
||||
thread.start()
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,140 @@
|
|||
import gradio as gr
|
||||
import os
|
||||
import modules.config
|
||||
import modules.html
|
||||
import modules.meta_parser
|
||||
|
||||
from tkinter import Tk, filedialog
|
||||
|
||||
|
||||
def process_directories(directory_paths):
|
||||
if not directory_paths:
|
||||
return "No directories selected."
|
||||
|
||||
results = []
|
||||
for directory in directory_paths:
|
||||
# List files in the directory
|
||||
files = os.listdir(directory)
|
||||
results.append(f"Contents of {directory}:\n" + "\n".join(files))
|
||||
|
||||
return "\n\n".join(results)
|
||||
|
||||
|
||||
def update_visibility(x):
|
||||
# Add more updates for other components
|
||||
return [gr.update(visible=x), gr.update(visible=x)]
|
||||
|
||||
|
||||
def list_to_string(filenames):
|
||||
# Join the filenames list into a comma-separated string
|
||||
file_list = ', '.join(filenames)
|
||||
return file_list
|
||||
|
||||
|
||||
def on_browse(data_type):
|
||||
root = Tk()
|
||||
root.attributes("-topmost", True)
|
||||
root.withdraw()
|
||||
if data_type == "Files":
|
||||
filenames = filedialog.askopenfilenames()
|
||||
if len(filenames) > 0:
|
||||
root.destroy()
|
||||
file_list = list_to_string(filenames)
|
||||
return file_list
|
||||
else:
|
||||
filename = "Files not seleceted"
|
||||
root.destroy()
|
||||
return None
|
||||
|
||||
elif data_type == "Folder":
|
||||
filename = filedialog.askdirectory()
|
||||
if filename:
|
||||
if os.path.isdir(filename):
|
||||
root.destroy()
|
||||
return str(filename)
|
||||
else:
|
||||
root.destroy()
|
||||
return str(filename)
|
||||
else:
|
||||
filename = "Folder not seleceted"
|
||||
root.destroy()
|
||||
return None
|
||||
|
||||
|
||||
def on_file_change(files, data_type):
|
||||
if files and data_type == "Files":
|
||||
return gr.update(visible=True), gr.update(), gr.update(value=True)
|
||||
|
||||
# If no files are selected, hide file explorer and clear input_path
|
||||
if not files and data_type == "Files":
|
||||
return gr.update(visible=False), gr.update(value=""), gr.update(value=False)
|
||||
|
||||
if data_type == "Folder":
|
||||
return gr.update(visible=False), gr.update(), gr.update(value=True)
|
||||
|
||||
return gr.update(visible=False), gr.update(), gr.update(value=False)
|
||||
|
||||
|
||||
def on_input_change(input, file_explorer):
|
||||
if input:
|
||||
# Verify with normalised version of path
|
||||
input_path = os.path.normpath(os.path.realpath(input))
|
||||
|
||||
if os.path.isdir(os.path.realpath(input_path)):
|
||||
# Return an empty list if input_path is a directory
|
||||
return None, gr.update(visible=True), gr.update(value=True)
|
||||
else:
|
||||
# Return an empty list if input_path is empty
|
||||
return None, gr.update(visible=False), gr.update(value=False)
|
||||
|
||||
# Initialize a dictionary to track unique file names and their paths
|
||||
unique_file_paths = {}
|
||||
|
||||
# Process the input_path string
|
||||
if input_path:
|
||||
# Clean up the input path string and split it into a list of file paths
|
||||
file_paths_list = input_path.strip("()").replace("'", "").split(", ")
|
||||
# Extract file names and ensure uniqueness
|
||||
for path in file_paths_list:
|
||||
file_name = os.path.basename(path)
|
||||
unique_file_paths[file_name] = path
|
||||
|
||||
# Process file_explorer items if provided
|
||||
if file_explorer:
|
||||
# Extract 'orig_name' from each file_explorer object and ensure uniqueness
|
||||
for item in file_explorer:
|
||||
sanitized_path = item.orig_name
|
||||
file_name = os.path.basename(sanitized_path)
|
||||
# Store the path, replacing any existing path with the same file name
|
||||
unique_file_paths[file_name] = sanitized_path
|
||||
|
||||
# Convert the dictionary values back to a list of unique file paths
|
||||
if len(unique_file_paths.values()) > 0:
|
||||
return list(unique_file_paths.values()), gr.update(visible=False), gr.update(value=True)
|
||||
else:
|
||||
return None, gr.update(visible=False), gr.update(value=False)
|
||||
|
||||
|
||||
def on_click_clear():
|
||||
return None, None, gr.update(visible=False), gr.update(visible=False)
|
||||
|
||||
# Function to set prompts based on the selected type
|
||||
|
||||
|
||||
def update_prompts(selected_type):
|
||||
# Ensure selected_type is a valid key and exists in the dictionary
|
||||
if selected_type in modules.config.default_enhance_prompts:
|
||||
positive_prompt = modules.config.default_enhance_prompts[selected_type]['positive']
|
||||
negative_prompt = modules.config.default_enhance_prompts[selected_type]['negative']
|
||||
return positive_prompt, negative_prompt
|
||||
else:
|
||||
# Returning default or empty values
|
||||
return "Default positive prompt", "Default negative prompt"
|
||||
|
||||
|
||||
def on_selection_change(selected_type):
|
||||
# Get prompts based on selected_type
|
||||
positive_prompt, negative_prompt = update_prompts(selected_type[0])
|
||||
|
||||
# Return the prompts
|
||||
return positive_prompt, negative_prompt
|
||||
|
|
@ -7,7 +7,6 @@ import args_manager
|
|||
import tempfile
|
||||
import modules.flags
|
||||
import modules.sdxl_styles
|
||||
|
||||
from modules.model_loader import load_file_from_url
|
||||
from modules.extra_utils import makedirs_with_log, get_files_from_folder, try_eval_env_var
|
||||
from modules.flags import OutputFormat, Performance, MetadataScheme
|
||||
|
|
@ -21,9 +20,11 @@ def get_config_path(key, default_value):
|
|||
else:
|
||||
return os.path.abspath(default_value)
|
||||
|
||||
|
||||
wildcards_max_bfs_depth = 64
|
||||
config_path = get_config_path('config_path', "./config.txt")
|
||||
config_example_path = get_config_path('config_example_path', "config_modification_tutorial.txt")
|
||||
config_example_path = get_config_path(
|
||||
'config_example_path', "config_modification_tutorial.txt")
|
||||
config_dict = {}
|
||||
always_save_keys = []
|
||||
visited_keys = []
|
||||
|
|
@ -41,9 +42,11 @@ try:
|
|||
config_dict.update(json.load(json_file))
|
||||
always_save_keys = list(config_dict.keys())
|
||||
except Exception as e:
|
||||
print(f'Failed to load config file "{config_path}" . The reason is: {str(e)}')
|
||||
print(
|
||||
f'Failed to load config file "{config_path}" . The reason is: {str(e)}')
|
||||
print('Please make sure that:')
|
||||
print(f'1. The file "{config_path}" is a valid text file, and you have access to read it.')
|
||||
print(
|
||||
f'1. The file "{config_path}" is a valid text file, and you have access to read it.')
|
||||
print('2. Use "\\\\" instead of "\\" when describing paths.')
|
||||
print('3. There is no "," before the last "}".')
|
||||
print('4. All key/value formats are correct.')
|
||||
|
|
@ -56,7 +59,8 @@ def try_load_deprecated_user_path_config():
|
|||
return
|
||||
|
||||
try:
|
||||
deprecated_config_dict = json.load(open('user_path_config.txt', "r", encoding="utf-8"))
|
||||
deprecated_config_dict = json.load(
|
||||
open('user_path_config.txt', "r", encoding="utf-8"))
|
||||
|
||||
def replace_config(old_key, new_key):
|
||||
if old_key in deprecated_config_dict:
|
||||
|
|
@ -75,7 +79,8 @@ def try_load_deprecated_user_path_config():
|
|||
replace_config('temp_outputs_path', 'path_outputs')
|
||||
|
||||
if deprecated_config_dict.get("default_model", None) == 'juggernautXL_version6Rundiffusion.safetensors':
|
||||
os.replace('user_path_config.txt', 'user_path_config-deprecated.txt')
|
||||
os.replace('user_path_config.txt',
|
||||
'user_path_config-deprecated.txt')
|
||||
print('Config updated successfully in silence. '
|
||||
'A backup of previous config is written to "user_path_config-deprecated.txt".')
|
||||
return
|
||||
|
|
@ -86,7 +91,8 @@ def try_load_deprecated_user_path_config():
|
|||
print('Loading using deprecated old models and deprecated old configs.')
|
||||
return
|
||||
else:
|
||||
os.replace('user_path_config.txt', 'user_path_config-deprecated.txt')
|
||||
os.replace('user_path_config.txt',
|
||||
'user_path_config-deprecated.txt')
|
||||
print('Config updated successfully by user. '
|
||||
'A backup of previous config is written to "user_path_config-deprecated.txt".')
|
||||
return
|
||||
|
|
@ -98,6 +104,7 @@ def try_load_deprecated_user_path_config():
|
|||
|
||||
try_load_deprecated_user_path_config()
|
||||
|
||||
|
||||
def get_presets():
|
||||
preset_folder = 'presets'
|
||||
presets = ['initial']
|
||||
|
|
@ -107,10 +114,12 @@ def get_presets():
|
|||
|
||||
return presets + [f[:f.index(".json")] for f in os.listdir(preset_folder) if f.endswith('.json')]
|
||||
|
||||
|
||||
def update_presets():
|
||||
global available_presets
|
||||
available_presets = get_presets()
|
||||
|
||||
|
||||
def try_get_preset_content(preset):
|
||||
if isinstance(preset, str):
|
||||
preset_path = os.path.abspath(f'./presets/{preset}.json')
|
||||
|
|
@ -127,18 +136,22 @@ def try_get_preset_content(preset):
|
|||
print(e)
|
||||
return {}
|
||||
|
||||
|
||||
available_presets = get_presets()
|
||||
preset = args_manager.args.preset
|
||||
config_dict.update(try_get_preset_content(preset))
|
||||
|
||||
|
||||
def get_path_output() -> str:
|
||||
"""
|
||||
Checking output path argument and overriding default path.
|
||||
"""
|
||||
global config_dict
|
||||
path_output = get_dir_or_set_default('path_outputs', '../outputs/', make_directory=True)
|
||||
path_output = get_dir_or_set_default(
|
||||
'path_outputs', '../outputs/', make_directory=True)
|
||||
if args_manager.args.output_path:
|
||||
print(f'Overriding config value path_outputs with {args_manager.args.output_path}')
|
||||
print(
|
||||
f'Overriding config value path_outputs with {args_manager.args.output_path}')
|
||||
config_dict['path_outputs'] = path_output = args_manager.args.output_path
|
||||
return path_output
|
||||
|
||||
|
|
@ -172,15 +185,18 @@ def get_dir_or_set_default(key, default_value, as_array=False, make_directory=Fa
|
|||
return v
|
||||
|
||||
if v is not None:
|
||||
print(f'Failed to load config key: {json.dumps({key:v})} is invalid or does not exist; will use {json.dumps({key:default_value})} instead.')
|
||||
print(
|
||||
f'Failed to load config key: {json.dumps({key:v})} is invalid or does not exist; will use {json.dumps({key:default_value})} instead.')
|
||||
if isinstance(default_value, list):
|
||||
dp = []
|
||||
for path in default_value:
|
||||
abs_path = os.path.abspath(os.path.join(os.path.dirname(__file__), path))
|
||||
abs_path = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), path))
|
||||
dp.append(abs_path)
|
||||
os.makedirs(abs_path, exist_ok=True)
|
||||
else:
|
||||
dp = os.path.abspath(os.path.join(os.path.dirname(__file__), default_value))
|
||||
dp = os.path.abspath(os.path.join(
|
||||
os.path.dirname(__file__), default_value))
|
||||
os.makedirs(dp, exist_ok=True)
|
||||
if as_array:
|
||||
dp = [dp]
|
||||
|
|
@ -188,18 +204,26 @@ def get_dir_or_set_default(key, default_value, as_array=False, make_directory=Fa
|
|||
return dp
|
||||
|
||||
|
||||
paths_checkpoints = get_dir_or_set_default('path_checkpoints', ['../models/checkpoints/'], True)
|
||||
paths_checkpoints = get_dir_or_set_default(
|
||||
'path_checkpoints', ['../models/checkpoints/'], True)
|
||||
paths_loras = get_dir_or_set_default('path_loras', ['../models/loras/'], True)
|
||||
path_embeddings = get_dir_or_set_default('path_embeddings', '../models/embeddings/')
|
||||
path_vae_approx = get_dir_or_set_default('path_vae_approx', '../models/vae_approx/')
|
||||
path_embeddings = get_dir_or_set_default(
|
||||
'path_embeddings', '../models/embeddings/')
|
||||
path_vae_approx = get_dir_or_set_default(
|
||||
'path_vae_approx', '../models/vae_approx/')
|
||||
path_vae = get_dir_or_set_default('path_vae', '../models/vae/')
|
||||
path_upscale_models = get_dir_or_set_default('path_upscale_models', '../models/upscale_models/')
|
||||
path_upscale_models = get_dir_or_set_default(
|
||||
'path_upscale_models', '../models/upscale_models/')
|
||||
path_inpaint = get_dir_or_set_default('path_inpaint', '../models/inpaint/')
|
||||
path_controlnet = get_dir_or_set_default('path_controlnet', '../models/controlnet/')
|
||||
path_clip_vision = get_dir_or_set_default('path_clip_vision', '../models/clip_vision/')
|
||||
path_fooocus_expansion = get_dir_or_set_default('path_fooocus_expansion', '../models/prompt_expansion/fooocus_expansion')
|
||||
path_controlnet = get_dir_or_set_default(
|
||||
'path_controlnet', '../models/controlnet/')
|
||||
path_clip_vision = get_dir_or_set_default(
|
||||
'path_clip_vision', '../models/clip_vision/')
|
||||
path_fooocus_expansion = get_dir_or_set_default(
|
||||
'path_fooocus_expansion', '../models/prompt_expansion/fooocus_expansion')
|
||||
path_wildcards = get_dir_or_set_default('path_wildcards', '../wildcards/')
|
||||
path_safety_checker = get_dir_or_set_default('path_safety_checker', '../models/safety_checker/')
|
||||
path_safety_checker = get_dir_or_set_default(
|
||||
'path_safety_checker', '../models/safety_checker/')
|
||||
path_sam = get_dir_or_set_default('path_sam', '../models/sam/')
|
||||
path_outputs = get_path_output()
|
||||
|
||||
|
|
@ -209,7 +233,7 @@ def get_config_item_or_set_default(key, default_value, validator, disable_empty_
|
|||
|
||||
if key not in visited_keys:
|
||||
visited_keys.append(key)
|
||||
|
||||
|
||||
v = os.getenv(key)
|
||||
if v is not None:
|
||||
v = try_eval_env_var(v, expected_type)
|
||||
|
|
@ -228,7 +252,8 @@ def get_config_item_or_set_default(key, default_value, validator, disable_empty_
|
|||
return v
|
||||
else:
|
||||
if v is not None:
|
||||
print(f'Failed to load config key: {json.dumps({key:v})} is invalid; will use {json.dumps({key:default_value})} instead.')
|
||||
print(
|
||||
f'Failed to load config key: {json.dumps({key:v})} is invalid; will use {json.dumps({key:default_value})} instead.')
|
||||
config_dict[key] = default_value
|
||||
return default_value
|
||||
|
||||
|
|
@ -274,7 +299,8 @@ default_base_model_name = default_model = get_config_item_or_set_default(
|
|||
previous_default_models = get_config_item_or_set_default(
|
||||
key='previous_default_models',
|
||||
default_value=[],
|
||||
validator=lambda x: isinstance(x, list) and all(isinstance(k, str) for k in x),
|
||||
validator=lambda x: isinstance(x, list) and all(
|
||||
isinstance(k, str) for k in x),
|
||||
expected_type=list
|
||||
)
|
||||
default_refiner_model_name = default_refiner = get_config_item_or_set_default(
|
||||
|
|
@ -331,15 +357,18 @@ default_loras = get_config_item_or_set_default(
|
|||
]
|
||||
],
|
||||
validator=lambda x: isinstance(x, list) and all(
|
||||
len(y) == 3 and isinstance(y[0], bool) and isinstance(y[1], str) and isinstance(y[2], numbers.Number)
|
||||
len(y) == 3 and isinstance(y[0], bool) and isinstance(
|
||||
y[1], str) and isinstance(y[2], numbers.Number)
|
||||
or len(y) == 2 and isinstance(y[0], str) and isinstance(y[1], numbers.Number)
|
||||
for y in x),
|
||||
expected_type=list
|
||||
)
|
||||
default_loras = [(y[0], y[1], y[2]) if len(y) == 3 else (True, y[0], y[1]) for y in default_loras]
|
||||
default_loras = [(y[0], y[1], y[2]) if len(y) == 3 else (
|
||||
True, y[0], y[1]) for y in default_loras]
|
||||
default_max_lora_number = get_config_item_or_set_default(
|
||||
key='default_max_lora_number',
|
||||
default_value=len(default_loras) if isinstance(default_loras, list) and len(default_loras) > 0 else 5,
|
||||
default_value=len(default_loras) if isinstance(
|
||||
default_loras, list) and len(default_loras) > 0 else 5,
|
||||
validator=lambda x: isinstance(x, int) and x >= 1,
|
||||
expected_type=int
|
||||
)
|
||||
|
|
@ -380,7 +409,8 @@ default_styles = get_config_item_or_set_default(
|
|||
"Fooocus Enhance",
|
||||
"Fooocus Sharp"
|
||||
],
|
||||
validator=lambda x: isinstance(x, list) and all(y in modules.sdxl_styles.legal_style_names for y in x),
|
||||
validator=lambda x: isinstance(x, list) and all(
|
||||
y in modules.sdxl_styles.legal_style_names for y in x),
|
||||
expected_type=list
|
||||
)
|
||||
default_prompt_negative = get_config_item_or_set_default(
|
||||
|
|
@ -448,37 +478,43 @@ default_output_format = get_config_item_or_set_default(
|
|||
default_image_number = get_config_item_or_set_default(
|
||||
key='default_image_number',
|
||||
default_value=2,
|
||||
validator=lambda x: isinstance(x, int) and 1 <= x <= default_max_image_number,
|
||||
validator=lambda x: isinstance(
|
||||
x, int) and 1 <= x <= default_max_image_number,
|
||||
expected_type=int
|
||||
)
|
||||
checkpoint_downloads = get_config_item_or_set_default(
|
||||
key='checkpoint_downloads',
|
||||
default_value={},
|
||||
validator=lambda x: isinstance(x, dict) and all(isinstance(k, str) and isinstance(v, str) for k, v in x.items()),
|
||||
validator=lambda x: isinstance(x, dict) and all(
|
||||
isinstance(k, str) and isinstance(v, str) for k, v in x.items()),
|
||||
expected_type=dict
|
||||
)
|
||||
lora_downloads = get_config_item_or_set_default(
|
||||
key='lora_downloads',
|
||||
default_value={},
|
||||
validator=lambda x: isinstance(x, dict) and all(isinstance(k, str) and isinstance(v, str) for k, v in x.items()),
|
||||
validator=lambda x: isinstance(x, dict) and all(
|
||||
isinstance(k, str) and isinstance(v, str) for k, v in x.items()),
|
||||
expected_type=dict
|
||||
)
|
||||
embeddings_downloads = get_config_item_or_set_default(
|
||||
key='embeddings_downloads',
|
||||
default_value={},
|
||||
validator=lambda x: isinstance(x, dict) and all(isinstance(k, str) and isinstance(v, str) for k, v in x.items()),
|
||||
validator=lambda x: isinstance(x, dict) and all(
|
||||
isinstance(k, str) and isinstance(v, str) for k, v in x.items()),
|
||||
expected_type=dict
|
||||
)
|
||||
vae_downloads = get_config_item_or_set_default(
|
||||
key='vae_downloads',
|
||||
default_value={},
|
||||
validator=lambda x: isinstance(x, dict) and all(isinstance(k, str) and isinstance(v, str) for k, v in x.items()),
|
||||
validator=lambda x: isinstance(x, dict) and all(
|
||||
isinstance(k, str) and isinstance(v, str) for k, v in x.items()),
|
||||
expected_type=dict
|
||||
)
|
||||
available_aspect_ratios = get_config_item_or_set_default(
|
||||
key='available_aspect_ratios',
|
||||
default_value=modules.flags.sdxl_aspect_ratios,
|
||||
validator=lambda x: isinstance(x, list) and all('*' in v for v in x) and len(x) > 1,
|
||||
validator=lambda x: isinstance(x, list) and all(
|
||||
'*' in v for v in x) and len(x) > 1,
|
||||
expected_type=list
|
||||
)
|
||||
default_aspect_ratio = get_config_item_or_set_default(
|
||||
|
|
@ -521,7 +557,8 @@ for image_count in range(default_controlnet_image_count):
|
|||
default_ip_images[image_count] = get_config_item_or_set_default(
|
||||
key=f'default_ip_image_{image_count}',
|
||||
default_value='None',
|
||||
validator=lambda x: x == 'None' or isinstance(x, str) and os.path.exists(x),
|
||||
validator=lambda x: x == 'None' or isinstance(
|
||||
x, str) and os.path.exists(x),
|
||||
expected_type=str
|
||||
)
|
||||
|
||||
|
|
@ -571,7 +608,8 @@ default_cfg_tsnr = get_config_item_or_set_default(
|
|||
default_clip_skip = get_config_item_or_set_default(
|
||||
key='default_clip_skip',
|
||||
default_value=2,
|
||||
validator=lambda x: isinstance(x, int) and 1 <= x <= modules.flags.clip_skip_max,
|
||||
validator=lambda x: isinstance(
|
||||
x, int) and 1 <= x <= modules.flags.clip_skip_max,
|
||||
expected_type=int
|
||||
)
|
||||
default_overwrite_step = get_config_item_or_set_default(
|
||||
|
|
@ -596,7 +634,8 @@ example_inpaint_prompts = get_config_item_or_set_default(
|
|||
default_value=[
|
||||
'highly detailed face', 'detailed girl face', 'detailed man face', 'detailed hand', 'beautiful eyes'
|
||||
],
|
||||
validator=lambda x: isinstance(x, list) and all(isinstance(v, str) for v in x),
|
||||
validator=lambda x: isinstance(x, list) and all(
|
||||
isinstance(v, str) for v in x),
|
||||
expected_type=list
|
||||
)
|
||||
example_enhance_detection_prompts = get_config_item_or_set_default(
|
||||
|
|
@ -604,9 +643,38 @@ example_enhance_detection_prompts = get_config_item_or_set_default(
|
|||
default_value=[
|
||||
'face', 'eye', 'mouth', 'hair', 'hand', 'body'
|
||||
],
|
||||
validator=lambda x: isinstance(x, list) and all(isinstance(v, str) for v in x),
|
||||
validator=lambda x: isinstance(x, list) and all(
|
||||
isinstance(v, str) for v in x),
|
||||
expected_type=list
|
||||
)
|
||||
|
||||
default_enhance_prompts = {
|
||||
'face': {
|
||||
'positive': "Enhance the face to ensure clear and detailed features. The face should have a well-defined structure with smooth skin, natural contours, and a balanced complexion. Make sure the expression is natural and engaging.",
|
||||
'negative': "Avoid any blurriness or distortions in the face. Do not include uneven skin tones, unnatural facial expressions, or any missing facial features. Ensure there are no artifacts or unnatural smoothing that might distort the face's natural appearance."
|
||||
},
|
||||
'eye': {
|
||||
'positive': "Enhance the eyes to be clear, sharp, and vividly detailed. The eyes should have natural reflections and a realistic appearance. Ensure the irises and pupils are distinct, and there are no shadows or blurs affecting the eyes.",
|
||||
'negative': "Exclude any blurring, distortions, or unnatural reflections in the eyes. Avoid asymmetrical or misaligned eyes, and ensure there are no unnatural colors or artifacts that could detract from a realistic appearance."
|
||||
},
|
||||
'mouth': {
|
||||
'positive': "Enhance the mouth to appear natural and symmetrical. The lips should be smooth and well-defined, with no abnormalities. Ensure the mouth reflects a realistic expression and that teeth are visible only if naturally exposed.",
|
||||
'negative': "Avoid any distortions, asymmetry, or unnatural shapes in the mouth. Do not include missing or extra teeth, and ensure there are no anomalies or artifacts affecting the mouth's appearance."
|
||||
},
|
||||
'hair': {
|
||||
'positive': "Enhance the hair to look full, natural, and well-styled. The texture should be realistic, with clear individual strands or locks and natural shine. Ensure the color and style match the intended look without any unnatural effects.",
|
||||
'negative': "Exclude any unnatural textures, blurs, or artifacts in the hair. Avoid colors that look artificial or inconsistent, and ensure there are no missing or irregular sections of hair that could disrupt the natural appearance."
|
||||
},
|
||||
'hand': {
|
||||
'positive': "Enhance the hands to ensure all fingers are clearly visible and well-defined. The hands should have realistic textures and proportions, with no missing or distorted fingers. The overall appearance should be natural and proportional.",
|
||||
'negative': "Avoid any distortions or missing fingers in the hands. Do not include unnatural shapes or proportions, and ensure there are no anomalies or artifacts that affect the realistic appearance of the hands."
|
||||
},
|
||||
'body': {
|
||||
'positive': "Enhance the body to ensure a complete and natural appearance with all limbs properly defined. The body should reflect realistic proportions and posture, with no missing or distorted body parts. Ensure the overall shape and anatomy are natural and well-balanced.",
|
||||
'negative': "Exclude any missing limbs, distortions, or unrealistic body shapes. Avoid anomalies in body posture or proportions, and ensure there are no artifacts or inconsistencies that could affect the natural appearance of the body."
|
||||
}
|
||||
}
|
||||
|
||||
default_enhance_tabs = get_config_item_or_set_default(
|
||||
key='default_enhance_tabs',
|
||||
default_value=3,
|
||||
|
|
@ -658,7 +726,8 @@ default_save_metadata_to_images = get_config_item_or_set_default(
|
|||
default_metadata_scheme = get_config_item_or_set_default(
|
||||
key='default_metadata_scheme',
|
||||
default_value=MetadataScheme.FOOOCUS.value,
|
||||
validator=lambda x: x in [y[1] for y in modules.flags.metadata_scheme if y[1] == x],
|
||||
validator=lambda x: x in [y[1]
|
||||
for y in modules.flags.metadata_scheme if y[1] == x],
|
||||
expected_type=str
|
||||
)
|
||||
metadata_created_by = get_config_item_or_set_default(
|
||||
|
|
@ -669,7 +738,8 @@ metadata_created_by = get_config_item_or_set_default(
|
|||
)
|
||||
|
||||
example_inpaint_prompts = [[x] for x in example_inpaint_prompts]
|
||||
example_enhance_detection_prompts = [[x] for x in example_enhance_detection_prompts]
|
||||
example_enhance_detection_prompts = [[x]
|
||||
for x in example_enhance_detection_prompts]
|
||||
|
||||
default_invert_mask_checkbox = get_config_item_or_set_default(
|
||||
key='default_invert_mask_checkbox',
|
||||
|
|
@ -719,7 +789,9 @@ default_describe_content_type = get_config_item_or_set_default(
|
|||
expected_type=list
|
||||
)
|
||||
|
||||
config_dict["default_loras"] = default_loras = default_loras[:default_max_lora_number] + [[True, 'None', 1.0] for _ in range(default_max_lora_number - len(default_loras))]
|
||||
config_dict["default_loras"] = default_loras = default_loras[:default_max_lora_number] + \
|
||||
[[True, 'None', 1.0]
|
||||
for _ in range(default_max_lora_number - len(default_loras))]
|
||||
|
||||
# mapping config to meta parameter
|
||||
possible_preset_keys = {
|
||||
|
|
@ -759,7 +831,8 @@ REWRITE_PRESET = False
|
|||
if REWRITE_PRESET and isinstance(args_manager.args.preset, str):
|
||||
save_path = 'presets/' + args_manager.args.preset + '.json'
|
||||
with open(save_path, "w", encoding="utf-8") as json_file:
|
||||
json.dump({k: config_dict[k] for k in possible_preset_keys}, json_file, indent=4)
|
||||
json.dump({k: config_dict[k]
|
||||
for k in possible_preset_keys}, json_file, indent=4)
|
||||
print(f'Preset saved to {save_path}. Exiting ...')
|
||||
exit(0)
|
||||
|
||||
|
|
@ -772,13 +845,15 @@ def add_ratio(x):
|
|||
|
||||
|
||||
default_aspect_ratio = add_ratio(default_aspect_ratio)
|
||||
available_aspect_ratios_labels = [add_ratio(x) for x in available_aspect_ratios]
|
||||
available_aspect_ratios_labels = [
|
||||
add_ratio(x) for x in available_aspect_ratios]
|
||||
|
||||
|
||||
# Only write config in the first launch.
|
||||
if not os.path.exists(config_path):
|
||||
with open(config_path, "w", encoding="utf-8") as json_file:
|
||||
json.dump({k: config_dict[k] for k in always_save_keys}, json_file, indent=4)
|
||||
json.dump({k: config_dict[k]
|
||||
for k in always_save_keys}, json_file, indent=4)
|
||||
|
||||
|
||||
# Always write tutorials.
|
||||
|
|
@ -799,7 +874,8 @@ wildcard_filenames = []
|
|||
|
||||
def get_model_filenames(folder_paths, extensions=None, name_filter=None):
|
||||
if extensions is None:
|
||||
extensions = ['.pth', '.ckpt', '.bin', '.safetensors', '.fooocus.patch']
|
||||
extensions = ['.pth', '.ckpt', '.bin',
|
||||
'.safetensors', '.fooocus.patch']
|
||||
files = []
|
||||
|
||||
if not isinstance(folder_paths, list):
|
||||
|
|
@ -913,14 +989,16 @@ def downloading_ip_adapters(v):
|
|||
model_dir=path_clip_vision,
|
||||
file_name='clip_vision_vit_h.safetensors'
|
||||
)
|
||||
results += [os.path.join(path_clip_vision, 'clip_vision_vit_h.safetensors')]
|
||||
results += [os.path.join(path_clip_vision,
|
||||
'clip_vision_vit_h.safetensors')]
|
||||
|
||||
load_file_from_url(
|
||||
url='https://huggingface.co/lllyasviel/misc/resolve/main/fooocus_ip_negative.safetensors',
|
||||
model_dir=path_controlnet,
|
||||
file_name='fooocus_ip_negative.safetensors'
|
||||
)
|
||||
results += [os.path.join(path_controlnet, 'fooocus_ip_negative.safetensors')]
|
||||
results += [os.path.join(path_controlnet,
|
||||
'fooocus_ip_negative.safetensors')]
|
||||
|
||||
if v == 'ip':
|
||||
load_file_from_url(
|
||||
|
|
@ -928,7 +1006,8 @@ def downloading_ip_adapters(v):
|
|||
model_dir=path_controlnet,
|
||||
file_name='ip-adapter-plus_sdxl_vit-h.bin'
|
||||
)
|
||||
results += [os.path.join(path_controlnet, 'ip-adapter-plus_sdxl_vit-h.bin')]
|
||||
results += [os.path.join(path_controlnet,
|
||||
'ip-adapter-plus_sdxl_vit-h.bin')]
|
||||
|
||||
if v == 'face':
|
||||
load_file_from_url(
|
||||
|
|
@ -936,7 +1015,8 @@ def downloading_ip_adapters(v):
|
|||
model_dir=path_controlnet,
|
||||
file_name='ip-adapter-plus-face_sdxl_vit-h.bin'
|
||||
)
|
||||
results += [os.path.join(path_controlnet, 'ip-adapter-plus-face_sdxl_vit-h.bin')]
|
||||
results += [os.path.join(path_controlnet,
|
||||
'ip-adapter-plus-face_sdxl_vit-h.bin')]
|
||||
|
||||
return results
|
||||
|
||||
|
|
@ -949,6 +1029,7 @@ def downloading_upscale_model():
|
|||
)
|
||||
return os.path.join(path_upscale_models, 'fooocus_upscaler_s409985e5.bin')
|
||||
|
||||
|
||||
def downloading_safety_checker_model():
|
||||
load_file_from_url(
|
||||
url='https://huggingface.co/mashb1t/misc/resolve/main/stable-diffusion-safety-checker.bin',
|
||||
|
|
|
|||
|
|
@ -8,17 +8,21 @@ upscale_15 = 'Upscale (1.5x)'
|
|||
upscale_2 = 'Upscale (2x)'
|
||||
upscale_fast = 'Upscale (Fast 2x)'
|
||||
|
||||
uov_list = [disabled, subtle_variation, strong_variation, upscale_15, upscale_2, upscale_fast]
|
||||
uov_list = [disabled, subtle_variation, strong_variation,
|
||||
upscale_15, upscale_2, upscale_fast]
|
||||
|
||||
enhancement_uov_before = "Before First Enhancement"
|
||||
enhancement_uov_after = "After Last Enhancement"
|
||||
enhancement_uov_processing_order = [enhancement_uov_before, enhancement_uov_after]
|
||||
enhancement_uov_processing_order = [
|
||||
enhancement_uov_before, enhancement_uov_after]
|
||||
|
||||
enhancement_uov_prompt_type_original = 'Original Prompts'
|
||||
enhancement_uov_prompt_type_last_filled = 'Last Filled Enhancement Prompts'
|
||||
enhancement_uov_prompt_types = [enhancement_uov_prompt_type_original, enhancement_uov_prompt_type_last_filled]
|
||||
enhancement_uov_prompt_types = [
|
||||
enhancement_uov_prompt_type_original, enhancement_uov_prompt_type_last_filled]
|
||||
|
||||
CIVITAI_NO_KARRAS = ["euler", "euler_ancestral", "heun", "dpm_fast", "dpm_adaptive", "ddim", "uni_pc"]
|
||||
CIVITAI_NO_KARRAS = ["euler", "euler_ancestral", "heun",
|
||||
"dpm_fast", "dpm_adaptive", "ddim", "uni_pc"]
|
||||
|
||||
# fooocus: a1111 (Civitai)
|
||||
KSAMPLER = {
|
||||
|
|
@ -55,7 +59,8 @@ SAMPLERS = KSAMPLER | SAMPLER_EXTRA
|
|||
|
||||
KSAMPLER_NAMES = list(KSAMPLER.keys())
|
||||
|
||||
SCHEDULER_NAMES = ["normal", "karras", "exponential", "sgm_uniform", "simple", "ddim_uniform", "lcm", "turbo", "align_your_steps", "tcd", "edm_playground_v2.5"]
|
||||
SCHEDULER_NAMES = ["normal", "karras", "exponential", "sgm_uniform", "simple",
|
||||
"ddim_uniform", "lcm", "turbo", "align_your_steps", "tcd", "edm_playground_v2.5"]
|
||||
SAMPLER_NAMES = KSAMPLER_NAMES + list(SAMPLER_EXTRA.keys())
|
||||
|
||||
sampler_list = SAMPLER_NAMES
|
||||
|
|
@ -68,7 +73,8 @@ default_vae = 'Default (model)'
|
|||
refiner_swap_method = 'joint'
|
||||
|
||||
default_input_image_tab = 'uov_tab'
|
||||
input_image_tab_ids = ['uov_tab', 'ip_tab', 'inpaint_tab', 'describe_tab', 'enhance_tab', 'metadata_tab']
|
||||
input_image_tab_ids = ['uov_tab', 'ip_tab', 'inpaint_tab',
|
||||
'describe_tab', 'enhance_tab', 'metadata_tab']
|
||||
|
||||
cn_ip = "ImagePrompt"
|
||||
cn_ip_face = "FaceSwap"
|
||||
|
|
@ -84,7 +90,8 @@ default_parameters = {
|
|||
|
||||
output_formats = ['png', 'jpeg', 'webp']
|
||||
|
||||
inpaint_mask_models = ['u2net', 'u2netp', 'u2net_human_seg', 'u2net_cloth_seg', 'silueta', 'isnet-general-use', 'isnet-anime', 'sam']
|
||||
inpaint_mask_models = ['u2net', 'u2netp', 'u2net_human_seg',
|
||||
'u2net_cloth_seg', 'silueta', 'isnet-general-use', 'isnet-anime', 'sam']
|
||||
inpaint_mask_cloth_category = ['full', 'upper', 'lower']
|
||||
inpaint_mask_sam_model = ['vit_b', 'vit_l', 'vit_h']
|
||||
|
||||
|
|
@ -92,14 +99,15 @@ inpaint_engine_versions = ['None', 'v1', 'v2.5', 'v2.6']
|
|||
inpaint_option_default = 'Inpaint or Outpaint (default)'
|
||||
inpaint_option_detail = 'Improve Detail (face, hand, eyes, etc.)'
|
||||
inpaint_option_modify = 'Modify Content (add objects, change background, etc.)'
|
||||
inpaint_options = [inpaint_option_default, inpaint_option_detail, inpaint_option_modify]
|
||||
inpaint_options = [inpaint_option_default,
|
||||
inpaint_option_detail, inpaint_option_modify]
|
||||
|
||||
describe_type_photo = 'Photograph'
|
||||
describe_type_anime = 'Art/Anime'
|
||||
describe_types = [describe_type_photo, describe_type_anime]
|
||||
|
||||
sdxl_aspect_ratios = [
|
||||
'704*1408', '704*1344', '768*1344', '768*1280', '832*1216', '832*1152',
|
||||
'512*512', '704*704', '704*1408', '704*1344', '768*1344', '768*1280', '832*1216', '832*1152',
|
||||
'896*1152', '896*1088', '960*1088', '960*1024', '1024*1024', '1024*960',
|
||||
'1088*960', '1088*896', '1152*896', '1152*832', '1216*832', '1280*768',
|
||||
'1344*768', '1344*704', '1408*704', '1472*704', '1536*640', '1600*640',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
/* fallback */
|
||||
@font-face {
|
||||
font-family: 'Material Icons';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('material.woff2') format('woff2');
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-family: 'Material Icons';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
word-wrap: normal;
|
||||
direction: ltr;
|
||||
-webkit-font-feature-settings: 'liga';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
Binary file not shown.
|
|
@ -0,0 +1,398 @@
|
|||
#chart-button {
|
||||
position: sticky;
|
||||
transition: background-color 0.3s ease, width 0.8s ease, height 0.8s ease, transform 0.5s ease;
|
||||
background-color: #00000096 !important;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2) !important;
|
||||
color: white !important;
|
||||
border-radius: 10px !important;
|
||||
z-index: 9998;
|
||||
-webkit-app-region: drag; /* Make this container draggable */
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
transform: scale(0);
|
||||
-webkit-user-select: none; /* For Chrome, Safari, and Opera */
|
||||
-moz-user-select: none; /* For Firefox */
|
||||
-ms-user-select: none; /* For Internet Explorer and Edge */
|
||||
user-select: none; /* Standard syntax */
|
||||
}
|
||||
|
||||
#chart-button-container {
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height: 0% !important;
|
||||
width: 0% !important;
|
||||
transform: scale(0);
|
||||
position: fixed;
|
||||
z-index: 9991;
|
||||
will-change: transform;
|
||||
text-align: center;
|
||||
transition: width 0.8s ease, height 0.8s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
#chart-button-container.active {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
#mydivheader {
|
||||
padding: 10px;
|
||||
cursor: move;
|
||||
z-index: 10;
|
||||
background-color: #2196f3;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
body {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
#chart-button.small {
|
||||
transform: scale(0.83);
|
||||
}
|
||||
|
||||
#chart-button.medium {
|
||||
transform: scale(0.93);
|
||||
}
|
||||
|
||||
#chart-button.large {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
#chart-button.bottom-right {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#chart-button.bottom-left {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#chart-button.bottom-center {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#chart-button.top-right {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#chart-button.top-left {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#chart-button.top-center {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#chart-button.left-center {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#chart-button.right-center {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#chart-button.center {
|
||||
margin: 0px;
|
||||
}
|
||||
#chart-button.bottom-right.active {
|
||||
margin: 0px;
|
||||
}
|
||||
#chart-button.bottom-left.active {
|
||||
margin: 0px;
|
||||
}
|
||||
#chart-button.bottom-center.active {
|
||||
margin: 0px;
|
||||
}
|
||||
#chart-button.top-right.active {
|
||||
margin: 0px;
|
||||
}
|
||||
#chart-button.top-left.active {
|
||||
margin: 0px;
|
||||
}
|
||||
#chart-button.top-center.active {
|
||||
margin: 0px;
|
||||
}
|
||||
#chart-button.left-center.active {
|
||||
margin: 0px;
|
||||
}
|
||||
#chart-button.right-center.active {
|
||||
margin: 0px;
|
||||
}
|
||||
#chart-button.center.active {
|
||||
margin: 0px;
|
||||
}
|
||||
.chart-row {
|
||||
padding: 5px 5px 0px 5px !important;
|
||||
text-align: right !important;
|
||||
display: flex !important;
|
||||
justify-content: space-evenly !important;
|
||||
z-index: 9999 !important;
|
||||
position: relative !important;
|
||||
align-items: center;
|
||||
margin-bottom: -10px;
|
||||
}
|
||||
|
||||
.chart-row.no-drag {
|
||||
-webkit-app-region: no-drag; /* Make these elements non-draggable */
|
||||
}
|
||||
|
||||
.chart-col {
|
||||
flex: auto !important;
|
||||
}
|
||||
|
||||
.left-col a {
|
||||
width: 15px !important;
|
||||
cursor: pointer !important;
|
||||
border-radius: 4px !important;
|
||||
display: inline-block !important;
|
||||
text-align: center !important;
|
||||
color: #fff !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.left-col a:hover {
|
||||
background-color: #fff !important;
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
#chart-container.bar.small {
|
||||
padding: 5px !important;
|
||||
}
|
||||
|
||||
#chart-container.bar.medium {
|
||||
padding: 5px !important;
|
||||
}
|
||||
|
||||
#chart-container.bar.large {
|
||||
padding: 5px !important;
|
||||
}
|
||||
|
||||
#chart-container.line.small {
|
||||
padding: 5px !important;
|
||||
}
|
||||
|
||||
#chart-container.line.medium {
|
||||
padding: 5px !important;
|
||||
}
|
||||
|
||||
#chart-container.line.large {
|
||||
padding: 5px !important;
|
||||
}
|
||||
|
||||
i {
|
||||
color: #fff !important; /* Adjust color */
|
||||
cursor: pointer !important; /* Change cursor to pointer on hover */
|
||||
-webkit-app-region: no-drag; /* Make these elements non-draggable */
|
||||
}
|
||||
|
||||
a,
|
||||
button {
|
||||
-webkit-app-region: no-drag; /* Make these elements non-draggable */
|
||||
}
|
||||
|
||||
.toggle-resources-button:hover {
|
||||
color: rgb(101, 101, 101) !important; /* Change color on hover */
|
||||
}
|
||||
|
||||
.drag {
|
||||
-webkit-app-region: drag; /* Make this container draggable */
|
||||
}
|
||||
|
||||
.no-drag {
|
||||
-webkit-app-region: no-drag; /* Make these elements non-draggable */
|
||||
}
|
||||
|
||||
#settingsMenu {
|
||||
display: grid !important; /* Show the menu */
|
||||
position: absolute !important;
|
||||
transform: scale(0) translateX(-100%) translateY(-200%) !important; /* Center alignment */
|
||||
background: #000000 !important;
|
||||
border: 0px solid #ddd !important;
|
||||
border-radius: 6px !important;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !important;
|
||||
opacity: 0 !important;
|
||||
transition: opacity 0.5s ease, transform 0.3s ease !important;
|
||||
text-align: center;
|
||||
-webkit-app-region: no-drag; /* Make these elements non-draggable */
|
||||
}
|
||||
|
||||
#settingsMenu.show {
|
||||
min-width: 110px;
|
||||
opacity: 1 !important;
|
||||
z-index: 1000 !important;
|
||||
transform: scale(0.8) translateY(-30px) translateX(-10px) !important; /* Animate to visible state */
|
||||
}
|
||||
|
||||
.position-menu {
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(3, 1fr) !important;
|
||||
grid-template-rows: repeat(3, 1fr) !important;
|
||||
grid-gap: 0px !important;
|
||||
}
|
||||
|
||||
.position-btn > i {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.position-btn {
|
||||
color: #fff !important;
|
||||
padding: 6px !important;
|
||||
margin: 0px !important;
|
||||
font-size: 12px !important;
|
||||
cursor: pointer !important;
|
||||
border: 0px solid #ccc !important;
|
||||
background-color: transparent !important;
|
||||
border-radius: 4px !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.position-btn:hover {
|
||||
background-color: #fff !important;
|
||||
}
|
||||
.position-btn:hover > i {
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.left-col {
|
||||
text-align: left !important;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.left-col.text {
|
||||
margin-bottom: 5px !important;
|
||||
}
|
||||
.settings-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: 4px !important;
|
||||
transition: width 1s ease;
|
||||
}
|
||||
|
||||
.settings-hr.show {
|
||||
width: 100%;
|
||||
}
|
||||
#custom-legend {
|
||||
display: flex !important;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.custom-legend-item {
|
||||
margin-bottom: 5px !important;
|
||||
font-size: 14px !important;
|
||||
flex: auto !important;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.custom-legend-color {
|
||||
display: inline-block !important;
|
||||
width: 15px !important;
|
||||
height: 15px !important;
|
||||
margin-right: 10px !important;
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
|
||||
.custom-legend-text {
|
||||
display: inline !important;
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
|
||||
#show_resource_monitor {
|
||||
cursor: pointer !important;
|
||||
z-index: 9991;
|
||||
margin: 5px;
|
||||
display: flex;
|
||||
font-size: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.resource-monitor-icon {
|
||||
margin: 5px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
/* Example CSS transition for smooth resizing */
|
||||
.window {
|
||||
transition: width 0.3s ease, height 0.3s ease;
|
||||
}
|
||||
|
||||
#show_resource_monitor_link {
|
||||
cursor: pointer !important;
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 10px;
|
||||
z-index: 9991;
|
||||
margin: 5px;
|
||||
display: flex;
|
||||
font-size: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.resource-monitor-icon {
|
||||
margin: 5px;
|
||||
height: 14px;
|
||||
}
|
||||
#item-table {
|
||||
width: -webkit-fill-available;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
margin-bottom: 5px;
|
||||
border-collapse: separate; /* Ensures that border-radius works */
|
||||
border-spacing: 0; /* Removes spacing between cells */
|
||||
border: 0px solid #ddd; /* Adds border to the table */
|
||||
border-radius: 8px; /* Adjust the value for desired corner radius */
|
||||
overflow: hidden; /* Ensures rounded corners are visible */
|
||||
}
|
||||
|
||||
#item-table th,
|
||||
#item-table td {
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
text-align: left;
|
||||
line-height: 10px;
|
||||
}
|
||||
|
||||
#item-table thead {
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#item-table tbody tr:nth-child(even) {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
#item-table tbody tr:nth-child(odd) {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#item-table tbody td {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#progress-bar-container {
|
||||
width: -webkit-fill-available;
|
||||
|
||||
height: 20px; /* Height of the progress bar */
|
||||
background-color: #e0e0e0; /* Light gray background */
|
||||
border-radius: 10px; /* Rounded corners for the progress bar container */
|
||||
margin: 0px 5px; /* Space between the progress bar and the table */
|
||||
display: none;
|
||||
}
|
||||
|
||||
#progress-bar {
|
||||
height: 100%;
|
||||
width: 0%; /* Start with 0% width */
|
||||
background-color: #76c7c0; /* Fill color of the progress bar */
|
||||
border-radius: 10px; /* Rounded corners for the progress bar */
|
||||
transition: width 0.5s ease; /* Smooth transition effect */
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 99 B |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.0 KiB |
|
|
@ -0,0 +1,563 @@
|
|||
// VARS AND CONST
|
||||
const hostname = 'localhost' // Gets the host without port
|
||||
const baseUrl = `http://${hostname}:5000` // Append the port 5000
|
||||
const apiUrl = `${baseUrl}/gpu_usage/`
|
||||
|
||||
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)',
|
||||
]
|
||||
|
||||
let currentChart = null // Track the current chart instance
|
||||
const MAX_DATA_POINTS = 50 // Number of data points to keep
|
||||
|
||||
// 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 ? '\u00B0' : '%'}`,
|
||||
})
|
||||
})
|
||||
const size = localStorage.getItem('chart-size') ?? 'small'
|
||||
let fontSize = 10 // Default font size
|
||||
|
||||
switch (size) {
|
||||
case 'small':
|
||||
fontSize = '7px'
|
||||
break
|
||||
case 'medium':
|
||||
fontSize = '16px'
|
||||
break
|
||||
default:
|
||||
fontSize = '18px'
|
||||
break
|
||||
}
|
||||
|
||||
ctx.font = fontSize
|
||||
ctx.fillStyle = '#FFFFFF'
|
||||
ctx.textAlign = 'center'
|
||||
ctx.textBaseline = 'middle'
|
||||
|
||||
labelPositions.forEach((label) => {
|
||||
ctx.fillText(label.value, label.x, label.y)
|
||||
})
|
||||
|
||||
ctx.restore()
|
||||
},
|
||||
}
|
||||
|
||||
// Initialize the bar chart
|
||||
window.initializeBarChart = function () {
|
||||
localStorage.setItem('active-chart', 'bar')
|
||||
var table = document.getElementById('item-table')
|
||||
table.style.display = 'none'
|
||||
const chartContainer = document.getElementById('chart-container')
|
||||
const existingCanvas = document.getElementById('usage-chart')
|
||||
const chartWrapper = document.getElementById('chart-wrapper')
|
||||
if (existingCanvas) {
|
||||
chartContainer.removeChild(existingCanvas)
|
||||
}
|
||||
const size = localStorage.getItem('chart-size') ?? 'small'
|
||||
let fontSize = 10 // Default font size
|
||||
|
||||
switch (size) {
|
||||
case 'small':
|
||||
fontSize = '7px'
|
||||
break
|
||||
case 'medium':
|
||||
fontSize = '16px'
|
||||
break
|
||||
default:
|
||||
fontSize = '18px'
|
||||
break
|
||||
}
|
||||
|
||||
// Create a new canvas element
|
||||
const newCanvas = document.createElement('canvas')
|
||||
newCanvas.id = 'usage-chart'
|
||||
newCanvas.classList.add('bar') // Add the class directly to the canvas element
|
||||
chartContainer.appendChild(newCanvas)
|
||||
|
||||
const ctx = newCanvas.getContext('2d')
|
||||
$(chartWrapper).hide()
|
||||
|
||||
currentChart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: ['CPU', 'RAM', 'GPU', 'VRAM', 'HDD', 'TEMP'],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Usage',
|
||||
data: [0, 0, 0, 0, 0],
|
||||
barPercentage: 0.8, // Adjust space occupied by bars
|
||||
categoryPercentage: 1, // Adjust space between bars
|
||||
backgroundColor: function (context) {
|
||||
const value = context.dataset.data[context.dataIndex]
|
||||
return value > 90 ? '#D9534F' : colorPalette[context.dataIndex]
|
||||
},
|
||||
borderColor: function (context) {
|
||||
const value = context.dataset.data[context.dataIndex]
|
||||
return value > 90 ? '#D9534F' : borderColors[context.dataIndex]
|
||||
},
|
||||
borderWidth: 1.5,
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
indexAxis: 'y', // Horizontal bars
|
||||
scales: {
|
||||
x: {
|
||||
grid: {
|
||||
display: false, // Hide all grid lines
|
||||
},
|
||||
border: {
|
||||
display: false, // Hide all grid lines
|
||||
},
|
||||
beginAtZero: true,
|
||||
max: 100,
|
||||
ticks: {
|
||||
color: '#ffffff',
|
||||
font: {
|
||||
size: fontSize,
|
||||
weight: 600,
|
||||
},
|
||||
align: 'center',
|
||||
callback: function (value, index, ticks) {
|
||||
return value + '%'
|
||||
},
|
||||
},
|
||||
},
|
||||
y: {
|
||||
grid: {
|
||||
display: false,
|
||||
},
|
||||
border: {
|
||||
color: '#ffffff30',
|
||||
width: 1, // Width of the axis border
|
||||
},
|
||||
ticks: {
|
||||
color: '#FFFFFF',
|
||||
crossAlign: 'far',
|
||||
font: {
|
||||
weight: 600,
|
||||
size: fontSize,
|
||||
},
|
||||
// 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
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
},
|
||||
plugins: [fixedLabelPlugin], // Register the custom plugins
|
||||
})
|
||||
|
||||
currentChart.options.animation = true
|
||||
const legendContainer = document.getElementById('custom-legend')
|
||||
legendContainer.innerHTML = ''
|
||||
|
||||
document.getElementById('settingsMenu').classList.remove('show') // Hide the menu
|
||||
|
||||
document.querySelectorAll('canvas').forEach((row) => {
|
||||
row.classList.remove('no-drag')
|
||||
row.classList.add('drag')
|
||||
})
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
currentChart.resize()
|
||||
})
|
||||
$(chartWrapper).fadeIn(300)
|
||||
if (size == 'large') {
|
||||
$(table).fadeIn(800)
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the line chart
|
||||
window.initializeLineChart = function () {
|
||||
localStorage.setItem('active-chart', 'line')
|
||||
var table = document.getElementById('item-table')
|
||||
table.style.display = 'none'
|
||||
const size = localStorage.getItem('chart-size') ?? 'medium'
|
||||
const existingCanvas = document.getElementById('usage-chart')
|
||||
const chartContainer = document.getElementById('chart-container')
|
||||
const chartWrapper = document.getElementById('chart-wrapper')
|
||||
if (existingCanvas) {
|
||||
chartContainer.removeChild(existingCanvas)
|
||||
}
|
||||
|
||||
// Create a new canvas element
|
||||
const newCanvas = document.createElement('canvas')
|
||||
newCanvas.id = 'usage-chart'
|
||||
newCanvas.classList.add('line') // Add the class directly to the canvas element
|
||||
chartContainer.appendChild(newCanvas)
|
||||
$(chartWrapper).hide()
|
||||
|
||||
const ctx = newCanvas.getContext('2d')
|
||||
|
||||
currentChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
label: 'CPU',
|
||||
data: [],
|
||||
borderColor: function (context) {
|
||||
const dataset = context.dataset
|
||||
const datasetIndex = context.datasetIndex
|
||||
const shouldUseRed = dataset.data.some((value) => value > 90)
|
||||
|
||||
if (shouldUseRed) {
|
||||
return '#D9534F' // Return red color if any value exceeds 90
|
||||
}
|
||||
return borderColors[datasetIndex % borderColors.length]
|
||||
},
|
||||
borderWidth: 1.5,
|
||||
backgroundColor: function (context) {
|
||||
const dataset = context.dataset
|
||||
const datasetIndex = context.datasetIndex
|
||||
const shouldUseRed = dataset.data.some((value) => value > 90)
|
||||
|
||||
if (shouldUseRed) {
|
||||
return '#D9534F' // Return red color if any value exceeds 90
|
||||
}
|
||||
return colorPalette[datasetIndex % borderColors.length]
|
||||
},
|
||||
fill: false,
|
||||
tension: 0.1,
|
||||
},
|
||||
{
|
||||
label: 'RAM',
|
||||
data: [],
|
||||
borderColor: function (context) {
|
||||
const dataset = context.dataset
|
||||
const datasetIndex = context.datasetIndex
|
||||
const shouldUseRed = dataset.data.some((value) => value > 90)
|
||||
|
||||
if (shouldUseRed) {
|
||||
return '#D9534F' // Return red color if any value exceeds 90
|
||||
}
|
||||
return borderColors[datasetIndex % borderColors.length]
|
||||
},
|
||||
borderWidth: 1.5,
|
||||
backgroundColor: function (context) {
|
||||
const dataset = context.dataset
|
||||
const datasetIndex = context.datasetIndex
|
||||
const shouldUseRed = dataset.data.some((value) => value > 90)
|
||||
|
||||
if (shouldUseRed) {
|
||||
return '#D9534F' // Return red color if any value exceeds 90
|
||||
}
|
||||
return colorPalette[datasetIndex % borderColors.length]
|
||||
},
|
||||
fill: false,
|
||||
tension: 0.1,
|
||||
},
|
||||
{
|
||||
label: 'GPU',
|
||||
data: [],
|
||||
borderColor: function (context) {
|
||||
const dataset = context.dataset
|
||||
const datasetIndex = context.datasetIndex
|
||||
const shouldUseRed = dataset.data.some((value) => value > 90)
|
||||
|
||||
if (shouldUseRed) {
|
||||
return '#D9534F' // Return red color if any value exceeds 90
|
||||
}
|
||||
return borderColors[datasetIndex % borderColors.length]
|
||||
},
|
||||
borderWidth: 1.5,
|
||||
backgroundColor: function (context) {
|
||||
const dataset = context.dataset
|
||||
const datasetIndex = context.datasetIndex
|
||||
const shouldUseRed = dataset.data.some((value) => value > 90)
|
||||
|
||||
if (shouldUseRed) {
|
||||
return '#D9534F' // Return red color if any value exceeds 90
|
||||
}
|
||||
return colorPalette[datasetIndex % borderColors.length]
|
||||
},
|
||||
fill: false,
|
||||
tension: 0.1,
|
||||
},
|
||||
{
|
||||
label: 'VRAM',
|
||||
data: [],
|
||||
borderColor: function (context) {
|
||||
const dataset = context.dataset
|
||||
const datasetIndex = context.datasetIndex
|
||||
const shouldUseRed = dataset.data.some((value) => value > 90)
|
||||
|
||||
if (shouldUseRed) {
|
||||
return '#D9534F' // Return red color if any value exceeds 90
|
||||
}
|
||||
return borderColors[datasetIndex % borderColors.length]
|
||||
},
|
||||
borderWidth: 1.5,
|
||||
backgroundColor: function (context) {
|
||||
const dataset = context.dataset
|
||||
const datasetIndex = context.datasetIndex
|
||||
const shouldUseRed = dataset.data.some((value) => value > 90)
|
||||
|
||||
if (shouldUseRed) {
|
||||
return '#D9534F' // Return red color if any value exceeds 90
|
||||
}
|
||||
return colorPalette[datasetIndex % borderColors.length]
|
||||
},
|
||||
fill: false,
|
||||
tension: 0.1,
|
||||
},
|
||||
{
|
||||
label: 'HDD',
|
||||
data: [],
|
||||
borderColor: function (context) {
|
||||
const dataset = context.dataset
|
||||
const datasetIndex = context.datasetIndex
|
||||
const shouldUseRed = dataset.data.some((value) => value > 90)
|
||||
|
||||
if (shouldUseRed) {
|
||||
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]
|
||||
},
|
||||
borderWidth: 1.5,
|
||||
backgroundColor: function (context) {
|
||||
const dataset = context.dataset
|
||||
const datasetIndex = context.datasetIndex
|
||||
const shouldUseRed = dataset.data.some((value) => value > 90)
|
||||
|
||||
if (shouldUseRed) {
|
||||
return '#D9534F' // Return red color if any value exceeds 90
|
||||
}
|
||||
return colorPalette[datasetIndex % borderColors.length]
|
||||
},
|
||||
fill: false,
|
||||
tension: 0.1,
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
animation: {
|
||||
enabled: false,
|
||||
tension: {
|
||||
duration: 1000,
|
||||
easing: 'linear',
|
||||
from: 1,
|
||||
to: 0,
|
||||
loop: true,
|
||||
},
|
||||
},
|
||||
elements: {
|
||||
point: {
|
||||
radius: 0,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
max: 100,
|
||||
ticks: {
|
||||
color: '#FFFFFF',
|
||||
crossAlign: 'far',
|
||||
padding: 0,
|
||||
font: {
|
||||
weight: 600,
|
||||
size: 7,
|
||||
},
|
||||
callback: function (value, index, ticks) {
|
||||
return value + '%'
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true,
|
||||
labels: {
|
||||
generateLabels: false,
|
||||
},
|
||||
},
|
||||
title: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
currentChart.options.animation = false
|
||||
generateCustomLegend()
|
||||
document.getElementById('settingsMenu').classList.remove('show') // Hide the menu
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
currentChart.resize()
|
||||
})
|
||||
$(chartWrapper).fadeIn(300)
|
||||
if (size == 'large') {
|
||||
$(table).fadeIn(800)
|
||||
}
|
||||
}
|
||||
|
||||
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 ? '800' : `600`
|
||||
const size = localStorage.getItem('chart-size') ?? 'small'
|
||||
switch (size) {
|
||||
case 'small':
|
||||
legendText.style.fontSize = '7px'
|
||||
break
|
||||
case 'medium':
|
||||
legendText.style.fontSize = '16px'
|
||||
break
|
||||
default:
|
||||
legendText.style.fontSize = '18px'
|
||||
break
|
||||
}
|
||||
|
||||
legendItem.appendChild(legendText)
|
||||
legendContainer.appendChild(legendItem)
|
||||
})
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
const socket = io('http://localhost:5000')
|
||||
|
||||
// Function to update the chart with new data
|
||||
function updateChart(data) {
|
||||
const timestamp = new Date().toLocaleTimeString()
|
||||
|
||||
if (currentChart) {
|
||||
if (currentChart.config.type === 'bar') {
|
||||
// Update data for bar chart
|
||||
currentChart.data.datasets[0].data = [
|
||||
data.cpu,
|
||||
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.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
|
||||
const MAX_DATA_POINTS = 50 // Adjust as needed
|
||||
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
|
||||
currentChart.update()
|
||||
}
|
||||
}
|
||||
|
||||
// Handle data updates from the WebSocket server
|
||||
socket.on('data_update', function (data) {
|
||||
updateChart(data)
|
||||
})
|
||||
|
||||
// Optional: Handle WebSocket connection errors
|
||||
socket.on('connect_error', function (error) {
|
||||
console.error('Connection error:', error)
|
||||
})
|
||||
|
||||
// Optional: Handle WebSocket disconnection
|
||||
socket.on('disconnect', function () {
|
||||
console.log('WebSocket disconnected')
|
||||
})
|
||||
}, 4000)
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,105 @@
|
|||
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/elegant-resource-monitor.js'
|
||||
document.body.appendChild(script)
|
||||
|
||||
var script = document.createElement('script')
|
||||
script.src = '/file=web/assets/js/socket.io.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/script.js'
|
||||
document.body.appendChild(script)
|
||||
|
||||
var chart = document.createElement('script')
|
||||
chart.src = '/file=web/assets/js/chart-settings.js'
|
||||
document.body.appendChild(chart)
|
||||
}
|
||||
await loadHtmlContent()
|
||||
}
|
||||
|
|
@ -0,0 +1,211 @@
|
|||
import { api } from '/scripts/api.js'
|
||||
import { getResolver } from './shared_utils.js'
|
||||
export class ElegantResourceMonitorExecution {
|
||||
constructor(id) {
|
||||
this.promptApi = null
|
||||
this.executedNodeIds = []
|
||||
this.totalNodes = 0
|
||||
this.currentlyExecuting = null
|
||||
this.errorDetails = null
|
||||
this.apiPrompt = getResolver()
|
||||
this.id = id
|
||||
}
|
||||
setPrompt(prompt) {
|
||||
this.promptApi = prompt.output
|
||||
this.totalNodes = Object.keys(this.promptApi).length
|
||||
this.apiPrompt.resolve(null)
|
||||
}
|
||||
getApiNode(nodeId) {
|
||||
var _a
|
||||
return ((_a = this.promptApi) === null || _a === void 0 ? void 0 : _a[String(nodeId)]) || null
|
||||
}
|
||||
getNodeLabel(nodeId) {
|
||||
var _a, _b
|
||||
const apiNode = this.getApiNode(nodeId)
|
||||
let label =
|
||||
((_a = apiNode === null || apiNode === void 0 ? void 0 : apiNode._meta) === null ||
|
||||
_a === void 0
|
||||
? void 0
|
||||
: _a.title) ||
|
||||
(apiNode === null || apiNode === void 0 ? void 0 : apiNode.class_type) ||
|
||||
undefined
|
||||
if (!label) {
|
||||
const graphNode =
|
||||
(_b = this.maybeGetComfyGraph()) === null || _b === void 0
|
||||
? void 0
|
||||
: _b.getNodeById(Number(nodeId))
|
||||
label =
|
||||
(graphNode === null || graphNode === void 0 ? void 0 : graphNode.title) ||
|
||||
(graphNode === null || graphNode === void 0 ? void 0 : graphNode.type) ||
|
||||
undefined
|
||||
}
|
||||
return label
|
||||
}
|
||||
executing(nodeId, step, maxSteps) {
|
||||
var _a
|
||||
if (nodeId == null) {
|
||||
this.currentlyExecuting = null
|
||||
return
|
||||
}
|
||||
if (
|
||||
((_a = this.currentlyExecuting) === null || _a === void 0 ? void 0 : _a.nodeId) !== nodeId
|
||||
) {
|
||||
if (this.currentlyExecuting != null) {
|
||||
this.executedNodeIds.push(nodeId)
|
||||
}
|
||||
this.currentlyExecuting = { nodeId, nodeLabel: this.getNodeLabel(nodeId), pass: 0 }
|
||||
this.apiPrompt.promise.then(() => {
|
||||
var _a
|
||||
if (this.currentlyExecuting == null) {
|
||||
return
|
||||
}
|
||||
const apiNode = this.getApiNode(nodeId)
|
||||
if (!this.currentlyExecuting.nodeLabel) {
|
||||
this.currentlyExecuting.nodeLabel = this.getNodeLabel(nodeId)
|
||||
}
|
||||
if (
|
||||
(apiNode === null || apiNode === void 0 ? void 0 : apiNode.class_type) ===
|
||||
'UltimateSDUpscale'
|
||||
) {
|
||||
this.currentlyExecuting.pass--
|
||||
this.currentlyExecuting.maxPasses = -1
|
||||
} else if (
|
||||
(apiNode === null || apiNode === void 0 ? void 0 : apiNode.class_type) ===
|
||||
'IterativeImageUpscale'
|
||||
) {
|
||||
this.currentlyExecuting.maxPasses =
|
||||
(_a = apiNode === null || apiNode === void 0 ? void 0 : apiNode.inputs['steps']) !==
|
||||
null && _a !== void 0
|
||||
? _a
|
||||
: -1
|
||||
}
|
||||
})
|
||||
}
|
||||
if (step != null) {
|
||||
if (!this.currentlyExecuting.step || step < this.currentlyExecuting.step) {
|
||||
this.currentlyExecuting.pass++
|
||||
}
|
||||
this.currentlyExecuting.step = step
|
||||
this.currentlyExecuting.maxSteps = maxSteps
|
||||
}
|
||||
}
|
||||
error(details) {
|
||||
this.errorDetails = details
|
||||
}
|
||||
maybeGetComfyGraph() {
|
||||
var _a
|
||||
return (
|
||||
((_a = window === null || window === void 0 ? void 0 : window.app) === null || _a === void 0
|
||||
? void 0
|
||||
: _a.graph) || null
|
||||
)
|
||||
}
|
||||
}
|
||||
class ElegantResourceMonitorService extends EventTarget {
|
||||
constructor(api) {
|
||||
super()
|
||||
this.promptsMap = new Map()
|
||||
this.currentExecution = null
|
||||
this.lastQueueRemaining = 0
|
||||
const that = this
|
||||
const queuePrompt = api.queuePrompt
|
||||
api.queuePrompt = async function (num, prompt) {
|
||||
let response
|
||||
try {
|
||||
response = await queuePrompt.apply(api, [...arguments])
|
||||
} catch (e) {
|
||||
const promptExecution = that.getOrMakePrompt('error')
|
||||
promptExecution.error({ exception_type: 'Unknown.' })
|
||||
throw e
|
||||
}
|
||||
const promptExecution = that.getOrMakePrompt(response.prompt_id)
|
||||
promptExecution.setPrompt(prompt)
|
||||
if (!that.currentExecution) {
|
||||
that.currentExecution = promptExecution
|
||||
}
|
||||
that.promptsMap.set(response.prompt_id, promptExecution)
|
||||
|
||||
return response
|
||||
}
|
||||
api.addEventListener('status', (e) => {
|
||||
var _a
|
||||
if (!((_a = e.detail) === null || _a === void 0 ? void 0 : _a.exec_info)) return
|
||||
this.lastQueueRemaining = e.detail.exec_info.queue_remaining
|
||||
this.dispatchProgressUpdate()
|
||||
})
|
||||
api.addEventListener('execution_start', (e) => {
|
||||
if (!this.promptsMap.has(e.detail.prompt_id)) {
|
||||
console.warn("'execution_start' fired before prompt was made.")
|
||||
}
|
||||
const prompt = this.getOrMakePrompt(e.detail.prompt_id)
|
||||
this.currentExecution = prompt
|
||||
this.dispatchProgressUpdate()
|
||||
})
|
||||
api.addEventListener('executing', (e) => {
|
||||
if (!this.currentExecution) {
|
||||
this.currentExecution = this.getOrMakePrompt('unknown')
|
||||
console.warn("'executing' fired before prompt was made.")
|
||||
}
|
||||
this.currentExecution.executing(e.detail)
|
||||
this.dispatchProgressUpdate()
|
||||
if (e.detail == null) {
|
||||
this.currentExecution = null
|
||||
}
|
||||
})
|
||||
api.addEventListener('progress', (e) => {
|
||||
if (!this.currentExecution) {
|
||||
this.currentExecution = this.getOrMakePrompt(e.detail.prompt_id)
|
||||
console.warn("'progress' fired before prompt was made.")
|
||||
}
|
||||
this.currentExecution.executing(e.detail.node, e.detail.value, e.detail.max)
|
||||
this.dispatchProgressUpdate()
|
||||
})
|
||||
api.addEventListener('execution_cached', (e) => {
|
||||
if (!this.currentExecution) {
|
||||
this.currentExecution = this.getOrMakePrompt(e.detail.prompt_id)
|
||||
console.warn("'execution_cached' fired before prompt was made.")
|
||||
}
|
||||
for (const cached of e.detail.nodes) {
|
||||
this.currentExecution.executing(cached)
|
||||
}
|
||||
this.dispatchProgressUpdate()
|
||||
})
|
||||
api.addEventListener('executed', (e) => {
|
||||
if (!this.currentExecution) {
|
||||
this.currentExecution = this.getOrMakePrompt(e.detail.prompt_id)
|
||||
console.warn("'executed' fired before prompt was made.")
|
||||
}
|
||||
})
|
||||
api.addEventListener('execution_error', (e) => {
|
||||
var _a
|
||||
if (!this.currentExecution) {
|
||||
this.currentExecution = this.getOrMakePrompt(e.detail.prompt_id)
|
||||
console.warn("'execution_error' fired before prompt was made.")
|
||||
}
|
||||
;(_a = this.currentExecution) === null || _a === void 0 ? void 0 : _a.error(e.detail)
|
||||
this.dispatchProgressUpdate()
|
||||
})
|
||||
}
|
||||
async queuePrompt(prompt) {
|
||||
return await api.queuePrompt(-1, prompt)
|
||||
}
|
||||
dispatchProgressUpdate() {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('elegant-resource-monitor-update', {
|
||||
detail: {
|
||||
queue: this.lastQueueRemaining,
|
||||
prompt: this.currentExecution,
|
||||
},
|
||||
}),
|
||||
)
|
||||
}
|
||||
getOrMakePrompt(id) {
|
||||
let prompt = this.promptsMap.get(id)
|
||||
if (!prompt) {
|
||||
prompt = new ElegantResourceMonitorExecution(id)
|
||||
this.promptsMap.set(id, prompt)
|
||||
}
|
||||
return prompt
|
||||
}
|
||||
}
|
||||
export const MONITOR_SERVICE = new ElegantResourceMonitorService(api)
|
||||
|
|
@ -0,0 +1,288 @@
|
|||
class ElegantResourceMonitor extends HTMLElement {
|
||||
constructor() {
|
||||
super()
|
||||
this.shadow = null
|
||||
this.currentPromptExecution = null
|
||||
this.onProgressUpdateBound = this.onProgressUpdate.bind(this)
|
||||
this.connected = false
|
||||
}
|
||||
|
||||
render() {
|
||||
this.innerHTML = `
|
||||
<div id="chart-button-container" draggable="true">
|
||||
<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="settings-row"><div class="settings-col">Settings</div></div>
|
||||
<hr id="settings-hr" class="settings-hr" />
|
||||
<div class="settings-row">
|
||||
<div class="settings-row">
|
||||
<div class="settings-col">Layout:</div>
|
||||
<div class="settings-col">
|
||||
<a href="#" onclick="barChart()">1</a> |
|
||||
<a href="#" onclick="lineChart()">2</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-row">
|
||||
<div class="settings-col">Size:</div>
|
||||
<div class="settings-col">
|
||||
<a href="#" onclick="smallChart()">S</a> |
|
||||
<a href="#" 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 position-clickable" 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">close</i>
|
||||
</div>
|
||||
</div>
|
||||
<div id="chart-wrapper">
|
||||
<div id="progress-bar-container">
|
||||
<div id="progress-bar"></div>
|
||||
</div>
|
||||
<div id="chart-container">
|
||||
<canvas id="usage-chart" style="width: 100%; height: 100%"></canvas>
|
||||
</div>
|
||||
<div id="custom-legend"></div>
|
||||
<div id="table-view">
|
||||
<table id="item-table">
|
||||
<tbody id="item-body">
|
||||
<!-- Table rows will be dynamically added here -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
addEventListeners() {
|
||||
this.querySelector('#popupTrigger').addEventListener('click', () => {
|
||||
const settingsMenu = this.querySelector('#settingsMenu')
|
||||
settingsMenu.style.display = settingsMenu.style.display === 'block' ? 'none' : 'block'
|
||||
})
|
||||
|
||||
this.querySelector('#close-button').addEventListener('click', () => {
|
||||
this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }))
|
||||
})
|
||||
}
|
||||
|
||||
getSizes() {
|
||||
const savedChart = localStorage.getItem('active-chart') ?? 'bar'
|
||||
var sizes = {}
|
||||
if (savedChart == 'bar') {
|
||||
sizes = {
|
||||
small: { height: '120', width: '150' },
|
||||
medium: { height: '300', width: '410' },
|
||||
large: { height: '450', width: '700' },
|
||||
}
|
||||
} else {
|
||||
sizes = {
|
||||
small: { height: '110', width: '160' },
|
||||
medium: { height: '245', width: '425' },
|
||||
large: { height: '380', width: '700' },
|
||||
}
|
||||
}
|
||||
return sizes
|
||||
}
|
||||
getButtonSize() {
|
||||
const size = localStorage.getItem('chart-size') ?? 'medium'
|
||||
const sizes = this.getSizes()
|
||||
const sizeStyles = sizes[size]
|
||||
var buttonHeight = +sizeStyles.height + 25
|
||||
const buttonWidth = +sizeStyles.width + 25
|
||||
var table = this.querySelector('#item-table')
|
||||
var totalRowCount = table.rows.length // 5
|
||||
var tableHeight = totalRowCount * 30
|
||||
if (size == 'large') {
|
||||
buttonHeight = +buttonHeight + tableHeight
|
||||
}
|
||||
|
||||
return { buttonHeight, buttonWidth }
|
||||
}
|
||||
|
||||
addListItem(itemContent) {
|
||||
const itemBody = this.querySelector('#item-body')
|
||||
const row = document.createElement('tr')
|
||||
const cell = document.createElement('td')
|
||||
cell.innerText = itemContent
|
||||
row.appendChild(cell)
|
||||
itemBody.appendChild(row)
|
||||
}
|
||||
|
||||
get currentNodeId() {
|
||||
var _a, _b
|
||||
const prompt = this.currentPromptExecution
|
||||
const nodeId =
|
||||
((_a = prompt === null || prompt === void 0 ? void 0 : prompt.errorDetails) === null ||
|
||||
_a === void 0
|
||||
? void 0
|
||||
: _a.node_id) ||
|
||||
((_b = prompt === null || prompt === void 0 ? void 0 : prompt.currentlyExecuting) === null ||
|
||||
_b === void 0
|
||||
? void 0
|
||||
: _b.nodeId)
|
||||
return nodeId || null
|
||||
}
|
||||
|
||||
adjustButtonHeight(e) {
|
||||
const chartButtonContainer = this.querySelector('#chart-button-container')
|
||||
|
||||
const shouldShowPerfMonitor = JSON.parse(localStorage.getItem('shouldShowPerfMonitor')) ?? false
|
||||
if (!shouldShowPerfMonitor || chartButtonContainer.clientHeight == 0) return
|
||||
|
||||
const { buttonHeight, buttonWidth } = this.getButtonSize()
|
||||
const chartContainer = this.querySelector('#chart-container')
|
||||
const savedChart = localStorage.getItem('active-chart') ?? 'bar'
|
||||
const size = localStorage.getItem('chart-size') ?? 'medium'
|
||||
|
||||
const height = this.querySelector('#chart-container').style.height.replace('px', '')
|
||||
let bar = this.querySelector('#progress-bar-container')
|
||||
|
||||
var actulaButtonHeight = 0
|
||||
var actualButtonWidth = 0
|
||||
|
||||
var viewportHeight = +buttonHeight
|
||||
var viewportWidth = +buttonWidth
|
||||
|
||||
$(chartButtonContainer).each(function () {
|
||||
this.style.setProperty('height', `${viewportHeight}px`, 'important')
|
||||
this.style.setProperty('width', `${viewportWidth}px`, 'important')
|
||||
})
|
||||
var table = this.querySelector('#item-table')
|
||||
var totalRowCount = table.rows.length // 5
|
||||
var tableHeight = totalRowCount * 35
|
||||
var workArea = actulaButtonHeight
|
||||
if (size == 'large') {
|
||||
workArea = viewportHeight - tableHeight
|
||||
}
|
||||
|
||||
const prompt = e.detail.prompt
|
||||
if (prompt && prompt.currentlyExecuting) {
|
||||
bar.style.display = 'block'
|
||||
workArea = workArea - 30
|
||||
} else {
|
||||
bar.style.display = 'none'
|
||||
}
|
||||
|
||||
if (savedChart == 'bar') {
|
||||
$(chartContainer).each(function () {
|
||||
this.style.setProperty('height', `${workArea * 0.95}px`, 'important')
|
||||
})
|
||||
} else {
|
||||
$(chartContainer).each(function () {
|
||||
this.style.setProperty('height', `${workArea * 0.87}px`, 'important')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onProgressUpdate(e) {
|
||||
// this.adjustButtonHeight(e)
|
||||
var _a, _b, _c, _d
|
||||
if (!this.connected) return
|
||||
const prompt = e.detail.prompt
|
||||
|
||||
this.currentPromptExecution = prompt
|
||||
|
||||
// Default progress to 0 if no totalNodes
|
||||
let progressPercentage = 0
|
||||
|
||||
if (prompt === null || prompt === void 0 ? void 0 : prompt.errorDetails) {
|
||||
let progressText = `${
|
||||
(_a = prompt.errorDetails) === null || _a === void 0 ? void 0 : _a.exception_type
|
||||
} ${((_b = prompt.errorDetails) === null || _b === void 0 ? void 0 : _b.node_id) || ''} ${
|
||||
((_c = prompt.errorDetails) === null || _c === void 0 ? void 0 : _c.node_type) || ''
|
||||
}`
|
||||
console.log(progressText)
|
||||
// Set the progress bar to 0% or some error state if needed
|
||||
this.querySelector('#progress-bar').style.width = '0%'
|
||||
return
|
||||
}
|
||||
if (prompt === null || prompt === void 0 ? void 0 : prompt.currentlyExecuting) {
|
||||
const current = prompt === null || prompt === void 0 ? void 0 : prompt.currentlyExecuting
|
||||
|
||||
let progressText = `(${e.detail.queue}) `
|
||||
if (!prompt.totalNodes) {
|
||||
progressText += `??%`
|
||||
} else {
|
||||
progressPercentage = (prompt.executedNodeIds.length / prompt.totalNodes) * 100
|
||||
progressText += `${Math.round(progressPercentage)}%`
|
||||
}
|
||||
|
||||
let nodeLabel = (_d = current.nodeLabel) === null || _d === void 0 ? void 0 : _d.trim()
|
||||
const monitor = document.querySelector('elegant-resource-monitor')
|
||||
// monitor.addListItem(nodeLabel)
|
||||
|
||||
let stepsLabel = ''
|
||||
if (current.step != null && current.maxSteps) {
|
||||
const percent = (current.step / current.maxSteps) * 100
|
||||
if (current.pass > 1 || current.maxPasses != null) {
|
||||
stepsLabel += `#${current.pass}`
|
||||
if (current.maxPasses && current.maxPasses > 0) {
|
||||
stepsLabel += `/${current.maxPasses}`
|
||||
}
|
||||
stepsLabel += ` - `
|
||||
}
|
||||
stepsLabel += `${Math.round(percent)}%`
|
||||
}
|
||||
|
||||
if (nodeLabel || stepsLabel) {
|
||||
progressText += ` - ${nodeLabel || '???'}${stepsLabel ? ` (${stepsLabel})` : ''}`
|
||||
}
|
||||
console.log(progressText)
|
||||
} else {
|
||||
if (e === null || e === void 0 ? void 0 : e.detail.queue) {
|
||||
console.log(`(${e.detail.queue}) Running... in another tab`)
|
||||
} else {
|
||||
console.log('Idle')
|
||||
}
|
||||
}
|
||||
|
||||
// Update the progress bar width
|
||||
this.querySelector('#progress-bar').style.width = `${Math.round(progressPercentage)}%`
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
if (!this.connected) {
|
||||
console.log('Adding event listener to MONITOR_SERVICE')
|
||||
// MONITOR_SERVICE.addEventListener(
|
||||
// 'elegant-resource-monitor-update',
|
||||
// this.onProgressUpdateBound,
|
||||
// )
|
||||
|
||||
this.render()
|
||||
this.addEventListeners()
|
||||
|
||||
this.connected = true
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.connected = false
|
||||
// MONITOR_SERVICE.removeEventListener(
|
||||
// 'elegant-resource-monitor-update',
|
||||
// this.onProgressUpdateBound,
|
||||
// )
|
||||
}
|
||||
}
|
||||
|
||||
// Register the custom element
|
||||
customElements.define('elegant-resource-monitor', ElegantResourceMonitor)
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,689 @@
|
|||
dragElement(document.getElementById('chart-button-container'))
|
||||
|
||||
var wasDragged = false
|
||||
function dragElement(elmnt) {
|
||||
var isDragging = false
|
||||
var pos1 = 0,
|
||||
pos2 = 0,
|
||||
pos3 = 0,
|
||||
pos4 = 0
|
||||
// otherwise, move the DIV from anywhere inside the DIV:
|
||||
elmnt.onmousedown = dragMouseDown
|
||||
function dragMouseDown(e) {
|
||||
e = e || window.event
|
||||
e.preventDefault()
|
||||
// get the mouse cursor position at startup:
|
||||
pos3 = e.clientX
|
||||
pos4 = e.clientY
|
||||
document.onmouseup = closeDragElement
|
||||
// call a function whenever the cursor moves:
|
||||
document.onmousemove = elementDrag
|
||||
elmnt.style.cursor = 'grabbing'
|
||||
}
|
||||
|
||||
function elementDrag(e) {
|
||||
e = e || window.event
|
||||
e.preventDefault()
|
||||
// calculate the new cursor position:
|
||||
pos1 = pos3 - e.clientX
|
||||
pos2 = pos4 - e.clientY
|
||||
pos3 = e.clientX
|
||||
pos4 = e.clientY
|
||||
// set the element's new position:
|
||||
wasDragged = true
|
||||
isDragging = true
|
||||
elmnt.style.top = elmnt.offsetTop - pos2 + 'px'
|
||||
elmnt.style.left = elmnt.offsetLeft - pos1 + 'px'
|
||||
}
|
||||
|
||||
function closeDragElement() {
|
||||
// stop moving when mouse button is released:
|
||||
document.onmouseup = null
|
||||
document.onmousemove = null
|
||||
elmnt.style.transition = 'transform .7s ease-in-out'
|
||||
elmnt.style.cursor = 'grab'
|
||||
setTimeout(() => {
|
||||
if (!isDragging) {
|
||||
elmnt.style.cursor = 'auto'
|
||||
}
|
||||
}, 1000)
|
||||
isDragging = false
|
||||
getNearestPosition()
|
||||
}
|
||||
}
|
||||
|
||||
function moveButtonToCenter(duration = 300) {
|
||||
const { buttonHeight, buttonWidth } = getButtonSize()
|
||||
// Get button dimensions and viewport dimensions
|
||||
const widgetWidth = buttonWidth
|
||||
const widgetHeight = buttonHeight
|
||||
const viewportWidth = window.innerWidth
|
||||
const viewportHeight = window.innerHeight
|
||||
|
||||
// Calculate center of the viewport
|
||||
const windowCenterX = viewportWidth / 2
|
||||
const windowCenterY = viewportHeight / 2
|
||||
|
||||
// Calculate button center
|
||||
const buttonCenterX = widgetWidth / 2
|
||||
const buttonCenterY = widgetHeight / 2
|
||||
|
||||
// Calculate the translation offsets needed to center the button
|
||||
const posx = windowCenterX - buttonCenterX
|
||||
const posy = windowCenterY - buttonCenterY
|
||||
|
||||
goToPosition({ x: posx, y: posy })
|
||||
}
|
||||
|
||||
// Call the function to move the button
|
||||
|
||||
// HELPER FUNCTIONS //
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
window.barChart = async function () {
|
||||
checkForUpdates('active-chart', 'bar')
|
||||
await updateChartSize()
|
||||
}
|
||||
|
||||
window.lineChart = async function () {
|
||||
checkForUpdates('active-chart', 'line')
|
||||
await updateChartSize()
|
||||
}
|
||||
|
||||
window.smallChart = async function () {
|
||||
checkForUpdates('chart-size', 'small')
|
||||
await updateChartSize()
|
||||
}
|
||||
|
||||
window.mediumChart = async function () {
|
||||
checkForUpdates('chart-size', 'medium')
|
||||
await updateChartSize()
|
||||
}
|
||||
|
||||
window.largeChart = async function () {
|
||||
setTimeout(async () => {
|
||||
checkForUpdates('perf-monitor-position', 'center')
|
||||
checkForUpdates('chart-size', 'large')
|
||||
await updateChartSize()
|
||||
}, 50)
|
||||
}
|
||||
|
||||
function moveToCenter() {
|
||||
if (localStorage.getItem('perf-monitor-position') === 'center') {
|
||||
moveButtonToCenter(150)
|
||||
}
|
||||
}
|
||||
function checkForUpdates(key, value) {
|
||||
var previous = localStorage.getItem(key)
|
||||
var updated = previous != value
|
||||
localStorage.setItem('hasUpdates', updated)
|
||||
localStorage.setItem(key, value)
|
||||
}
|
||||
|
||||
function isWindowOutsideWorkingArea() {
|
||||
const { buttonHeight, buttonWidth } = getButtonSize()
|
||||
|
||||
// Get display bounds
|
||||
const { displayBounds } = getDisplayAndWindowBounds()
|
||||
|
||||
const widget = document.getElementById('chart-button-container')
|
||||
const rect = widget.getBoundingClientRect()
|
||||
const currentTop = rect.top + window.scrollY
|
||||
const currentLeft = rect.left + window.scrollX
|
||||
|
||||
const windowLeft = currentLeft
|
||||
const windowTop = currentTop
|
||||
const windowRight = windowLeft + buttonWidth
|
||||
const windowBottom = windowTop + buttonHeight
|
||||
|
||||
const displayLeft = 0
|
||||
const displayTop = 0
|
||||
const displayRight = displayLeft + displayBounds.width
|
||||
const displayBottom = displayTop + displayBounds.height
|
||||
let isOutside =
|
||||
windowLeft < displayLeft ||
|
||||
windowTop < displayTop ||
|
||||
windowRight > displayRight ||
|
||||
windowBottom > displayBottom
|
||||
|
||||
if (isOutside) {
|
||||
console.log('The window is outside the working area.')
|
||||
} else {
|
||||
console.log('The window is within the working area.')
|
||||
}
|
||||
|
||||
return isOutside
|
||||
}
|
||||
|
||||
function getSizes() {
|
||||
const savedChart = localStorage.getItem('active-chart') ?? 'bar'
|
||||
var sizes = {}
|
||||
if (savedChart == 'bar') {
|
||||
sizes = {
|
||||
small: { height: '120', width: '150' },
|
||||
medium: { height: '300', width: '410' },
|
||||
large: { height: '450', width: '700' },
|
||||
}
|
||||
} else {
|
||||
sizes = {
|
||||
small: { height: '110', width: '160' },
|
||||
medium: { height: '245', width: '425' },
|
||||
large: { height: '380', width: '700' },
|
||||
}
|
||||
}
|
||||
return sizes
|
||||
}
|
||||
|
||||
// SETTINGS MENU //
|
||||
// POSITIONS BUTTONS
|
||||
document.querySelectorAll('.position-clickable').forEach((button) => {
|
||||
button.addEventListener('click', async function () {
|
||||
const position = this.id
|
||||
wasDragged = false
|
||||
|
||||
localStorage.setItem('perf-monitor-position', position)
|
||||
|
||||
//the position we should be going to
|
||||
const pos = getCoordinates(false)
|
||||
if (pos) {
|
||||
goToPosition(pos)
|
||||
} else {
|
||||
console.error('Invalid position:', pos)
|
||||
}
|
||||
|
||||
// Optionally hide the settings menu and adjust UI
|
||||
const settingsMenu = document.getElementById('settingsMenu')
|
||||
settingsMenu.classList.remove('show') // Hide the menu if visible
|
||||
|
||||
document.querySelectorAll('.chart-row').forEach((row) => {
|
||||
row.classList.remove('no-drag')
|
||||
row.classList.add('drag')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Show or hide the settings menu when the settings icon is clicked
|
||||
document.getElementById('popupTrigger').addEventListener('click', function (event) {
|
||||
const settingsMenu = document.getElementById('settingsMenu')
|
||||
settingsMenu.classList.toggle('show') // Toggle the 'show' class for animation
|
||||
|
||||
document.querySelectorAll('.chart-row').forEach((row) => {
|
||||
row.classList.add('no-drag')
|
||||
row.classList.remove('drag')
|
||||
})
|
||||
document.querySelectorAll('canvas').forEach((row) => {
|
||||
row.classList.add('no-drag')
|
||||
row.classList.remove('drag')
|
||||
})
|
||||
setTimeout(() => {
|
||||
const settingsMenuHr = document.getElementById('settings-hr')
|
||||
settingsMenuHr.classList.add('show') // Toggle the 'show' class for animation
|
||||
}, 300)
|
||||
|
||||
event.stopPropagation()
|
||||
})
|
||||
|
||||
// Hide the settings menu when clicking outside
|
||||
window.addEventListener('click', function (e) {
|
||||
if (e.target.className.includes('settings')) {
|
||||
return
|
||||
}
|
||||
|
||||
const settingsMenu = document.getElementById('settingsMenu')
|
||||
const trigger = document.getElementById('popupTrigger')
|
||||
if (!settingsMenu.contains(e.target) && e.target !== trigger) {
|
||||
settingsMenu.classList.remove('show') // Hide the menu if clicking outside
|
||||
}
|
||||
document.querySelectorAll('canvas').forEach((row) => {
|
||||
row.classList.remove('no-drag')
|
||||
row.classList.add('drag')
|
||||
})
|
||||
document.querySelectorAll('.chart-row').forEach((row) => {
|
||||
row.classList.remove('no-drag')
|
||||
row.classList.add('drag')
|
||||
})
|
||||
})
|
||||
|
||||
// Calculate if the menu will overflow the bottom of the viewport
|
||||
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'
|
||||
menu.style.top = `29px`
|
||||
}
|
||||
let topPosition = buttonRect.bottom
|
||||
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`
|
||||
}
|
||||
})
|
||||
|
||||
function goToPosition(pos) {
|
||||
const widget = document.getElementById('chart-button-container')
|
||||
|
||||
// Set transition for smooth animation
|
||||
// widget.style.transition = 'transform .7s ease-in-out'
|
||||
widget.style.transition = `top .4s ease, left .4s ease`
|
||||
|
||||
const currentTop = +widget.style.top.replace('px', '')
|
||||
const currentLeft = +widget.style.left.replace('px', '')
|
||||
|
||||
// Target position
|
||||
|
||||
const offsetX = pos.x - currentLeft
|
||||
const offsetY = pos.y - currentTop
|
||||
|
||||
// Set transition duration and easing
|
||||
widget.style.transition = `transform .7s ease-in-out`
|
||||
|
||||
// Animate to the center
|
||||
widget.style.transform = `translate(${offsetX}px, ${offsetY}px)`
|
||||
}
|
||||
// MAIN METHODS //
|
||||
|
||||
function getDisplayAndWindowBounds() {
|
||||
const availWidth = window.screen.availWidth
|
||||
const availHeight = window.screen.availHeight
|
||||
|
||||
// Assume work area starts at (0, 0)
|
||||
// Work area dimensions approximate available screen area minus some margins
|
||||
const workArea = {
|
||||
x: 0, // Typically starts at (0, 0)
|
||||
y: 0, // Typically starts at (0, 0)
|
||||
width: availWidth,
|
||||
height: availHeight,
|
||||
}
|
||||
|
||||
const displayBounds = {
|
||||
width: window.screen.width,
|
||||
height: window.screen.height,
|
||||
availableWidth: window.screen.availWidth,
|
||||
availableHeight: window.screen.availHeight,
|
||||
workArea,
|
||||
}
|
||||
|
||||
const windowBounds = {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
}
|
||||
|
||||
return { displayBounds, windowBounds }
|
||||
}
|
||||
|
||||
function getPositions() {
|
||||
const { buttonHeight, buttonWidth } = getButtonSize()
|
||||
|
||||
// Get button dimensions and viewport dimensions
|
||||
const widgetWidth = buttonWidth
|
||||
const widgetHeight = buttonHeight
|
||||
const viewportWidth = window.innerWidth
|
||||
const viewportHeight = window.innerHeight
|
||||
|
||||
// Calculate center of the viewport
|
||||
const windowCenterX = viewportWidth / 2
|
||||
const windowCenterY = viewportHeight / 2
|
||||
|
||||
// Calculate button center
|
||||
const buttonCenterX = widgetWidth / 2
|
||||
const buttonCenterY = widgetHeight / 2
|
||||
|
||||
// Calculate the translation offsets needed to center the button
|
||||
|
||||
// Define positions based on work area
|
||||
const positions = {
|
||||
'bottom-right': {
|
||||
x: viewportWidth - widgetWidth - 10,
|
||||
y: viewportHeight - widgetHeight - 10,
|
||||
},
|
||||
'bottom-left': {
|
||||
x: 10,
|
||||
y: viewportHeight - widgetHeight - 10,
|
||||
},
|
||||
'bottom-center': {
|
||||
x: (viewportWidth - widgetWidth) / 2,
|
||||
y: viewportHeight - widgetHeight - 10,
|
||||
},
|
||||
'top-right': {
|
||||
x: viewportWidth - widgetWidth - 10,
|
||||
y: 10,
|
||||
},
|
||||
'top-left': { x: 10, y: 10 },
|
||||
'top-center': {
|
||||
x: (viewportWidth - widgetWidth) / 2,
|
||||
y: 10,
|
||||
},
|
||||
'left-center': {
|
||||
x: 10,
|
||||
y: windowCenterY - buttonCenterY,
|
||||
},
|
||||
'right-center': {
|
||||
x: viewportWidth - widgetWidth - 10,
|
||||
y: windowCenterY - buttonCenterY,
|
||||
},
|
||||
center: {
|
||||
x: (viewportWidth - widgetWidth) / 2,
|
||||
y: windowCenterY - buttonCenterY,
|
||||
},
|
||||
}
|
||||
return positions
|
||||
}
|
||||
|
||||
function getCoordinates(isOutside) {
|
||||
var position = localStorage.getItem('perf-monitor-position')
|
||||
|
||||
if (isOutside) {
|
||||
var outsidePosition = getNearestPosition()
|
||||
return outsidePosition
|
||||
}
|
||||
|
||||
const positions = getPositions()
|
||||
const pos = positions[position]
|
||||
return pos
|
||||
}
|
||||
|
||||
function getNearestPosition() {
|
||||
const { buttonHeight, buttonWidth } = getButtonSize()
|
||||
const widget = document.getElementById('chart-button-container')
|
||||
const viewportWidth = window.innerWidth
|
||||
const viewportHeight = window.innerHeight
|
||||
// Get display bounds
|
||||
const { displayBounds } = getDisplayAndWindowBounds()
|
||||
// Define positions based on work area
|
||||
const positions = getPositions()
|
||||
|
||||
// Get current window position
|
||||
const currentX = $(widget).offset().left
|
||||
let currentY = $(widget).offset().top
|
||||
const windowCenter = {
|
||||
x: $(widget).offset().left,
|
||||
y: $(widget).offset().top,
|
||||
}
|
||||
const workAreaCenter = {
|
||||
x: viewportWidth / 2,
|
||||
y: viewportHeight / 2,
|
||||
}
|
||||
const distanceToCenter = {
|
||||
x: Math.abs(workAreaCenter.x - windowCenter.x),
|
||||
y: Math.abs(workAreaCenter.y - windowCenter.y),
|
||||
}
|
||||
var threshold = 100 // Define a threshold to determine proximity
|
||||
const size = localStorage.getItem('chart-size') ?? 'medium'
|
||||
switch (size) {
|
||||
case 'small':
|
||||
threshold = 250
|
||||
|
||||
break
|
||||
case 'medium':
|
||||
threshold = 200
|
||||
default:
|
||||
threshold = 150
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
var nearestPosition = ''
|
||||
|
||||
if (distanceToCenter.x < threshold && distanceToCenter.y < threshold) {
|
||||
nearestPosition = 'center'
|
||||
} else {
|
||||
// Function to calculate distance
|
||||
function calculateDistance(x1, y1, x2, y2) {
|
||||
return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
|
||||
}
|
||||
|
||||
// Find the nearest position
|
||||
let minDistance = Infinity
|
||||
|
||||
for (const [key, pos] of Object.entries(positions)) {
|
||||
// Adjust for edge cases
|
||||
const adjustedPosX = Math.max(
|
||||
displayBounds.workArea.x,
|
||||
Math.min(pos.x, displayBounds.width - buttonWidth),
|
||||
)
|
||||
const adjustedPosY = Math.max(
|
||||
displayBounds.workArea.y,
|
||||
Math.min(pos.y, displayBounds.height - buttonHeight),
|
||||
)
|
||||
|
||||
const distance = calculateDistance(currentX, currentY, adjustedPosX, adjustedPosY)
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance
|
||||
nearestPosition = key
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Output or use the nearest position
|
||||
console.log('Nearest position:', nearestPosition)
|
||||
// Set the position
|
||||
const pos = positions[nearestPosition]
|
||||
localStorage.setItem('perf-monitor-position', nearestPosition)
|
||||
|
||||
return pos
|
||||
}
|
||||
function getButtonSize() {
|
||||
const size = localStorage.getItem('chart-size') ?? 'medium'
|
||||
const sizes = getSizes()
|
||||
const sizeStyles = sizes[size]
|
||||
var buttonHeight = +sizeStyles.height + 25
|
||||
const buttonWidth = +sizeStyles.width + 25
|
||||
var table = document.getElementById('item-table')
|
||||
var totalRowCount = table.rows.length // 5
|
||||
var tableHeight = totalRowCount * 30
|
||||
if (size == 'large') {
|
||||
buttonHeight = +buttonHeight + tableHeight
|
||||
}
|
||||
|
||||
return { buttonHeight, buttonWidth }
|
||||
}
|
||||
async function updateChartSize() {
|
||||
var table = document.getElementById('item-table')
|
||||
table.style.display = 'none'
|
||||
|
||||
const settingsMenu = document.getElementById('settingsMenu')
|
||||
settingsMenu.classList.remove('show') // Hide the menu if visible
|
||||
$('#chart-wrapper').fadeOut()
|
||||
document.querySelectorAll('.chart-row').forEach((row) => {
|
||||
row.classList.remove('no-drag')
|
||||
row.classList.add('drag')
|
||||
})
|
||||
|
||||
document.querySelectorAll('canvas').forEach((row) => {
|
||||
row.classList.remove('no-drag')
|
||||
row.classList.add('drag')
|
||||
})
|
||||
|
||||
const size = localStorage.getItem('chart-size') ?? 'medium'
|
||||
const chartContainer = document.getElementById('chart-container')
|
||||
const savedChart = localStorage.getItem('active-chart') ?? 'bar'
|
||||
|
||||
chartContainer.classList.remove('small', 'medium', 'large', 'bar', 'line')
|
||||
chartContainer.classList.add(size)
|
||||
chartContainer.classList.add(savedChart)
|
||||
|
||||
const { buttonHeight, buttonWidth } = getButtonSize()
|
||||
const chartButtonContainer = document.getElementById('chart-button-container')
|
||||
|
||||
var actulaButtonHeight = 0
|
||||
var actualButtonWidth = 0
|
||||
|
||||
var viewportHeight = +buttonHeight
|
||||
var viewportWidth = +buttonWidth
|
||||
|
||||
$(chartButtonContainer).each(function () {
|
||||
this.style.setProperty('height', `${viewportHeight}px`, 'important')
|
||||
this.style.setProperty('width', `${viewportWidth}px`, 'important')
|
||||
})
|
||||
|
||||
var position = localStorage.getItem('perf-monitor-position')
|
||||
if (position === 'center') {
|
||||
moveToCenter()
|
||||
} else {
|
||||
const isOutside = isWindowOutsideWorkingArea()
|
||||
const pos = getCoordinates(isOutside)
|
||||
if (pos && isOutside && wasDragged) {
|
||||
goToPosition(pos)
|
||||
} else if (pos && !wasDragged) {
|
||||
goToPosition(pos)
|
||||
} else {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
var sizeClasses = ['small', 'medium', 'large']
|
||||
|
||||
const chartButton = document.getElementById('chart-button')
|
||||
chartButton.classList.add(size)
|
||||
sizeClasses.forEach((prop) => {
|
||||
if (prop != size) {
|
||||
setTimeout(() => {
|
||||
chartButton.classList.remove(prop)
|
||||
}, 500)
|
||||
}
|
||||
})
|
||||
|
||||
switch (size) {
|
||||
case 'small':
|
||||
actulaButtonHeight = viewportHeight * 0.83
|
||||
actualButtonWidth = viewportWidth * 0.83
|
||||
break
|
||||
case 'medium':
|
||||
actulaButtonHeight = viewportHeight * 0.93
|
||||
actualButtonWidth = viewportWidth * 0.93
|
||||
break
|
||||
default:
|
||||
actulaButtonHeight = viewportHeight * 0.96
|
||||
actualButtonWidth = viewportWidth * 0.96
|
||||
break
|
||||
}
|
||||
|
||||
const bottom = `12.5px`
|
||||
const right = `12.5px`
|
||||
|
||||
$(chartButton).each(function () {
|
||||
this.style.setProperty('bottom', bottom, 'important')
|
||||
this.style.setProperty('right', right, 'important')
|
||||
|
||||
if (size === 'large') {
|
||||
this.style.setProperty('background-color', ` #000000d6`, 'important')
|
||||
} else {
|
||||
this.style.setProperty('background-color', ` #00000096`, 'important')
|
||||
}
|
||||
})
|
||||
var totalRowCount = table.rows.length // 5
|
||||
var tableHeight = totalRowCount * 30
|
||||
var workArea = actulaButtonHeight
|
||||
if (size == 'large') {
|
||||
workArea = viewportHeight - tableHeight
|
||||
}
|
||||
|
||||
const hasUpdates = localStorage.getItem('hasUpdates') ?? 'false'
|
||||
if (hasUpdates === 'true') {
|
||||
if (savedChart == 'bar') {
|
||||
$(chartContainer).each(function () {
|
||||
this.style.setProperty('height', `${workArea * 0.95}px`, 'important')
|
||||
})
|
||||
|
||||
initializeBarChart()
|
||||
} else {
|
||||
$(chartContainer).each(function () {
|
||||
this.style.setProperty('height', `${workArea * 0.87}px`, 'important')
|
||||
})
|
||||
setTimeout(() => {
|
||||
initializeLineChart()
|
||||
}, 500)
|
||||
}
|
||||
} else {
|
||||
$('#chart-wrapper').fadeIn()
|
||||
}
|
||||
|
||||
localStorage.setItem('hasUpdates', 'false')
|
||||
|
||||
const active = `#chart-button.top-left.active`
|
||||
var positionStyles = {
|
||||
bottom: bottom,
|
||||
right: right,
|
||||
}
|
||||
var lastClass = {
|
||||
key: active,
|
||||
values: [positionStyles],
|
||||
}
|
||||
var lastClassString = JSON.stringify(lastClass)
|
||||
localStorage.setItem('lastClass', lastClassString)
|
||||
}
|
||||
|
||||
const pos = getCoordinates(false)
|
||||
goToPosition(pos)
|
||||
var appIsLoaded = false
|
||||
var shouldShowPerfMonitor = false
|
||||
if (JSON.parse(localStorage.getItem('shouldShowPerfMonitor')) ?? false) {
|
||||
setTimeout(() => {
|
||||
// showPerfMonitor()
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
window.showPerfMonitor = async function () {
|
||||
shouldShowPerfMonitor = !shouldShowPerfMonitor
|
||||
localStorage.setItem('shouldShowPerfMonitor', shouldShowPerfMonitor)
|
||||
const chartButton = document.getElementById('chart-button')
|
||||
const chartWrapper = document.getElementById('chart-wrapper')
|
||||
const chartButtonContainer = document.getElementById('chart-button-container')
|
||||
const resourceMonitorLink = document.getElementById('show_resource_monitor')
|
||||
|
||||
if (shouldShowPerfMonitor === true) {
|
||||
$(chartButtonContainer).toggleClass('active')
|
||||
|
||||
localStorage.setItem('hasUpdates', 'true')
|
||||
await updateChartSize()
|
||||
$(resourceMonitorLink).fadeOut(500)
|
||||
appIsLoaded = true
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
$(chartButtonContainer).toggleClass('active')
|
||||
$(chartButtonContainer).each(function () {
|
||||
this.style.setProperty('height', `0px`, 'important')
|
||||
})
|
||||
}, 500)
|
||||
chartButton.classList.remove('small', 'medium', 'large')
|
||||
$(chartWrapper).fadeOut()
|
||||
$(resourceMonitorLink).fadeIn(500)
|
||||
}
|
||||
|
||||
$(chartButton).toggleClass('active')
|
||||
}
|
||||
|
||||
// when the close button is clicked
|
||||
document.getElementById('close-button').addEventListener('click', function () {
|
||||
document.getElementById('settingsMenu').classList.remove('show') // Hide the menu
|
||||
document.querySelectorAll('.chart-row').forEach((row) => {
|
||||
row.classList.remove('no-drag')
|
||||
row.classList.add('drag')
|
||||
})
|
||||
showPerfMonitor()
|
||||
})
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
export function getResolver(timeout = 5000) {
|
||||
const resolver = {}
|
||||
resolver.id = generateId(8)
|
||||
resolver.completed = false
|
||||
resolver.resolved = false
|
||||
resolver.rejected = false
|
||||
resolver.promise = new Promise((resolve, reject) => {
|
||||
resolver.reject = () => {
|
||||
resolver.completed = true
|
||||
resolver.rejected = true
|
||||
reject()
|
||||
}
|
||||
resolver.resolve = (data) => {
|
||||
resolver.completed = true
|
||||
resolver.resolved = true
|
||||
resolve(data)
|
||||
}
|
||||
})
|
||||
resolver.timeout = setTimeout(() => {
|
||||
if (!resolver.completed) {
|
||||
resolver.reject()
|
||||
}
|
||||
}, timeout)
|
||||
return resolver
|
||||
}
|
||||
const DEBOUNCE_FN_TO_PROMISE = new WeakMap()
|
||||
export function debounce(fn, ms = 64) {
|
||||
if (!DEBOUNCE_FN_TO_PROMISE.get(fn)) {
|
||||
DEBOUNCE_FN_TO_PROMISE.set(
|
||||
fn,
|
||||
wait(ms).then(() => {
|
||||
DEBOUNCE_FN_TO_PROMISE.delete(fn)
|
||||
fn()
|
||||
}),
|
||||
)
|
||||
}
|
||||
return DEBOUNCE_FN_TO_PROMISE.get(fn)
|
||||
}
|
||||
export function wait(ms = 16) {
|
||||
if (ms === 16) {
|
||||
return new Promise((resolve) => {
|
||||
requestAnimationFrame(() => {
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve()
|
||||
}, ms)
|
||||
})
|
||||
}
|
||||
function dec2hex(dec) {
|
||||
return dec.toString(16).padStart(2, '0')
|
||||
}
|
||||
export function generateId(length) {
|
||||
const arr = new Uint8Array(length / 2)
|
||||
crypto.getRandomValues(arr)
|
||||
return Array.from(arr, dec2hex).join('')
|
||||
}
|
||||
export function getObjectValue(obj, objKey, def) {
|
||||
if (!obj || !objKey) return def
|
||||
const keys = objKey.split('.')
|
||||
const key = keys.shift()
|
||||
const found = obj[key]
|
||||
if (keys.length) {
|
||||
return getObjectValue(found, keys.join('.'), def)
|
||||
}
|
||||
return found
|
||||
}
|
||||
export function setObjectValue(obj, objKey, value, createMissingObjects = true) {
|
||||
if (!obj || !objKey) return obj
|
||||
const keys = objKey.split('.')
|
||||
const key = keys.shift()
|
||||
if (obj[key] === undefined) {
|
||||
if (!createMissingObjects) {
|
||||
return
|
||||
}
|
||||
obj[key] = {}
|
||||
}
|
||||
if (!keys.length) {
|
||||
obj[key] = value
|
||||
} else {
|
||||
if (typeof obj[key] != 'object') {
|
||||
obj[key] = {}
|
||||
}
|
||||
setObjectValue(obj[key], keys.join('.'), value, createMissingObjects)
|
||||
}
|
||||
return obj
|
||||
}
|
||||
export function moveArrayItem(arr, itemOrFrom, to) {
|
||||
const from = typeof itemOrFrom === 'number' ? itemOrFrom : arr.indexOf(itemOrFrom)
|
||||
arr.splice(to, 0, arr.splice(from, 1)[0])
|
||||
}
|
||||
export function removeArrayItem(arr, itemOrIndex) {
|
||||
const index = typeof itemOrIndex === 'number' ? itemOrIndex : arr.indexOf(itemOrIndex)
|
||||
arr.splice(index, 1)
|
||||
}
|
||||
export function injectCss(href) {
|
||||
if (document.querySelector(`link[href^="${href}"]`)) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
const link = document.createElement('link')
|
||||
link.setAttribute('rel', 'stylesheet')
|
||||
link.setAttribute('type', 'text/css')
|
||||
const timeout = setTimeout(resolve, 1000)
|
||||
link.addEventListener('load', (e) => {
|
||||
clearInterval(timeout)
|
||||
resolve()
|
||||
})
|
||||
link.href = href
|
||||
document.head.appendChild(link)
|
||||
})
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,61 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>System Monitor</title>
|
||||
<script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
}
|
||||
.data-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.data-item {
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Real-Time System Monitor</h1>
|
||||
<div class="data-container" id="data-container">
|
||||
<div class="data-item" id="cpu">CPU: Loading...</div>
|
||||
<div class="data-item" id="ram">RAM: Loading...</div>
|
||||
<div class="data-item" id="gpu">GPU: Loading...</div>
|
||||
<div class="data-item" id="vram">VRAM: Loading...</div>
|
||||
<div class="data-item" id="hdd">HDD: Loading...</div>
|
||||
<div class="data-item" id="temp">Temperature: Loading...</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const socket = io('http://localhost:5000')
|
||||
|
||||
// Listen for 'data_update' event and update the data on the page
|
||||
socket.on('data_update', (data) => {
|
||||
document.getElementById('cpu').textContent = `CPU: ${data.cpu.toFixed(2)}%`
|
||||
document.getElementById('ram').textContent = `RAM: ${data.ram.toFixed(2)}%`
|
||||
document.getElementById('gpu').textContent = `GPU: ${data.gpu.toFixed(2)}%`
|
||||
document.getElementById('vram').textContent = `VRAM: ${data.vram.toFixed(2)}%`
|
||||
document.getElementById('hdd').textContent = `HDD: ${data.hdd.toFixed(2)}%`
|
||||
document.getElementById('temp').textContent = `Temperature: ${data.temp.toFixed(2)}°C`
|
||||
})
|
||||
|
||||
socket.on('connect', () => {
|
||||
console.log('Connected to the server')
|
||||
})
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
console.log('Disconnected from the server')
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<div id="perf-monitor-container"></div>
|
||||
<img
|
||||
src="/file=web/assets/img/clearfix.png"
|
||||
onload="{
|
||||
var script = document.createElement('script');
|
||||
script.src = '/file=web/assets/js/dependencies.js';
|
||||
document.body.appendChild(script);
|
||||
}"
|
||||
/>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<elegant-resource-monitor></elegant-resource-monitor>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue