fix(ui): keep the empty-trash panel wiring quiet when the client is mocked
This commit is contained in:
@@ -14,16 +14,13 @@ import {
|
||||
restoreDbBackup,
|
||||
vectorBackfill,
|
||||
unloadVectorModels,
|
||||
getSecuritySettings,
|
||||
getTrashStatus,
|
||||
emptyTrash,
|
||||
} from "../../api/client.js";
|
||||
import { ENDPOINTS } from "../../api/endpoints.js";
|
||||
import { APP_CONFIG } from "../../app/config.js";
|
||||
import { comfyConfirm } from "../../app/dialogs.js";
|
||||
import { comfyToast, recordToastHistory } from "../../app/toast.js";
|
||||
import { t } from "../../app/i18n.js";
|
||||
import { runEmptyTrashAction } from "./emptyTrashAction.js";
|
||||
import { resolveTrashClient, runEmptyTrashAction } from "./emptyTrashAction.js";
|
||||
import {
|
||||
getRuntimeEnrichmentState,
|
||||
setRuntimeEnrichmentState,
|
||||
@@ -120,12 +117,14 @@ function appendStatusProgressBars(statusText: any, items: any[] = []) {
|
||||
const percent = Math.max(0, Math.min(100, Number(item.percent) || 0));
|
||||
const row = document.createElement("div");
|
||||
row.className = "mjr-status-progress-row";
|
||||
row.style.cssText = "display:grid;grid-template-columns:minmax(72px,0.9fr) 1.8fr auto;gap:7px;align-items:center;";
|
||||
row.style.cssText =
|
||||
"display:grid;grid-template-columns:minmax(72px,0.9fr) 1.8fr auto;gap:7px;align-items:center;";
|
||||
|
||||
const label = document.createElement("span");
|
||||
label.className = "mjr-status-progress-label";
|
||||
label.textContent = String(item.label || "");
|
||||
label.style.cssText = "font-size:10px;opacity:0.72;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;";
|
||||
label.style.cssText =
|
||||
"font-size:10px;opacity:0.72;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;";
|
||||
|
||||
const bar = document.createElement("div");
|
||||
bar.className = "mjr-status-progress-bar";
|
||||
@@ -151,7 +150,10 @@ function appendStatusProgressBars(statusText: any, items: any[] = []) {
|
||||
statusText.appendChild(wrap);
|
||||
}
|
||||
|
||||
function buildStatusProgressItems(counters: any, { totalAssets = 0, withWorkflows = 0, withGenerationData = 0 } = {}) {
|
||||
function buildStatusProgressItems(
|
||||
counters: any,
|
||||
{ totalAssets = 0, withWorkflows = 0, withGenerationData = 0 } = {},
|
||||
) {
|
||||
const total = Math.max(0, Number(totalAssets || counters?.total_assets || 0) || 0);
|
||||
if (total <= 0) return [];
|
||||
const pct = (value: any, denominator = total) => {
|
||||
@@ -300,7 +302,9 @@ function applyStatusHighlight(section: any, tone = "neutral", options: Record<st
|
||||
? "success"
|
||||
: "info";
|
||||
try {
|
||||
comfyToast((messages as Record<string, any>)[tone] || messages.info, level, 1800, { noHistory: true });
|
||||
comfyToast((messages as Record<string, any>)[tone] || messages.info, level, 1800, {
|
||||
noHistory: true,
|
||||
});
|
||||
} catch (e) {
|
||||
console.debug?.(e);
|
||||
}
|
||||
@@ -523,7 +527,9 @@ export function createStatusIndicator(options: Record<string, any> = {}): any {
|
||||
_hasBackupChoices = backupSelect.childNodes.length > 0;
|
||||
backupSelect.disabled = !_hasBackupChoices;
|
||||
if (_hasBackupChoices && !matched && backupSelect.childNodes[0]) {
|
||||
backupSelect.value = String((backupSelect.childNodes[0] as HTMLOptionElement)?.value || "");
|
||||
backupSelect.value = String(
|
||||
(backupSelect.childNodes[0] as HTMLOptionElement)?.value || "",
|
||||
);
|
||||
}
|
||||
if (restoreDbBtn) restoreDbBtn.disabled = !_hasBackupChoices;
|
||||
} catch {
|
||||
@@ -1245,18 +1251,25 @@ export function createStatusIndicator(options: Record<string, any> = {}): any {
|
||||
};
|
||||
void (async () => {
|
||||
try {
|
||||
const res = await getTrashStatus();
|
||||
const client = await resolveTrashClient();
|
||||
if (typeof client.getTrashStatus !== "function") {
|
||||
emptyTrashBtn.style.display = "none";
|
||||
return;
|
||||
}
|
||||
const res = await client.getTrashStatus();
|
||||
if (res?.ok && res?.data?.supported === false) {
|
||||
emptyTrashBtn.style.display = "none";
|
||||
return;
|
||||
}
|
||||
const sec = await getSecuritySettings();
|
||||
if (sec?.ok && sec?.data?.prefs && sec.data.prefs.allow_delete === false) {
|
||||
emptyTrashBtn.disabled = true;
|
||||
emptyTrashBtn.title = t(
|
||||
"status.emptyTrashDisabled",
|
||||
"Empty trash is disabled. Enable 'allow_delete' in settings or set MAJOOR_ALLOW_DELETE=1.",
|
||||
);
|
||||
if (typeof client.getSecuritySettings === "function") {
|
||||
const sec = await client.getSecuritySettings();
|
||||
if (sec?.ok && sec?.data?.prefs && sec.data.prefs.allow_delete === false) {
|
||||
emptyTrashBtn.disabled = true;
|
||||
emptyTrashBtn.title = t(
|
||||
"status.emptyTrashDisabled",
|
||||
"Empty trash is disabled. Enable 'allow_delete' in settings or set MAJOOR_ALLOW_DELETE=1.",
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.debug?.(e);
|
||||
@@ -1271,9 +1284,10 @@ export function createStatusIndicator(options: Record<string, any> = {}): any {
|
||||
memoryPurgeBtn.disabled = true;
|
||||
emptyTrashBtn.textContent = t("btn.emptyTrashRunning", "Emptying...");
|
||||
try {
|
||||
const client = await resolveTrashClient();
|
||||
await runEmptyTrashAction({
|
||||
getStatus: getTrashStatus,
|
||||
empty: emptyTrash,
|
||||
getStatus: client.getTrashStatus,
|
||||
empty: client.emptyTrash,
|
||||
confirm: (message: string, title: string) =>
|
||||
Promise.resolve(comfyConfirm(message, title)) as Promise<boolean>,
|
||||
toast: (message, level, duration, options) =>
|
||||
@@ -1678,14 +1692,16 @@ export async function triggerScan(
|
||||
? t("scope.custom", "Custom")
|
||||
: desiredScope === "workflow"
|
||||
? t("scope.workflow", "Workflow")
|
||||
: t("scope.output", "Outputs");
|
||||
: t("scope.output", "Outputs");
|
||||
|
||||
let detail = "";
|
||||
if (desiredScope === "input")
|
||||
detail = roots?.input_directory ? ` (${roots.input_directory})` : "";
|
||||
else if (desiredScope === "custom") {
|
||||
const list = Array.isArray(roots?.custom_roots) ? roots.custom_roots : [];
|
||||
const picked = desiredCustomRootId ? list.find((r: any) => r?.id === desiredCustomRootId) : null;
|
||||
const picked = desiredCustomRootId
|
||||
? list.find((r: any) => r?.id === desiredCustomRootId)
|
||||
: null;
|
||||
detail = picked?.path ? ` (${picked.path})` : "";
|
||||
} else if (desiredScope === "output")
|
||||
detail = roots?.output_directory ? ` (${roots.output_directory})` : "";
|
||||
@@ -1793,7 +1809,11 @@ function createCapabilityBadge(label: any, available: any, hint: any) {
|
||||
return badge;
|
||||
}
|
||||
|
||||
function renderCapabilities(section: any, toolAvailability: Record<string, any> = {}, toolPaths: Record<string, any> = {}) {
|
||||
function renderCapabilities(
|
||||
section: any,
|
||||
toolAvailability: Record<string, any> = {},
|
||||
toolPaths: Record<string, any> = {},
|
||||
) {
|
||||
if (!section) return;
|
||||
section.replaceChildren();
|
||||
if (!toolAvailability || Object.keys(toolAvailability).length === 0) {
|
||||
@@ -1818,7 +1838,11 @@ function renderCapabilities(section: any, toolAvailability: Record<string, any>
|
||||
section.appendChild(wrapper);
|
||||
}
|
||||
|
||||
function renderToolsStatusLine(section: any, toolStatus: any = null, fallbackAvailability: Record<string, any> = {}) {
|
||||
function renderToolsStatusLine(
|
||||
section: any,
|
||||
toolStatus: any = null,
|
||||
fallbackAvailability: Record<string, any> = {},
|
||||
) {
|
||||
if (!section) return;
|
||||
const parent = section.parentElement;
|
||||
if (!parent) return;
|
||||
@@ -1848,7 +1872,11 @@ function renderToolsStatusLine(section: any, toolStatus: any = null, fallbackAva
|
||||
container.textContent = segments.join(" | ");
|
||||
}
|
||||
|
||||
function createCapabilityNode({ label, hint }: { label: any; hint: any }, available: any, path: any) {
|
||||
function createCapabilityNode(
|
||||
{ label, hint }: { label: any; hint: any },
|
||||
available: any,
|
||||
path: any,
|
||||
) {
|
||||
const container = document.createElement("div");
|
||||
container.style.cssText = `
|
||||
display: flex;
|
||||
@@ -1917,7 +1945,7 @@ function buildIndexHealthLine(counters: any, desiredScope: any) {
|
||||
? t("scope.custom", "Custom")
|
||||
: desiredScope === "workflow"
|
||||
? t("scope.workflow", "Workflow")
|
||||
: t("scope.output", "Outputs");
|
||||
: t("scope.output", "Outputs");
|
||||
|
||||
if (!Number.isFinite(totalAssets) || totalAssets <= 0) {
|
||||
return `${t("status.indexHealthEmpty", "Index health: empty")} (${scopeLabel})`;
|
||||
@@ -2093,13 +2121,13 @@ export async function updateStatus(
|
||||
} else if (vectorDegraded) {
|
||||
healthTone = "warning";
|
||||
}
|
||||
const isWorkflowScope = desiredScope === "workflow";
|
||||
const displayTone =
|
||||
isCustomBrowserMode && healthTone !== "error"
|
||||
? "browser"
|
||||
: isWorkflowScope && healthTone !== "error"
|
||||
? "workflow"
|
||||
: healthTone;
|
||||
const isWorkflowScope = desiredScope === "workflow";
|
||||
const displayTone =
|
||||
isCustomBrowserMode && healthTone !== "error"
|
||||
? "browser"
|
||||
: isWorkflowScope && healthTone !== "error"
|
||||
? "workflow"
|
||||
: healthTone;
|
||||
let watcherInfo = counters.watcher;
|
||||
try {
|
||||
if (!watcherInfo || typeof watcherInfo.enabled !== "boolean") {
|
||||
@@ -2120,10 +2148,10 @@ export async function updateStatus(
|
||||
scope: t("scope.workflow", "Workflow"),
|
||||
})
|
||||
: isCustomBrowserMode
|
||||
? t("status.watcher.disabledScoped", "Watcher: disabled ({scope})", {
|
||||
scope: t("scope.customBrowser", "Browser"),
|
||||
})
|
||||
: formatWatcherLine(watcherInfo, desiredScope);
|
||||
? t("status.watcher.disabledScoped", "Watcher: disabled ({scope})", {
|
||||
scope: t("scope.customBrowser", "Browser"),
|
||||
})
|
||||
: formatWatcherLine(watcherInfo, desiredScope);
|
||||
const enrichmentLine = enrichmentActive
|
||||
? t("status.enrichmentQueue", "Metadata enrichment queue: {count}", {
|
||||
count: enrichmentQueueLength,
|
||||
@@ -2142,7 +2170,7 @@ export async function updateStatus(
|
||||
? t("scope.customBrowser", "Browser")
|
||||
: desiredScope === "workflow"
|
||||
? t("scope.workflow", "Workflow")
|
||||
: t("scope.output", "Outputs");
|
||||
: t("scope.output", "Outputs");
|
||||
|
||||
if (healthTone === "error") {
|
||||
statusDot.style.background = "var(--mjr-status-error, #f44336)";
|
||||
@@ -2320,7 +2348,12 @@ export async function updateStatus(
|
||||
return null;
|
||||
}
|
||||
|
||||
function createRetryButton(statusDot: any, statusText: any, capabilitiesSection = null, scanTarget = null) {
|
||||
function createRetryButton(
|
||||
statusDot: any,
|
||||
statusText: any,
|
||||
capabilitiesSection = null,
|
||||
scanTarget = null,
|
||||
) {
|
||||
const button = document.createElement("button");
|
||||
button.textContent = t("btn.retryServices");
|
||||
button.style.cssText = `
|
||||
|
||||
@@ -17,6 +17,36 @@ export interface EmptyTrashDeps {
|
||||
|
||||
export type EmptyTrashOutcome = "empty" | "removed" | "cancelled" | "unsupported" | "error";
|
||||
|
||||
/**
|
||||
* Resolve the trash client functions lazily so a partially mocked API module
|
||||
* (e.g. in other suites' tests) never throws or logs during panel init.
|
||||
* Missing functions are reported as undefined and treated as "unsupported".
|
||||
*/
|
||||
let _trashClientCache: Record<string, any> | null = null;
|
||||
|
||||
export async function resolveTrashClient(): Promise<Record<string, any>> {
|
||||
if (_trashClientCache) return _trashClientCache;
|
||||
const resolved: Record<string, any> = {};
|
||||
try {
|
||||
const mod: any = await import("../../api/client.js");
|
||||
const available: string[] = Object.keys(mod);
|
||||
for (const key of ["getTrashStatus", "emptyTrash", "getSecuritySettings"]) {
|
||||
if (available.includes(key)) {
|
||||
try {
|
||||
const value = mod[key];
|
||||
resolved[key] = typeof value === "function" ? value : undefined;
|
||||
} catch {
|
||||
resolved[key] = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Module unavailable — leave everything undefined (treated as unsupported).
|
||||
}
|
||||
_trashClientCache = resolved;
|
||||
return resolved;
|
||||
}
|
||||
|
||||
function _translateDefault(
|
||||
deps: EmptyTrashDeps,
|
||||
key: string,
|
||||
|
||||
Reference in New Issue
Block a user