Do not hard-inject remote.credentials on shipped dsh.

0.1.1-rc.2 remotes never mounts credentials; soft-inject unlocks the token field when available, otherwise point users at set-netx-token.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-04 22:43:46 +08:00
parent 69a4820369
commit 99ded2fe2d
6 changed files with 143 additions and 28 deletions

View file

@ -251,7 +251,7 @@ function NetxopsCard(props) {
}),
/* @__PURE__ */ jsx_runtime.jsx("p", {
className: "dsh-nx-hint",
children: t("apiTokenHint")
children: state.apiTokenRemoteReady ? t("apiTokenHint") : t("apiTokenUnavailable")
})
]
}),
@ -533,7 +533,12 @@ class NetxopsCardController {
ctx;
form;
store;
credential = { ref: "", configured: false, writable: true };
credential = {
ref: "",
configured: false,
writable: false,
remoteReady: false
};
constructor(scope, ctx) {
this.scope = scope;
this.ctx = ctx;
@ -544,6 +549,18 @@ class NetxopsCardController {
});
this.readCredential();
}
setCredentialsAvailable(ready) {
if (this.credential.remoteReady === ready)
return;
this.credential = {
...this.credential,
remoteReady: ready,
writable: ready
};
this.store.set(this.projection());
if (ready)
this.readCredential();
}
projection() {
return {
...this.form.shell(),
@ -552,25 +569,43 @@ class NetxopsCardController {
pythonCommand: this.form.field("pythonCommand"),
apiToken: this.form.field(API_TOKEN_FIELD),
apiTokenConfigured: this.credential.configured,
apiTokenWritable: this.credential.writable
apiTokenWritable: this.credential.remoteReady && this.credential.writable,
apiTokenRemoteReady: this.credential.remoteReady
};
}
credentials() {
return this.ctx.get("remote.credentials");
}
async readCredential() {
const ref = refOf(this.scope.getSnapshot());
const api = this.credentials();
if (api === undefined) {
if (ref !== this.credential.ref || this.credential.remoteReady) {
this.credential = {
ref,
configured: false,
writable: false,
remoteReady: false
};
this.store.set(this.projection());
}
return;
}
if (ref !== this.credential.ref) {
this.credential = { ref, configured: false, writable: true };
this.credential = { ref, configured: false, writable: true, remoteReady: true };
this.store.set(this.projection());
}
const response = await this.ctx.remote.credentials.describe([ref]);
const response = await api.describe([ref]);
if (!response.ok || ref !== refOf(this.scope.getSnapshot()))
return;
const view = response.value[ref];
const view = response.value?.[ref];
const next = {
ref,
configured: view?.configured ?? false,
writable: view?.writable ?? true
writable: view?.writable ?? true,
remoteReady: true
};
if (next.configured === this.credential.configured && next.writable === this.credential.writable)
if (next.configured === this.credential.configured && next.writable === this.credential.writable && next.remoteReady === this.credential.remoteReady)
return;
this.credential = next;
this.store.set(this.projection());
@ -584,7 +619,10 @@ class NetxopsCardController {
return { hooks: { netxopsCard: this.store }, ...this.form.actions() };
}
async writeToken(value) {
await this.ctx.remote.credentials.set(refOf(this.scope.getSnapshot()), value);
const api = this.credentials();
if (api === undefined)
return false;
await api.set(refOf(this.scope.getSnapshot()), value);
await this.readCredential();
return this.credential.configured;
}
@ -606,6 +644,7 @@ var en = {
pythonCommandHint: "Executable that can run `python -m netx_mcp`.",
apiToken: "API token",
apiTokenHint: "Stored as credential NETX_API_TOKEN (never written into settings). Leave blank to keep the current token.",
apiTokenUnavailable: "This DSH build does not expose remote.credentials. Set the token with scripts/set-netx-token.ps1 (or .sh), then restart is not required if credentials are watched.",
apiTokenSet: "Configured",
apiTokenUnset: "Not set",
overridden: "Overridden",
@ -631,6 +670,7 @@ var zh = {
pythonCommandHint: "能执行 `python -m netx_mcp` 的解释器。",
apiToken: "API Token",
apiTokenHint: "写入凭据 NETX_API_TOKEN(不会进 settings)。留空表示保留已有 token。",
apiTokenUnavailable: "当前 DSH 未提供 remote.credentials。请用 scripts/set-netx-token.ps1(或 .sh)写入 token;若 harness 在监视凭据文件则无需重启。",
apiTokenSet: "已配置",
apiTokenUnset: "未设置",
overridden: "已覆盖",
@ -652,15 +692,23 @@ var inject = [
"slots",
"locale",
"remote",
"remote.credentials",
"settingsScope"
];
function apply(ctx) {
ctx.effect(() => ctx.locale.register(LOCALE_NS, { zh, en }), "netxops: locales");
const card = new NetxopsCardController(ctx.settingsScope.bind({ namespace: NETXOPS_NS }), ctx);
ctx.effect(() => ctx.remote.$on("credentials/reference-updated", (ref) => {
card.refreshCredential(ref);
}), "netxops: credential invalidations");
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.slots.inject("settings.plugin.item", () => ctx.slots.register({
name: "settings.plugin.item",
key: NETXOPS_NS,