mirror of
https://github.com/hansjone/netxops.git
synced 2026-10-15 15:53:23 +08:00
Fix Desktop link-install boot and show Netx Ops settings.
Prefer shared schemastery 3.18.2 peers (Desktop 3.18.4 alone breaks Config), and register settings.section eagerly with a deferred form scope so the Settings nav appears without waiting on configForms. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
3861006c13
commit
838ff2fed4
6 changed files with 487 additions and 149 deletions
214
lib/client.js
214
lib/client.js
|
|
@ -2656,6 +2656,98 @@ function namespaceCandidates(ctx) {
|
|||
out.push(NETXOPS_NS);
|
||||
return out;
|
||||
}
|
||||
function createMemoryScope(initial) {
|
||||
let value = { ...initial };
|
||||
let revision = 0;
|
||||
const listeners = new Set;
|
||||
const notify = () => {
|
||||
for (const listener of listeners)
|
||||
listener();
|
||||
};
|
||||
return {
|
||||
getSnapshot: () => ({
|
||||
status: "ready",
|
||||
value,
|
||||
writable: true,
|
||||
revision
|
||||
}),
|
||||
subscribe: (listener) => {
|
||||
listeners.add(listener);
|
||||
return () => {
|
||||
listeners.delete(listener);
|
||||
};
|
||||
},
|
||||
set: async (field, next) => {
|
||||
value = { ...value, [field]: next };
|
||||
revision += 1;
|
||||
notify();
|
||||
return true;
|
||||
},
|
||||
unset: async (field) => {
|
||||
const next = { ...value };
|
||||
delete next[field];
|
||||
value = next;
|
||||
revision += 1;
|
||||
notify();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
function createDeferredScope(fallback) {
|
||||
let inner = fallback;
|
||||
const outerListeners = new Set;
|
||||
let innerOff;
|
||||
const relay = () => {
|
||||
for (const listener of outerListeners)
|
||||
listener();
|
||||
};
|
||||
const bindInner = () => {
|
||||
innerOff?.();
|
||||
innerOff = inner.subscribe(relay);
|
||||
};
|
||||
bindInner();
|
||||
return {
|
||||
getSnapshot: () => inner.getSnapshot(),
|
||||
subscribe: (listener) => {
|
||||
outerListeners.add(listener);
|
||||
return () => {
|
||||
outerListeners.delete(listener);
|
||||
};
|
||||
},
|
||||
set: (field, next) => inner.set(field, next),
|
||||
unset: (field) => inner.unset(field),
|
||||
attach: (real) => {
|
||||
if (inner === real)
|
||||
return;
|
||||
inner = real;
|
||||
bindInner();
|
||||
relay();
|
||||
}
|
||||
};
|
||||
}
|
||||
var MEMORY_DEFAULTS = {
|
||||
apiUrl: "http://127.0.0.1:8890",
|
||||
lang: "zh",
|
||||
thinkingLanguage: "auto",
|
||||
replyLanguage: "follow-user",
|
||||
nmsProvider: "zte-ume",
|
||||
tokenCredentialRef: "NETX_API_TOKEN",
|
||||
alarmPushEnabled: false,
|
||||
alarmDeliverDsh: true,
|
||||
alarmDeliverIm: false,
|
||||
imBotId: "",
|
||||
imTargetId: "",
|
||||
imTargets: "",
|
||||
groupOpsInPreset: true,
|
||||
groupOpsPublic: false,
|
||||
groupTopologyInPreset: false,
|
||||
groupTopologyPublic: false,
|
||||
groupBizMonitorInPreset: false,
|
||||
groupBizMonitorPublic: false,
|
||||
kbRoot: "",
|
||||
groupKbInPreset: true,
|
||||
groupKbPublic: false
|
||||
};
|
||||
function apply(ctx) {
|
||||
ctx.effect(() => {
|
||||
try {
|
||||
|
|
@ -2670,43 +2762,36 @@ function apply(ctx) {
|
|||
}
|
||||
}, "netxops: locales");
|
||||
const t = ctx.locale.bind(LOCALE_NS);
|
||||
let card;
|
||||
let wiredExtras = false;
|
||||
let sectionRegistered = false;
|
||||
const deferred = createDeferredScope(createMemoryScope(MEMORY_DEFAULTS));
|
||||
const card = new NetxopsCardController(deferred, ctx);
|
||||
let pluginsItemId;
|
||||
const ensureCard = (scope) => {
|
||||
if (card)
|
||||
return card;
|
||||
card = new NetxopsCardController(scope, ctx);
|
||||
if (!wiredExtras) {
|
||||
wiredExtras = true;
|
||||
wireOptionalServices(ctx, card);
|
||||
}
|
||||
return card;
|
||||
};
|
||||
const registerSection = (owner) => {
|
||||
if (sectionRegistered)
|
||||
return () => {};
|
||||
let wiredExtras = false;
|
||||
const ensureExtras = () => {
|
||||
if (wiredExtras)
|
||||
return;
|
||||
wiredExtras = true;
|
||||
try {
|
||||
const off = ctx.slots.inject("settings.section", () => ctx.slots.register({
|
||||
name: "settings.section",
|
||||
id: NETXOPS_NS,
|
||||
order: 24,
|
||||
label: () => t("title"),
|
||||
locale: LOCALE_NS,
|
||||
inject: () => owner.inject()
|
||||
}, NetxopsCard));
|
||||
sectionRegistered = true;
|
||||
return () => {
|
||||
sectionRegistered = false;
|
||||
off();
|
||||
};
|
||||
wireOptionalServices(ctx, card);
|
||||
} catch (error) {
|
||||
ctx.logger?.warn?.("netxops: settings.section unavailable: %s", error);
|
||||
return () => {};
|
||||
ctx.logger?.warn?.("netxops: optional remotes skipped: %s", error);
|
||||
}
|
||||
};
|
||||
const registerPluginsItem = (owner, id) => {
|
||||
try {
|
||||
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
||||
name: "settings.section",
|
||||
id: NETXOPS_NS,
|
||||
order: 24,
|
||||
label: () => t("title"),
|
||||
locale: LOCALE_NS,
|
||||
inject: () => {
|
||||
ensureExtras();
|
||||
return card.inject();
|
||||
}
|
||||
}, NetxopsCard));
|
||||
} catch (error) {
|
||||
ctx.logger?.warn?.("netxops: settings.section unavailable: %s", error);
|
||||
}
|
||||
const registerPluginsItem = (id) => {
|
||||
if (pluginsItemId === id)
|
||||
return () => {};
|
||||
try {
|
||||
|
|
@ -2716,7 +2801,10 @@ function apply(ctx) {
|
|||
order: 24,
|
||||
label: () => t("title"),
|
||||
locale: LOCALE_NS,
|
||||
inject: () => owner.inject()
|
||||
inject: () => {
|
||||
ensureExtras();
|
||||
return card.inject();
|
||||
}
|
||||
}, NetxopsCard));
|
||||
pluginsItemId = id;
|
||||
return () => {
|
||||
|
|
@ -2729,17 +2817,6 @@ function apply(ctx) {
|
|||
return () => {};
|
||||
}
|
||||
};
|
||||
const mountUi = (owner, ns, opts) => {
|
||||
const offs = [];
|
||||
if (opts.section)
|
||||
offs.push(registerSection(owner));
|
||||
if (opts.pluginsItem)
|
||||
offs.push(registerPluginsItem(owner, ns));
|
||||
return () => {
|
||||
for (const off of offs)
|
||||
off();
|
||||
};
|
||||
};
|
||||
const tryGetForm = (forms, candidates) => {
|
||||
if (typeof forms.get !== "function")
|
||||
return;
|
||||
|
|
@ -2752,6 +2829,12 @@ function apply(ctx) {
|
|||
}
|
||||
return;
|
||||
};
|
||||
const attachForm = (hit, source) => {
|
||||
ctx.logger?.info?.("netxops: attach form via %s (%s)", source, hit.ns);
|
||||
deferred.attach(hit.scope);
|
||||
ensureExtras();
|
||||
return registerPluginsItem(hit.ns);
|
||||
};
|
||||
ctx.inject(["configForms"], (formsCtx) => {
|
||||
const forms = formsCtx.configForms;
|
||||
if (!forms) {
|
||||
|
|
@ -2772,35 +2855,42 @@ function apply(ctx) {
|
|||
formsCtx.logger?.warn?.("netxops: served %s but configForms.get failed", ns);
|
||||
return () => {};
|
||||
}
|
||||
formsCtx.logger?.info?.("netxops: settings via whileServed (%s)", hit.ns);
|
||||
const owner = ensureCard(hit.scope);
|
||||
return mountUi(owner, hit.ns, { section: true, pluginsItem: true });
|
||||
return attachForm(hit, "whileServed");
|
||||
}), "netxops: configForms whileServed");
|
||||
}
|
||||
const immediate = tryGetForm(forms, candidates);
|
||||
if (immediate) {
|
||||
formsCtx.logger?.info?.("netxops: settings eager mount (%s)", immediate.ns);
|
||||
const owner = ensureCard(immediate.scope);
|
||||
formsCtx.effect(() => mountUi(owner, immediate.ns, { section: true, pluginsItem: true }), "netxops: configForms eager slots");
|
||||
formsCtx.effect(() => attachForm(immediate, "eager"), "netxops: configForms eager attach");
|
||||
} else if (typeof forms.whileServed !== "function") {
|
||||
formsCtx.logger?.warn?.("netxops: configForms present but no get/whileServed match for %s", candidates.join(","));
|
||||
}
|
||||
});
|
||||
ctx.inject(["settingsScope"], (scopeCtx) => {
|
||||
if (card)
|
||||
return;
|
||||
const binder = scopeCtx.settingsScope;
|
||||
if (!binder || typeof binder.bind !== "function") {
|
||||
scopeCtx.logger?.warn?.("netxops: settingsScope present but .bind missing");
|
||||
return;
|
||||
}
|
||||
scopeCtx.logger?.info?.("netxops: settings card via settingsScope");
|
||||
const owner = ensureCard(binder.bind({ namespace: NETXOPS_NS }));
|
||||
scopeCtx.effect(() => registerSection(owner), "netxops: settings.section");
|
||||
scopeCtx.logger?.info?.("netxops: attach form via settingsScope");
|
||||
deferred.attach(binder.bind({ namespace: NETXOPS_NS }));
|
||||
ensureExtras();
|
||||
});
|
||||
}
|
||||
function wireOptionalServices(ctx, card) {
|
||||
ctx.inject(["remote.credentials"], (credCtx) => {
|
||||
const soft = (deps, run, label) => {
|
||||
try {
|
||||
ctx.inject(deps, (inner) => {
|
||||
try {
|
||||
run(inner);
|
||||
} catch (error) {
|
||||
inner.logger?.warn?.("netxops: %s handler failed: %s", label, error);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
ctx.logger?.warn?.("netxops: soft-inject %s skipped: %s", label, error);
|
||||
}
|
||||
};
|
||||
soft(["remote.credentials"], (credCtx) => {
|
||||
card.setCredentialsAvailable(true);
|
||||
credCtx.effect(() => {
|
||||
const off = credCtx.remote.$on("credentials/reference-updated", (ref) => {
|
||||
|
|
@ -2811,32 +2901,32 @@ function wireOptionalServices(ctx, card) {
|
|||
card.setCredentialsAvailable(false);
|
||||
};
|
||||
}, "netxops: credential invalidations");
|
||||
});
|
||||
ctx.inject(["connection"], (connCtx) => {
|
||||
}, "remote.credentials");
|
||||
soft(["connection"], (connCtx) => {
|
||||
const call = connCtx.connection?.rpc?.call?.bind(connCtx.connection.rpc);
|
||||
if (typeof call !== "function") {
|
||||
connCtx.logger.warn("netxops: connection.rpc.call unavailable — alarm status UI disabled");
|
||||
connCtx.logger?.warn?.("netxops: connection.rpc.call unavailable — alarm status UI disabled");
|
||||
return;
|
||||
}
|
||||
card.setAlarmPushRpc(call);
|
||||
connCtx.effect(() => () => {
|
||||
card.setAlarmPushRpc(undefined);
|
||||
}, "netxops: clear alarm-push rpc");
|
||||
});
|
||||
}, "connection");
|
||||
const bindDirectoryPicker = (picker) => {
|
||||
if (!picker || typeof picker.pick !== "function")
|
||||
return;
|
||||
card.setDirectoryPicker(picker);
|
||||
};
|
||||
bindDirectoryPicker(ctx.remote?.directoryPicker);
|
||||
ctx.inject(["remote.directoryPicker"], (dpCtx) => {
|
||||
soft(["remote.directoryPicker"], (dpCtx) => {
|
||||
const viaGet = typeof dpCtx.get === "function" ? dpCtx.get("remote.directoryPicker") : undefined;
|
||||
const viaNested = dpCtx.remote?.directoryPicker;
|
||||
bindDirectoryPicker(viaGet ?? viaNested);
|
||||
dpCtx.effect(() => () => {
|
||||
card.setDirectoryPicker(undefined);
|
||||
}, "netxops: clear directory picker");
|
||||
});
|
||||
}, "remote.directoryPicker");
|
||||
}
|
||||
|
||||
return module.exports;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue