feat: native disable/enable fallback and package whitelist; bump to 1.8.0
Publish to Comfy registry / Publish Custom Node to registry (push) Waiting to run
Publish to Comfy registry / Publish Custom Node to registry (push) Waiting to run
Disable/enable no longer require ComfyUI Manager: - New pack_fs.py moves packs in/out of custom_nodes/.disabled/ (no import, delete, or re-clone). Fallback for hand-cloned packs, loose single-file nodes, or when Manager is absent. enable strips the @version suffix so packs restore as clean, importable dir names. - Routes: native-disable, native-enable, disabled-packs. - Frontend routes each disable per-pack (Manager queue vs native move), and shows an Enable button on recoverable packs in the Uninstalled tier. The restart banner degrades to a manual-restart notice when no Manager exists. Whitelist (packages-only): a star toggle protects a pack — pulled into its own pinned group, no Disable button, skipped by the 7-day trial auto-disable. - New whitelist_packages table; whitelisted flag on package stats. - Routes: whitelist, whitelist/add, whitelist/remove. Tests: test_pack_fs.py, test_whitelist.py (60 passing). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+95
@@ -7,6 +7,7 @@ from server import PromptServer
|
||||
|
||||
from .mapper import NodePackageMapper, ModelMapper
|
||||
from .node_introspect import find_disabled_pack_path, get_node_schema
|
||||
from .pack_fs import disable_pack_native, enable_pack_native, list_disabled_packs
|
||||
from .tracker import UsageTracker
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -144,6 +145,100 @@ async def get_node_schema_route(request):
|
||||
return web.json_response({"error": "internal error"}, status=500)
|
||||
|
||||
|
||||
@routes.post("/nodes-stats/native-disable")
|
||||
async def native_disable(request):
|
||||
"""Disable a pack by moving it into custom_nodes/.disabled/ (no Manager).
|
||||
|
||||
Fallback for packs ComfyUI Manager doesn't manage. A restart is required for
|
||||
ComfyUI to unload the pack.
|
||||
"""
|
||||
try:
|
||||
data = await request.json()
|
||||
package = data.get("package")
|
||||
if not package:
|
||||
return web.json_response({"error": "package required"}, status=400)
|
||||
|
||||
ok, message = await asyncio.get_event_loop().run_in_executor(
|
||||
None, disable_pack_native, package
|
||||
)
|
||||
if not ok:
|
||||
return web.json_response({"status": "error", "message": message}, status=409)
|
||||
mapper.invalidate()
|
||||
return web.json_response({"status": "ok", "message": message})
|
||||
except Exception:
|
||||
logger.error("nodes-stats: error in native disable", exc_info=True)
|
||||
return web.json_response({"error": "internal error"}, status=500)
|
||||
|
||||
|
||||
@routes.post("/nodes-stats/native-enable")
|
||||
async def native_enable(request):
|
||||
"""Re-enable a pack by moving it out of custom_nodes/.disabled/ (no Manager)."""
|
||||
try:
|
||||
data = await request.json()
|
||||
package = data.get("package")
|
||||
if not package:
|
||||
return web.json_response({"error": "package required"}, status=400)
|
||||
|
||||
ok, message = await asyncio.get_event_loop().run_in_executor(
|
||||
None, enable_pack_native, package
|
||||
)
|
||||
if not ok:
|
||||
return web.json_response({"status": "error", "message": message}, status=409)
|
||||
mapper.invalidate()
|
||||
return web.json_response({"status": "ok", "message": message})
|
||||
except Exception:
|
||||
logger.error("nodes-stats: error in native enable", exc_info=True)
|
||||
return web.json_response({"error": "internal error"}, status=500)
|
||||
|
||||
|
||||
@routes.get("/nodes-stats/disabled-packs")
|
||||
async def get_disabled_packs(request):
|
||||
"""List packs present in custom_nodes/.disabled/ (re-enable candidates)."""
|
||||
try:
|
||||
names = await asyncio.get_event_loop().run_in_executor(None, list_disabled_packs)
|
||||
return web.json_response(sorted(names))
|
||||
except Exception:
|
||||
logger.error("nodes-stats: error listing disabled packs", exc_info=True)
|
||||
return web.json_response({"error": "internal error"}, status=500)
|
||||
|
||||
|
||||
@routes.get("/nodes-stats/whitelist")
|
||||
async def get_whitelist(request):
|
||||
try:
|
||||
return web.json_response(sorted(tracker.get_whitelist()))
|
||||
except Exception:
|
||||
logger.error("nodes-stats: error getting whitelist", exc_info=True)
|
||||
return web.json_response({"error": "internal error"}, status=500)
|
||||
|
||||
|
||||
@routes.post("/nodes-stats/whitelist/add")
|
||||
async def whitelist_add(request):
|
||||
try:
|
||||
data = await request.json()
|
||||
package = data.get("package")
|
||||
if not package:
|
||||
return web.json_response({"error": "package required"}, status=400)
|
||||
tracker.add_to_whitelist(package)
|
||||
return web.json_response({"status": "ok"})
|
||||
except Exception:
|
||||
logger.error("nodes-stats: error adding to whitelist", exc_info=True)
|
||||
return web.json_response({"error": "internal error"}, status=500)
|
||||
|
||||
|
||||
@routes.post("/nodes-stats/whitelist/remove")
|
||||
async def whitelist_remove(request):
|
||||
try:
|
||||
data = await request.json()
|
||||
package = data.get("package")
|
||||
if not package:
|
||||
return web.json_response({"error": "package required"}, status=400)
|
||||
tracker.remove_from_whitelist(package)
|
||||
return web.json_response({"status": "ok"})
|
||||
except Exception:
|
||||
logger.error("nodes-stats: error removing from whitelist", exc_info=True)
|
||||
return web.json_response({"error": "internal error"}, status=500)
|
||||
|
||||
|
||||
@routes.get("/nodes-stats/trials")
|
||||
async def get_trials(request):
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user