fix: keep disabled unused packages manageable
This commit is contained in:
@@ -67,27 +67,31 @@ Click the **Node Stats** button (bar chart icon) in the ComfyUI top menu bar. A
|
||||
- Summary bar with counts for each classification tier
|
||||
- Sections for each tier, sorted from most actionable to least
|
||||
- Expandable rows — click any package to see per-node execution counts and timestamps
|
||||
- **Disable** buttons on the "Safe to Remove" and "Consider Removing" tiers (see below)
|
||||
- **Disable** buttons on the "Safe to Remove", "Consider Removing", and "Recently Unused" tiers (see below)
|
||||
|
||||
### Disabling unused packages
|
||||
|
||||
When [ComfyUI Manager](https://github.com/ltdrdata/ComfyUI-Manager) is installed, the
|
||||
"Safe to Remove" and "Consider Removing" sections show a **Disable** button on each
|
||||
package, plus a **Disable all** button per section. Disabling:
|
||||
The "Safe to Remove", "Consider Removing", and "Recently Unused" sections show
|
||||
a **Disable** button on each package, plus a **Disable all** button per section.
|
||||
The latter is useful for a package you installed but never used. Disabling:
|
||||
|
||||
- Hands off to ComfyUI Manager, which moves the package into `custom_nodes/.disabled/`
|
||||
- Is fully reversible — re-enable any package from ComfyUI Manager whenever you like
|
||||
- Uses ComfyUI Manager when it manages the package, otherwise moves it directly
|
||||
into `custom_nodes/.disabled/`
|
||||
- Is fully reversible — the *Uninstalled* section offers **Enable** for every
|
||||
package found in `.disabled/`, even if it was never run
|
||||
- Requires a ComfyUI restart to unload the package from the running session (a banner
|
||||
with a **Restart ComfyUI** button appears after disabling)
|
||||
|
||||
If ComfyUI Manager is not installed, the disable buttons are hidden and stats work as before.
|
||||
ComfyUI Manager is optional for these actions; packages it does not manage are
|
||||
disabled and re-enabled directly by Node Stats.
|
||||
|
||||
> **Manager compatibility:** works with both the standalone
|
||||
> [ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager) custom node and
|
||||
> ComfyUI core's built-in manager. For the built-in manager, launch ComfyUI with
|
||||
> `--enable-manager-legacy-ui` (the extension detects the active manager and its
|
||||
> API automatically). Without a reachable manager, all enable/disable/install
|
||||
> actions are simply omitted.
|
||||
> API automatically). Without a reachable manager, Node Stats still supports
|
||||
> direct enable/disable; Manager-only workflow install and trial actions are
|
||||
> unavailable.
|
||||
|
||||
### Workflow tab & temporary enable
|
||||
|
||||
|
||||
+7
-1
@@ -78,7 +78,13 @@ routes = PromptServer.instance.routes
|
||||
@routes.get("/nodes-stats/packages")
|
||||
async def get_package_stats(request):
|
||||
try:
|
||||
stats = tracker.get_package_stats(mapper)
|
||||
# Include entries parked in custom_nodes/.disabled even when they were
|
||||
# never executed. They are otherwise absent from both the DB and the
|
||||
# live node mapper, leaving no way to re-enable them in the UI.
|
||||
disabled = await asyncio.get_event_loop().run_in_executor(
|
||||
None, list_disabled_packs
|
||||
)
|
||||
stats = tracker.get_package_stats(mapper, disabled)
|
||||
return web.json_response(stats)
|
||||
except Exception:
|
||||
logger.error("nodes-stats: error getting package stats", exc_info=True)
|
||||
|
||||
+4
-2
@@ -21,8 +21,10 @@ const STATUS_META = {
|
||||
uninstalled: { label: "uninstalled", color: "#555", bg: "#1a1a1a", hover: "#252525", summaryBg: "#1a1a1a", summaryText: "#888" },
|
||||
};
|
||||
|
||||
// Tiers that may offer a "Disable" action (when ComfyUI Manager is available).
|
||||
const DISABLEABLE_TIERS = new Set(["safe_to_remove", "consider_removing"]);
|
||||
// Tiers that may offer a "Disable" action. A package never used during the
|
||||
// first month is safe to disable manually too; waiting a month would otherwise
|
||||
// strand common one-off installs such as VAE Decode PlusPlus.
|
||||
const DISABLEABLE_TIERS = new Set(["safe_to_remove", "consider_removing", "unused_new"]);
|
||||
|
||||
// Setting id for the auto-open-on-load behavior. Defaults to off so loading a
|
||||
// workflow with missing/disabled nodes no longer pops the dialog every time.
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
from tracker import UsageTracker
|
||||
|
||||
|
||||
class _Mapper:
|
||||
def __init__(self, mapping=()):
|
||||
self.mapping = dict(mapping)
|
||||
|
||||
def get_package(self, class_type):
|
||||
return self.mapping.get(class_type, "__unknown__")
|
||||
|
||||
def get_all_packages(self):
|
||||
return set(self.mapping.values()) - {"__builtin__"}
|
||||
|
||||
|
||||
def test_never_used_disabled_package_stays_visible_for_enable(tmp_path):
|
||||
tracker = UsageTracker(db_path=str(tmp_path / "test.db"))
|
||||
|
||||
stats = tracker.get_package_stats(
|
||||
_Mapper(), disabled_packages={"VAE-Decode-PlusPlus"}
|
||||
)
|
||||
|
||||
assert stats == [{
|
||||
"package": "VAE-Decode-PlusPlus",
|
||||
"total_executions": 0,
|
||||
"used_nodes": 0,
|
||||
"nodes": [],
|
||||
"last_seen": None,
|
||||
"total_nodes": 0,
|
||||
"installed": False,
|
||||
"status": "uninstalled",
|
||||
"whitelisted": False,
|
||||
}]
|
||||
|
||||
|
||||
def test_disabled_entry_does_not_override_an_active_package(tmp_path):
|
||||
tracker = UsageTracker(db_path=str(tmp_path / "test.db"))
|
||||
|
||||
stats = tracker.get_package_stats(
|
||||
_Mapper({"Node": "VAE-Decode-PlusPlus"}),
|
||||
disabled_packages={"vae-decode-plusplus"},
|
||||
)
|
||||
|
||||
assert len(stats) == 1
|
||||
assert stats[0]["package"] == "VAE-Decode-PlusPlus"
|
||||
assert stats[0]["installed"] is True
|
||||
+31
-5
@@ -217,8 +217,14 @@ class UsageTracker:
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def get_package_stats(self, mapper):
|
||||
"""Aggregate per-package stats combining DB data with known nodes."""
|
||||
def get_package_stats(self, mapper, disabled_packages=()):
|
||||
"""Aggregate package stats from usage, active nodes, and disabled packs.
|
||||
|
||||
``disabled_packages`` is the set of directory names currently parked in
|
||||
``custom_nodes/.disabled``. Those packages are no longer loaded, so
|
||||
they cannot appear in the node mapper. Including them here keeps a
|
||||
never-used package visible long enough for the UI to offer Enable.
|
||||
"""
|
||||
node_stats = self.get_node_stats()
|
||||
|
||||
# Build per-package data from DB
|
||||
@@ -263,13 +269,33 @@ class UsageTracker:
|
||||
}
|
||||
packages[pkg]["total_nodes"] = total
|
||||
|
||||
# Packages only in DB (not in mapper) are uninstalled/disabled
|
||||
# Packages only in DB (not in mapper) are uninstalled/disabled.
|
||||
# Also add disabled-on-disk packages that have no recorded executions;
|
||||
# without this, a package disabled before first use disappears from the
|
||||
# UI and cannot be re-enabled there.
|
||||
active_package_keys = {pkg.lower() for pkg in node_counts}
|
||||
existing_package_keys = {pkg.lower() for pkg in packages}
|
||||
for pkg in disabled_packages:
|
||||
if not isinstance(pkg, str) or not pkg or pkg.lower() in active_package_keys:
|
||||
continue
|
||||
if pkg.lower() in existing_package_keys:
|
||||
continue
|
||||
packages[pkg] = {
|
||||
"package": pkg,
|
||||
"total_executions": 0,
|
||||
"used_nodes": 0,
|
||||
"nodes": [],
|
||||
"last_seen": None,
|
||||
"total_nodes": 0,
|
||||
}
|
||||
existing_package_keys.add(pkg.lower())
|
||||
|
||||
# node_counts already includes all packages from mapper + get_all_packages()
|
||||
installed_packages = set(node_counts.keys())
|
||||
installed_packages = active_package_keys
|
||||
for pkg, entry in packages.items():
|
||||
if "total_nodes" not in entry:
|
||||
entry["total_nodes"] = entry["used_nodes"]
|
||||
entry["installed"] = pkg in installed_packages
|
||||
entry["installed"] = pkg.lower() in installed_packages
|
||||
|
||||
# Classify packages by usage recency
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
Reference in New Issue
Block a user