From 472ce00dc4eab6c66cd9656dc5b8f2c16204cd40 Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Sun, 21 Jun 2026 11:27:33 +0200 Subject: [PATCH] fix: build disable payload from getlist state, not commit hash The disable action failed for git/nightly-installed packs (e.g. masquerade-nodes-comfyui): /customnode/installed reports a git commit hash as the version and the cnr_id as id, but ComfyUI Manager's disable endpoint needs the install *state version* ("nightly"/semver/"unknown") and the directory name as id. Switch fetchManagerInfo to /customnode/getlist (the unified list Manager's own UI uses), key by directory name, and build the payload as {id: dirname, version: state, files?: repo}. Eligibility/reconciliation now key off pack state instead of an enabled flag. Co-Authored-By: Claude Opus 4.8 --- js/nodes_stats.js | 56 +++++++++++++++++++++++++++++------------------ pyproject.toml | 2 +- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/js/nodes_stats.js b/js/nodes_stats.js index 9f9d0a7..2276b18 100644 --- a/js/nodes_stats.js +++ b/js/nodes_stats.js @@ -257,11 +257,11 @@ function renderSection(title, subtitle, status, packages, managerInfo) { } // A package can be disabled only if ComfyUI Manager knows it (by directory -// name) and it is currently enabled (active on disk). +// name) and it is currently active (any state other than already-disabled). function isDisableEligible(pkg, managerInfo) { if (!managerInfo || !pkg.installed) return false; const info = managerInfo[pkg.package]; - return !!(info && info.enabled); + return !!(info && info.state && info.state !== "disabled"); } function buildModelsTabContent(modelData) { @@ -380,29 +380,43 @@ function buildTable(packages, status, withActions, managerInfo) { // ComfyUI Manager integration: disable unused node packages // --------------------------------------------------------------------------- -// Map of installed packages from ComfyUI Manager: -// { : { ver, cnr_id, aux_id, enabled }, ... } -// Returns null when the Manager is not installed/reachable, in which case the -// disable UI is omitted entirely. +// Map of installed packages from ComfyUI Manager, keyed by directory name: +// { : { id, version, files, state }, ... } +// We read the unified list (/customnode/getlist) rather than /customnode/installed +// because only the unified list reports the install *state version* the disable +// endpoint needs: "nightly" for git installs, the semver for registry installs, +// or "unknown". (/customnode/installed returns a raw git commit hash instead, +// which the disable endpoint rejects.) This mirrors what Manager's own UI sends. +// Returns null when the Manager is not installed/reachable, so the disable UI is +// omitted entirely. async function fetchManagerInfo() { try { - const resp = await fetch("/customnode/installed"); + const resp = await fetch("/customnode/getlist?mode=local&skip_update=true"); if (!resp.ok) return null; const data = await resp.json(); - return data && typeof data === "object" ? data : null; + const packs = data && data.node_packs; + if (!packs || typeof packs !== "object") return null; + const info = {}; + for (const [key, v] of Object.entries(packs)) { + if (!v || v.state === "not-installed") continue; + // For installed packs the key is the directory name — matches our package names. + info[key] = { id: v.id || key, version: v.version, files: v.files, state: v.state }; + } + return info; } catch { return null; } } -// Build the payload ComfyUI Manager's /manager/queue/disable expects. CNR -// (registry) packages are keyed by their cnr_id; everything else is treated as -// "unknown" and keyed by directory name. +// Build the payload ComfyUI Manager's /manager/queue/disable expects, mirroring +// Manager's own frontend: id = directory name, version = install state +// ("nightly" / semver / "unknown"), and files (repo URL) only for "unknown". function disablePayload(dirName, info) { - if (info && info.cnr_id && info.ver && info.ver !== "unknown") { - return { id: info.cnr_id, version: info.ver, ui_id: dirName }; + const payload = { id: info.id || dirName, version: info.version, ui_id: dirName }; + if (info.version === "unknown") { + payload.files = info.files && info.files.length ? info.files : [dirName]; } - return { id: dirName, version: "unknown", files: [dirName], ui_id: dirName }; + return payload; } function wireDisableButtons(dialog, managerInfo) { @@ -426,9 +440,9 @@ function wireDisableButtons(dialog, managerInfo) { } async function handleDisable(pkgNames, dialog, managerInfo) { - // Only act on packages Manager still reports as enabled (guards against + // Only act on packages Manager still reports as active (guards against // double-clicks and stale buttons after a partial batch). - pkgNames = pkgNames.filter((n) => managerInfo[n] && managerInfo[n].enabled); + pkgNames = pkgNames.filter((n) => managerInfo[n] && managerInfo[n].state !== "disabled"); if (pkgNames.length === 0) return; const what = pkgNames.length === 1 ? `"${pkgNames[0]}"` : `${pkgNames.length} packages`; @@ -451,13 +465,13 @@ async function handleDisable(pkgNames, dialog, managerInfo) { await runManagerDisable(payloads); // Reconcile against Manager's actual state: a package is considered - // disabled only if it's no longer reported as enabled on disk. + // disabled only if it's no longer reported as active on disk. const after = await fetchManagerInfo(); - const isStillEnabled = (n) => after && after[n] && after[n].enabled; - const succeeded = after ? pkgNames.filter((n) => !isStillEnabled(n)) : pkgNames; + const isStillActive = (n) => after && after[n] && after[n].state !== "disabled"; + const succeeded = after ? pkgNames.filter((n) => !isStillActive(n)) : pkgNames; const failed = pkgNames.filter((n) => !succeeded.includes(n)); - succeeded.forEach((n) => { if (managerInfo[n]) managerInfo[n].enabled = false; }); + succeeded.forEach((n) => { if (managerInfo[n]) managerInfo[n].state = "disabled"; }); markPackagesDisabled(dialog, succeeded); updateBulkButtons(dialog, managerInfo); @@ -534,7 +548,7 @@ function updateBulkButtons(dialog, managerInfo) { dialog.querySelectorAll(".ns-disable-all-btn").forEach((btn) => { let names = []; try { names = JSON.parse(btn.dataset.pkgs); } catch { names = []; } - const remaining = names.filter((n) => managerInfo[n] && managerInfo[n].enabled); + const remaining = names.filter((n) => managerInfo[n] && managerInfo[n].state !== "disabled"); if (remaining.length === 0) { btn.style.display = "none"; } else { diff --git a/pyproject.toml b/pyproject.toml index 08cb88b..b01afbe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "comfyui-nodes-stats" description = "Track usage statistics for all ComfyUI nodes and packages" -version = "1.2.0" +version = "1.2.1" license = "MIT" [project.urls]