Dr.Lt.Data 6 months ago
parent
commit
1c313ee822
  1. 34
      README.md
  2. 20
      glob/manager_core.py
  3. 97
      glob/manager_server.py
  4. 11
      js/cm-api.js
  5. 45
      js/comfyui-manager.js
  6. 59
      js/common.js
  7. 5
      js/model-downloader.js
  8. 29
      js/snapshot.js

34
README.md

@ -5,6 +5,7 @@
![menu](misc/menu.jpg)
## NOTICE
* V2.33 Security policy is applied.
* V2.21 [cm-cli](docs/en/cm-cli.md) tool is added.
* V2.18 to V2.18.3 is not functioning due to a severe bug. Users on these versions are advised to promptly update to V2.18.4. Please navigate to the `ComfyUI/custom_nodes/ComfyUI-Manager` directory and execute `git pull` to update.
* You can see whole nodes info on [ComfyUI Nodes Info](https://ltdrdata.github.io/) page.
@ -337,13 +338,32 @@ When you run the `scan.sh` script:
* Currently, `vid2vid` is not being updated, causing compatibility issues.
* If you encounter the error message `Overlapped Object has pending operation at deallocation on Comfyui Manager load` under Windows
* Edit `config.ini` file: add `windows_selector_event_loop_policy = True`
* How to disable unsecure features
* Edit `config.ini` file: add `disable_unsecure_features = True`
* If you disable unsecure features these features won't work
* install/uninstall/fix custom nodes
* restore/remove snapshot
* pip install
* reboot
## Security policy
* Edit `config.ini` file: add `security_level = <LEVEL>`
* `strong`
* doesn't allow `high` and `middle` level risky feature
* `normal`
* doesn't allow `high` level risky feature if `--listen` is specified and not starts with `127.`
* `middle` level risky feature is available
* `weak`
* all feature is available
* `high` level risky features
* `Install via git url`, `pip install`
* Installation of custom nodes registered not in the `default channel`.
* Display terminal log
* `middle` level risky features
* Uninstall/Update/Fix custom nodes
* Installation of custom nodes registered in the `default channel`.
* Restore/Remove Snapshot
* Restart
* `low` level risky features
* Update ComfyUI
## TODO: Unconventional form of custom node list

20
glob/manager_core.py

@ -23,7 +23,7 @@ sys.path.append(glob_path)
import cm_global
from manager_util import *
version = [2, 32, 7]
version = [2, 33]
version_str = f"V{version[0]}.{version[1]}" + (f'.{version[2]}' if len(version) > 2 else '')
comfyui_manager_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
@ -205,7 +205,7 @@ def write_config():
'windows_selector_event_loop_policy': get_config()['windows_selector_event_loop_policy'],
'model_download_by_agent': get_config()['model_download_by_agent'],
'downgrade_blacklist': get_config()['downgrade_blacklist'],
'disable_unsecure_features': get_config()['disable_unsecure_features'],
'security_level': get_config()['security_level'],
}
with open(config_path, 'w') as configfile:
config.write(configfile)
@ -217,6 +217,15 @@ def read_config():
config.read(config_path)
default_conf = config['default']
# policy migration: disable_unsecure_features -> security_level
if 'disable_unsecure_features' in default_conf:
if default_conf['disable_unsecure_features'].lower() == 'true':
security_level = 'strong'
else:
security_level = 'normal'
else:
security_level = default_conf['security_level'] if 'security_level' in default_conf else 'normal'
return {
'preview_method': default_conf['preview_method'] if 'preview_method' in default_conf else manager_funcs.get_current_preview_method(),
'badge_mode': default_conf['badge_mode'] if 'badge_mode' in default_conf else 'none',
@ -231,7 +240,7 @@ def read_config():
'windows_selector_event_loop_policy': default_conf['windows_selector_event_loop_policy'].lower() == 'true' if 'windows_selector_event_loop_policy' in default_conf else False,
'model_download_by_agent': default_conf['model_download_by_agent'].lower() == 'true' if 'model_download_by_agent' in default_conf else False,
'downgrade_blacklist': default_conf['downgrade_blacklist'] if 'downgrade_blacklist' in default_conf else '',
'disable_unsecure_features': default_conf['disable_unsecure_features'].lower() == 'true' if 'disable_unsecure_features' in default_conf else False,
'security_level': security_level
}
except Exception:
@ -249,7 +258,7 @@ def read_config():
'windows_selector_event_loop_policy': False,
'model_download_by_agent': False,
'downgrade_blacklist': '',
'disable_unsecure_features': False,
'security_level': 'normal',
}
@ -1190,6 +1199,3 @@ def unzip(model_path):
os.remove(model_path)
return True
def is_unsecure_features_disabled():
return get_config()['disable_unsecure_features']

97
glob/manager_server.py

@ -42,6 +42,36 @@ from comfy.cli_args import args
import latent_preview
is_local_mode = args.listen.startswith('127.')
def is_allowed_security_level(level):
if level == 'high':
if is_local_mode:
return core.get_config()['security_level'].lower() in ['weak', 'normal']
else:
return core.get_config()['security_level'].lower() == 'weak'
elif level == 'middle':
return core.get_config()['security_level'].lower() in ['weak', 'normal']
else:
return True
async def get_risky_level(files):
json_data1 = await core.get_data_by_mode('local', 'custom-node-list.json')
json_data2 = await core.get_data_by_mode('cache', 'custom-node-list.json', channel_url='https://github.com/ltdrdata/ComfyUI-Manager/raw/main/custom-node-list.json')
all_urls = set()
for x in json_data1['custom_nodes'] + json_data2['custom_nodes']:
all_urls.update(x['files'])
for x in files:
if x not in all_urls:
return "high"
return "middle"
class ManagerFuncsInComfyUI(core.ManagerFuncs):
def get_current_preview_method(self):
if args.preview_method == latent_preview.LatentPreviewMethod.Auto:
@ -358,6 +388,10 @@ async def fetch_updates(request):
@PromptServer.instance.routes.get("/customnode/update_all")
async def update_all(request):
if not is_allowed_security_level('middle'):
print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.")
return web.Response(status=403)
try:
core.save_snapshot_with_postfix('autosave')
@ -551,9 +585,9 @@ async def get_snapshot_list(request):
@PromptServer.instance.routes.get("/snapshot/remove")
async def remove_snapshot(request):
if core.is_unsecure_features_disabled():
print(f"ERROR: The unsecure feature is disabled, restricting the remove feature. Please contact the administrator.")
return web.Response(status=400)
if not is_allowed_security_level('middle'):
print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.")
return web.Response(status=403)
try:
target = request.rel_url.query["target"]
@ -569,9 +603,9 @@ async def remove_snapshot(request):
@PromptServer.instance.routes.get("/snapshot/restore")
async def remove_snapshot(request):
if core.is_unsecure_features_disabled():
print(f"ERROR: The unsecure feature is disabled, restricting the restore feature. Please contact the administrator.")
return web.Response(status=400)
if not is_allowed_security_level('middle'):
print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.")
return web.Response(status=403)
try:
target = request.rel_url.query["target"]
@ -737,12 +771,17 @@ def copy_set_active(files, is_disable, js_path_name='.'):
@PromptServer.instance.routes.post("/customnode/install")
async def install_custom_node(request):
if core.is_unsecure_features_disabled():
print(f"ERROR: The unsecure feature is disabled, restricting the installation of custom nodes. Please contact the administrator.")
return web.Response(status=400)
if not is_allowed_security_level('middle'):
print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.")
return web.Response(status=403)
json_data = await request.json()
risky_level = await get_risky_level(json_data['files'])
if not is_allowed_security_level(risky_level):
print(f"ERROR: This installation is not allowed in this security_level. Please contact the administrator.")
return web.Response(status=404)
install_type = json_data['install_type']
print(f"Install custom node '{json_data['title']}'")
@ -779,9 +818,9 @@ async def install_custom_node(request):
@PromptServer.instance.routes.post("/customnode/fix")
async def fix_custom_node(request):
if core.is_unsecure_features_disabled():
print(f"ERROR: The unsecure feature is disabled, restricting the fix feature. Please contact the administrator.")
return web.Response(status=400)
if not is_allowed_security_level('middle'):
print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.")
return web.Response(status=403)
json_data = await request.json()
@ -813,9 +852,9 @@ async def fix_custom_node(request):
@PromptServer.instance.routes.post("/customnode/install/git_url")
async def install_custom_node_git_url(request):
if core.is_unsecure_features_disabled():
print(f"ERROR: The unsecure feature is disabled, restricting the installation of custom nodes. Please contact the administrator.")
return web.Response(status=400)
if not is_allowed_security_level('high'):
print(f"ERROR: To use this feature, you must set '--listen' to a local IP and set the security level to 'middle' or 'weak'. Please contact the administrator.")
return web.Response(status=403)
url = await request.text()
res = core.gitclone_install([url])
@ -829,9 +868,9 @@ async def install_custom_node_git_url(request):
@PromptServer.instance.routes.post("/customnode/install/pip")
async def install_custom_node_git_url(request):
if core.is_unsecure_features_disabled():
print(f"ERROR: The unsecure feature is disabled, restricting the installation of pip package. Please contact the administrator.")
return web.Response(status=400)
if not is_allowed_security_level('high'):
print(f"ERROR: To use this feature, you must set '--listen' to a local IP and set the security level to 'middle' or 'weak'. Please contact the administrator.")
return web.Response(status=403)
packages = await request.text()
core.pip_install(packages.split(' '))
@ -841,9 +880,9 @@ async def install_custom_node_git_url(request):
@PromptServer.instance.routes.post("/customnode/uninstall")
async def uninstall_custom_node(request):
if core.is_unsecure_features_disabled():
print(f"ERROR: The unsecure feature is disabled, restricting the uninstallation of custom nodes. Please contact the administrator.")
return web.Response(status=400)
if not is_allowed_security_level('middle'):
print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.")
return web.Response(status=403)
json_data = await request.json()
@ -869,6 +908,10 @@ async def uninstall_custom_node(request):
@PromptServer.instance.routes.post("/customnode/update")
async def update_custom_node(request):
if not is_allowed_security_level('middle'):
print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.")
return web.Response(status=403)
json_data = await request.json()
install_type = json_data['install_type']
@ -983,9 +1026,9 @@ manager_terminal_hook = ManagerTerminalHook()
@PromptServer.instance.routes.get("/manager/terminal")
async def terminal_mode(request):
if core.is_unsecure_features_disabled():
print(f"ERROR: The unsecure feature is disabled, restricting the terminal feature. Please contact the administrator.")
return web.Response(status=400)
if not is_allowed_security_level('high'):
print(f"ERROR: To use this action, a security_level of `weak` is required. Please contact the administrator.")
return web.Response(status=403)
if "mode" in request.rel_url.query:
if request.rel_url.query['mode'] == 'true':
@ -1110,9 +1153,9 @@ async def get_notice(request):
@PromptServer.instance.routes.get("/manager/reboot")
def restart(self):
if core.is_unsecure_features_disabled():
print(f"ERROR: The unsecure feature is disabled, restricting the reboot feature. Please contact the administrator.")
return web.Response(status=400)
if not is_allowed_security_level('middle'):
print(f"ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.")
return web.Response(status=403)
try:
sys.stdout.close_log()

11
js/cm-api.js

@ -41,9 +41,18 @@ async function tryInstallCustomNode(event) {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(event.detail.target)
});
if(response.status == 403) {
show_message('This action is not allowed with this security level configuration.');
return false;
}
}
api.fetchApi("/manager/reboot");
let response = await api.fetchApi("/manager/reboot");
if(response.status == 403) {
show_message('This action is not allowed with this security level configuration.');
return false;
}
await sleep(300);

45
js/comfyui-manager.js vendored

@ -15,7 +15,7 @@ import { CustomNodesInstaller } from "./custom-nodes-downloader.js";
import { AlternativesInstaller } from "./a1111-alter-downloader.js";
import { SnapshotManager } from "./snapshot.js";
import { ModelInstaller } from "./model-downloader.js";
import { manager_instance, setManagerInstance, install_via_git_url, install_pip, rebootAPI, free_models } from "./common.js";
import { manager_instance, setManagerInstance, install_via_git_url, install_pip, rebootAPI, free_models, show_message } from "./common.js";
import { ComponentBuilderDialog, load_components, set_component_policy, getPureName } from "./components-manager.js";
import { set_double_click_policy } from "./node_fixer.js";
@ -520,25 +520,21 @@ async function updateComfyUI() {
const response = await api.fetchApi('/comfyui_manager/update_comfyui');
if (response.status == 400) {
app.ui.dialog.show('Failed to update ComfyUI.');
app.ui.dialog.element.style.zIndex = 10010;
show_message('Failed to update ComfyUI.');
return false;
}
if (response.status == 201) {
app.ui.dialog.show('ComfyUI has been successfully updated.');
app.ui.dialog.element.style.zIndex = 10010;
show_message('ComfyUI has been successfully updated.');
}
else {
app.ui.dialog.show('ComfyUI is already up to date with the latest version.');
app.ui.dialog.element.style.zIndex = 10010;
show_message('ComfyUI is already up to date with the latest version.');
}
return true;
}
catch (exception) {
app.ui.dialog.show(`Failed to update ComfyUI / ${exception}`);
app.ui.dialog.element.style.zIndex = 10010;
show_message(`Failed to update ComfyUI / ${exception}`);
return false;
}
finally {
@ -560,13 +556,12 @@ async function fetchUpdates(update_check_checkbox) {
const response = await api.fetchApi(`/customnode/fetch_updates?mode=${mode}`);
if (response.status != 200 && response.status != 201) {
app.ui.dialog.show('Failed to fetch updates.');
app.ui.dialog.element.style.zIndex = 10010;
show_message('Failed to fetch updates.');
return false;
}
if (response.status == 201) {
app.ui.dialog.show("There is an updated extension available.<BR><BR><P><B>NOTE:<BR>Fetch Updates is not an update.<BR>Please update from <button id='cm-install-customnodes-button'>Install Custom Nodes</button> </B></P>");
show_message("There is an updated extension available.<BR><BR><P><B>NOTE:<BR>Fetch Updates is not an update.<BR>Please update from <button id='cm-install-customnodes-button'>Install Custom Nodes</button> </B></P>");
const button = document.getElementById('cm-install-customnodes-button');
button.addEventListener("click",
@ -580,19 +575,16 @@ async function fetchUpdates(update_check_checkbox) {
}
);
app.ui.dialog.element.style.zIndex = 10010;
update_check_checkbox.checked = false;
}
else {
app.ui.dialog.show('All extensions are already up-to-date with the latest versions.');
app.ui.dialog.element.style.zIndex = 10010;
show_message('All extensions are already up-to-date with the latest versions.');
}
return true;
}
catch (exception) {
app.ui.dialog.show(`Failed to update custom nodes / ${exception}`);
app.ui.dialog.element.style.zIndex = 10010;
show_message(`Failed to update custom nodes / ${exception}`);
return false;
}
finally {
@ -615,11 +607,16 @@ async function updateAll(update_check_checkbox, manager_dialog) {
const response1 = await api.fetchApi('/comfyui_manager/update_comfyui');
const response2 = await api.fetchApi(`/customnode/update_all?mode=${mode}`);
if (response2.status == 403) {
show_message('This action is not allowed with this security level configuration.');
return false;
}
if (response1.status == 400 || response2.status == 400) {
app.ui.dialog.show('Failed to update ComfyUI or several extensions.<BR><BR>See terminal log.<BR>');
app.ui.dialog.element.style.zIndex = 10010;
show_message('Failed to update ComfyUI or several extensions.<BR><BR>See terminal log.<BR>');
return false;
}
if(response1.status == 201 || response2.status == 201) {
const update_info = await response2.json();
@ -633,7 +630,7 @@ async function updateAll(update_check_checkbox, manager_dialog) {
updated_list = "<BR>UPDATED: "+update_info.updated.join(", ");
}
app.ui.dialog.show(
show_message(
"ComfyUI and all extensions have been updated to the latest version.<BR>To apply the updated custom node, please <button class='cm-small-button' id='cm-reboot-button5'>RESTART</button> ComfyUI. And refresh browser.<BR>"
+failed_list
+updated_list
@ -646,19 +643,15 @@ async function updateAll(update_check_checkbox, manager_dialog) {
manager_dialog.close();
}
});
app.ui.dialog.element.style.zIndex = 10010;
}
else {
app.ui.dialog.show('ComfyUI and all extensions are already up-to-date with the latest versions.');
app.ui.dialog.element.style.zIndex = 10010;
show_message('ComfyUI and all extensions are already up-to-date with the latest versions.');
}
return true;
}
catch (exception) {
app.ui.dialog.show(`Failed to update ComfyUI or several extensions / ${exception}`);
app.ui.dialog.element.style.zIndex = 10010;
show_message(`Failed to update ComfyUI or several extensions / ${exception}`);
return false;
}
finally {

59
js/common.js

@ -1,6 +1,11 @@
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
export function show_message(msg) {
app.ui.dialog.show(msg);
app.ui.dialog.element.style.zIndex = 10010;
}
export async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
@ -47,6 +52,20 @@ export async function install_checked_custom_node(grid_rows, target_i, caller, m
body: JSON.stringify(target)
});
if(response.status == 403) {
show_message('This action is not allowed with this security level configuration.');
caller.updateMessage('');
await caller.invalidateControl();
return;
}
if(response.status == 404) {
show_message('With the current security level configuration, only custom nodes from the <B>"default channel"</B> can be installed.');
caller.updateMessage('');
await caller.invalidateControl();
return;
}
if(response.status == 400) {
show_message(`${mode} failed: ${target.title}`);
continue;
@ -94,19 +113,21 @@ export async function install_pip(packages) {
body: packages,
});
if(res.status == 403) {
show_message('This action is not allowed with this security level configuration.');
return;
}
if(res.status == 200) {
app.ui.dialog.show(`PIP package installation is processed.<br>To apply the pip packages, please click the <button id='cm-reboot-button3'><font size='3px'>RESTART</font></button> button in ComfyUI.`);
show_message(`PIP package installation is processed.<br>To apply the pip packages, please click the <button id='cm-reboot-button3'><font size='3px'>RESTART</font></button> button in ComfyUI.`);
const rebootButton = document.getElementById('cm-reboot-button3');
const self = this;
rebootButton.addEventListener("click", rebootAPI);
app.ui.dialog.element.style.zIndex = 10010;
}
else {
app.ui.dialog.show(`Failed to install '${packages}'<BR>See terminal log.`);
app.ui.dialog.element.style.zIndex = 10010;
show_message(`Failed to install '${packages}'<BR>See terminal log.`);
}
}
@ -116,21 +137,24 @@ export async function install_via_git_url(url, manager_dialog) {
}
if(!isValidURL(url)) {
app.ui.dialog.show(`Invalid Git url '${url}'`);
app.ui.dialog.element.style.zIndex = 10010;
show_message(`Invalid Git url '${url}'`);
return;
}
app.ui.dialog.show(`Wait...<BR><BR>Installing '${url}'`);
app.ui.dialog.element.style.zIndex = 10010;
show_message(`Wait...<BR><BR>Installing '${url}'`);
const res = await api.fetchApi("/customnode/install/git_url", {
method: "POST",
body: url,
});
if(res.status == 403) {
show_message('This action is not allowed with this security level configuration.');
return;
}
if(res.status == 200) {
app.ui.dialog.show(`'${url}' is installed<BR>To apply the installed custom node, please <button id='cm-reboot-button4'><font size='3px'>RESTART</font></button> ComfyUI.`);
show_message(`'${url}' is installed<BR>To apply the installed custom node, please <button id='cm-reboot-button4'><font size='3px'>RESTART</font></button> ComfyUI.`);
const rebootButton = document.getElementById('cm-reboot-button4');
const self = this;
@ -141,12 +165,9 @@ export async function install_via_git_url(url, manager_dialog) {
manager_dialog.close();
}
});
app.ui.dialog.element.style.zIndex = 10010;
}
else {
app.ui.dialog.show(`Failed to install '${url}'<BR>See terminal log.`);
app.ui.dialog.element.style.zIndex = 10010;
show_message(`Failed to install '${url}'<BR>See terminal log.`);
}
}
@ -158,15 +179,9 @@ export async function free_models() {
});
if(res.status == 200) {
app.ui.dialog.show('Models have been unloaded.')
show_message('Models have been unloaded.')
}
else {
app.ui.dialog.show('Unloading of models failed.<BR><BR>Installed ComfyUI may be an outdated version.')
}
app.ui.dialog.element.style.zIndex = 10010;
show_message('Unloading of models failed.<BR><BR>Installed ComfyUI may be an outdated version.')
}
export function show_message(msg) {
app.ui.dialog.show(msg);
app.ui.dialog.element.style.zIndex = 10010;
}

5
js/model-downloader.js

@ -1,7 +1,7 @@
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js"
import { ComfyDialog, $el } from "../../scripts/ui.js";
import { install_checked_custom_node, manager_instance, rebootAPI } from "./common.js";
import { install_checked_custom_node, manager_instance, rebootAPI, show_message } from "./common.js";
async function install_model(target) {
if(ModelInstaller.instance) {
@ -20,8 +20,7 @@ async function install_model(target) {
return true;
}
catch(exception) {
app.ui.dialog.show(`Install failed: ${target.title} / ${exception}`);
app.ui.dialog.element.style.zIndex = 10010;
show_message(`Install failed: ${target.title} / ${exception}`);
return false;
}
finally {

29
js/snapshot.js

@ -1,24 +1,28 @@
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js"
import { ComfyDialog, $el } from "../../scripts/ui.js";
import { manager_instance, rebootAPI } from "./common.js";
import { manager_instance, rebootAPI, show_message } from "./common.js";
async function restore_snapshot(target) {
if(SnapshotManager.instance) {
try {
const response = await api.fetchApi(`/snapshot/restore?target=${target}`, { cache: "no-store" });
if(response.status == 403) {
show_message('This action is not allowed with this security level configuration.');
return false;
}
if(response.status == 400) {
app.ui.dialog.show(`Restore snapshot failed: ${target.title} / ${exception}`);
app.ui.dialog.element.style.zIndex = 10010;
show_message(`Restore snapshot failed: ${target.title} / ${exception}`);
}
app.ui.dialog.close();
return true;
}
catch(exception) {
app.ui.dialog.show(`Restore snapshot failed: ${target.title} / ${exception}`);
app.ui.dialog.element.style.zIndex = 10010;
show_message(`Restore snapshot failed: ${target.title} / ${exception}`);
return false;
}
finally {
@ -32,17 +36,21 @@ async function remove_snapshot(target) {
if(SnapshotManager.instance) {
try {
const response = await api.fetchApi(`/snapshot/remove?target=${target}`, { cache: "no-store" });
if(response.status == 403) {
show_message('This action is not allowed with this security level configuration.');
return false;
}
if(response.status == 400) {
app.ui.dialog.show(`Remove snapshot failed: ${target.title} / ${exception}`);
app.ui.dialog.element.style.zIndex = 10010;
show_message(`Remove snapshot failed: ${target.title} / ${exception}`);
}
app.ui.dialog.close();
return true;
}
catch(exception) {
app.ui.dialog.show(`Restore snapshot failed: ${target.title} / ${exception}`);
app.ui.dialog.element.style.zIndex = 10010;
show_message(`Restore snapshot failed: ${target.title} / ${exception}`);
return false;
}
finally {
@ -58,8 +66,7 @@ async function save_current_snapshot() {
return true;
}
catch(exception) {
app.ui.dialog.show(`Backup snapshot failed: ${exception}`);
app.ui.dialog.element.style.zIndex = 10010;
show_message(`Backup snapshot failed: ${exception}`);
return false;
}
finally {

Loading…
Cancel
Save