mirror of
https://github.com/hansjone/netxops.git
synced 2026-10-10 17:35:11 +08:00
Adapt settings client for DSH 0.2.0 configForms without breaking boot.
Soft-inject configForms/settingsScope and mark Config fields volatile so desktop 0.2.0 no longer waits forever on the removed settingsScope service. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
30d4df23fa
commit
9b8e873c18
8 changed files with 358 additions and 166 deletions
112
lib/client.js
112
lib/client.js
|
|
@ -2629,9 +2629,7 @@ var zh = {
|
|||
var LOCALE_NS = "settings.netxops";
|
||||
var inject = [
|
||||
"slots",
|
||||
"locale",
|
||||
"remote",
|
||||
"settingsScope"
|
||||
"locale"
|
||||
];
|
||||
function apply(ctx) {
|
||||
ctx.effect(() => {
|
||||
|
|
@ -2646,53 +2644,79 @@ function apply(ctx) {
|
|||
};
|
||||
}
|
||||
}, "netxops: locales");
|
||||
const card = new NetxopsCardController(ctx.settingsScope.bind({ namespace: NETXOPS_NS }), ctx);
|
||||
const t = ctx.locale.bind(LOCALE_NS);
|
||||
ctx.inject(["remote.credentials"], (credCtx) => {
|
||||
card.setCredentialsAvailable(true);
|
||||
credCtx.effect(() => {
|
||||
const off = credCtx.remote.$on("credentials/reference-updated", (ref) => {
|
||||
card.refreshCredential(String(ref));
|
||||
});
|
||||
return () => {
|
||||
off();
|
||||
card.setCredentialsAvailable(false);
|
||||
};
|
||||
}, "netxops: credential invalidations");
|
||||
});
|
||||
ctx.inject(["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");
|
||||
let mounted = false;
|
||||
const mountCard = (scope) => {
|
||||
if (mounted)
|
||||
return;
|
||||
mounted = true;
|
||||
const card = new NetxopsCardController(scope, ctx);
|
||||
ctx.inject(["remote.credentials"], (credCtx) => {
|
||||
card.setCredentialsAvailable(true);
|
||||
credCtx.effect(() => {
|
||||
const off = credCtx.remote.$on("credentials/reference-updated", (ref) => {
|
||||
card.refreshCredential(String(ref));
|
||||
});
|
||||
return () => {
|
||||
off();
|
||||
card.setCredentialsAvailable(false);
|
||||
};
|
||||
}, "netxops: credential invalidations");
|
||||
});
|
||||
ctx.inject(["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");
|
||||
return;
|
||||
}
|
||||
card.setAlarmPushRpc(call);
|
||||
connCtx.effect(() => () => {
|
||||
card.setAlarmPushRpc(undefined);
|
||||
}, "netxops: clear alarm-push rpc");
|
||||
});
|
||||
const bindDirectoryPicker = (picker) => {
|
||||
if (!picker || typeof picker.pick !== "function")
|
||||
return;
|
||||
card.setDirectoryPicker(picker);
|
||||
};
|
||||
bindDirectoryPicker(ctx.remote?.directoryPicker);
|
||||
ctx.inject(["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");
|
||||
});
|
||||
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
||||
name: "settings.section",
|
||||
id: NETXOPS_NS,
|
||||
order: 24,
|
||||
label: () => t("title"),
|
||||
locale: LOCALE_NS,
|
||||
inject: () => card.inject()
|
||||
}, NetxopsCard));
|
||||
};
|
||||
ctx.inject(["configForms"], (formsCtx) => {
|
||||
const forms = formsCtx.configForms;
|
||||
if (!forms || typeof forms.get !== "function") {
|
||||
formsCtx.logger?.warn?.("netxops: configForms present but .get missing");
|
||||
return;
|
||||
}
|
||||
card.setAlarmPushRpc(call);
|
||||
connCtx.effect(() => () => {
|
||||
card.setAlarmPushRpc(undefined);
|
||||
}, "netxops: clear alarm-push rpc");
|
||||
formsCtx.logger?.info?.("netxops: settings card via configForms");
|
||||
mountCard(forms.get(NETXOPS_NS));
|
||||
});
|
||||
const bindDirectoryPicker = (picker) => {
|
||||
if (!picker || typeof picker.pick !== "function")
|
||||
ctx.inject(["settingsScope"], (scopeCtx) => {
|
||||
const binder = scopeCtx.settingsScope;
|
||||
if (!binder || typeof binder.bind !== "function") {
|
||||
scopeCtx.logger?.warn?.("netxops: settingsScope present but .bind missing");
|
||||
return;
|
||||
card.setDirectoryPicker(picker);
|
||||
};
|
||||
bindDirectoryPicker(ctx.remote?.directoryPicker);
|
||||
ctx.inject(["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");
|
||||
}
|
||||
if (mounted)
|
||||
return;
|
||||
scopeCtx.logger?.info?.("netxops: settings card via settingsScope");
|
||||
mountCard(binder.bind({ namespace: NETXOPS_NS }));
|
||||
});
|
||||
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
||||
name: "settings.section",
|
||||
id: NETXOPS_NS,
|
||||
order: 24,
|
||||
label: () => t("title"),
|
||||
locale: LOCALE_NS,
|
||||
inject: () => card.inject()
|
||||
}, NetxopsCard));
|
||||
}
|
||||
|
||||
return module.exports;
|
||||
|
|
|
|||
91
lib/index.js
91
lib/index.js
|
|
@ -5236,30 +5236,34 @@ var NETXOPS_RPC_CHANNEL = "/netxops";
|
|||
var NETXOPS_SETTINGS_NAMESPACE = "netxops";
|
||||
var NETXOPS_PRESET_ID = "netxops";
|
||||
var DEFAULT_TOKEN_REF = "NETX_API_TOKEN";
|
||||
function vol(schema) {
|
||||
const candidate = schema;
|
||||
return typeof candidate.volatile === "function" ? candidate.volatile() : schema;
|
||||
}
|
||||
var Config = z.object({
|
||||
apiUrl: z.string().default("http://127.0.0.1:8890"),
|
||||
lang: z.string().default("zh"),
|
||||
thinkingLanguage: z.string().default("auto"),
|
||||
replyLanguage: z.string().default("follow-user"),
|
||||
tokenCredentialRef: z.string().role("credential-ref").default(DEFAULT_TOKEN_REF),
|
||||
toolCallTimeoutMs: z.number().step(1).min(1000).default(120000),
|
||||
installAgentPreset: z.boolean().default(true),
|
||||
alarmPushEnabled: z.boolean().default(false),
|
||||
alarmDeliverDsh: z.boolean().default(true),
|
||||
alarmDeliverIm: z.boolean().default(false),
|
||||
imTargets: z.string().default(""),
|
||||
imBotId: z.string().default(""),
|
||||
imTargetId: z.string().default(""),
|
||||
nmsProvider: z.string().default("zte-ume"),
|
||||
groupOpsInPreset: z.boolean().default(true),
|
||||
groupOpsPublic: z.boolean().default(false),
|
||||
groupTopologyInPreset: z.boolean().default(false),
|
||||
groupTopologyPublic: z.boolean().default(false),
|
||||
groupBizMonitorInPreset: z.boolean().default(true),
|
||||
groupBizMonitorPublic: z.boolean().default(false),
|
||||
kbRoot: z.string().default(""),
|
||||
groupKbInPreset: z.boolean().default(true),
|
||||
groupKbPublic: z.boolean().default(false)
|
||||
apiUrl: vol(z.string().default("http://127.0.0.1:8890")),
|
||||
lang: vol(z.string().default("zh")),
|
||||
thinkingLanguage: vol(z.string().default("auto")),
|
||||
replyLanguage: vol(z.string().default("follow-user")),
|
||||
tokenCredentialRef: vol(z.string().role("credential-ref").default(DEFAULT_TOKEN_REF)),
|
||||
toolCallTimeoutMs: vol(z.number().step(1).min(1000).default(120000)),
|
||||
installAgentPreset: vol(z.boolean().default(true)),
|
||||
alarmPushEnabled: vol(z.boolean().default(false)),
|
||||
alarmDeliverDsh: vol(z.boolean().default(true)),
|
||||
alarmDeliverIm: vol(z.boolean().default(false)),
|
||||
imTargets: vol(z.string().default("")),
|
||||
imBotId: vol(z.string().default("")),
|
||||
imTargetId: vol(z.string().default("")),
|
||||
nmsProvider: vol(z.string().default("zte-ume")),
|
||||
groupOpsInPreset: vol(z.boolean().default(true)),
|
||||
groupOpsPublic: vol(z.boolean().default(false)),
|
||||
groupTopologyInPreset: vol(z.boolean().default(false)),
|
||||
groupTopologyPublic: vol(z.boolean().default(false)),
|
||||
groupBizMonitorInPreset: vol(z.boolean().default(true)),
|
||||
groupBizMonitorPublic: vol(z.boolean().default(false)),
|
||||
kbRoot: vol(z.string().default("")),
|
||||
groupKbInPreset: vol(z.boolean().default(true)),
|
||||
groupKbPublic: vol(z.boolean().default(false))
|
||||
});
|
||||
function packageRoot() {
|
||||
return join7(dirname4(fileURLToPath2(import.meta.url)), "..");
|
||||
|
|
@ -5296,6 +5300,19 @@ async function resolveToken(ctx, refName) {
|
|||
const hit = await ctx.credentials.resolve(credentialRef(refName));
|
||||
return hit?.value ?? "";
|
||||
}
|
||||
function describeRows(describe) {
|
||||
if (typeof describe !== "function")
|
||||
return [];
|
||||
try {
|
||||
const raw = describe();
|
||||
if (Array.isArray(raw))
|
||||
return raw;
|
||||
if (raw && typeof raw === "object" && Array.isArray(raw.namespaces)) {
|
||||
return raw.namespaces;
|
||||
}
|
||||
} catch {}
|
||||
return [];
|
||||
}
|
||||
function installNetxopsSettings(ctx, entry, hooks) {
|
||||
const legacy = DshSettings.installSettingsSection;
|
||||
if (typeof legacy === "function") {
|
||||
|
|
@ -5303,7 +5320,33 @@ function installNetxopsSettings(ctx, entry, hooks) {
|
|||
return;
|
||||
}
|
||||
ctx.inject(["settings"], (settingsCtx) => {
|
||||
settingsCtx.settings.installSection(ctx, NETXOPS_SETTINGS_NAMESPACE, Config, entry, hooks);
|
||||
const settings = settingsCtx.settings;
|
||||
if (typeof settings.installSection === "function") {
|
||||
settings.installSection(ctx, NETXOPS_SETTINGS_NAMESPACE, Config, entry, hooks);
|
||||
return;
|
||||
}
|
||||
const readLive = () => {
|
||||
const row = describeRows(settings.describe).find((item) => item.ns === NETXOPS_SETTINGS_NAMESPACE);
|
||||
if (row?.value !== null && typeof row?.value === "object" && !Array.isArray(row.value)) {
|
||||
return { ...entry, ...row.value };
|
||||
}
|
||||
return entry;
|
||||
};
|
||||
hooks.setSource(readLive);
|
||||
hooks.onChange();
|
||||
let lastFingerprint = JSON.stringify(readLive());
|
||||
const timer = setInterval(() => {
|
||||
const next = readLive();
|
||||
const fingerprint = JSON.stringify(next);
|
||||
if (fingerprint === lastFingerprint)
|
||||
return;
|
||||
lastFingerprint = fingerprint;
|
||||
hooks.onChange();
|
||||
}, 2000);
|
||||
settingsCtx.effect(() => () => {
|
||||
clearInterval(timer);
|
||||
}, "netxops: settings describe poll");
|
||||
settingsCtx.logger.info("netxops: following settings via describe() (no installSection — DSH ≥0.1.7 path)");
|
||||
});
|
||||
}
|
||||
function apply(ctx, config = Config({})) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue