mirror of
https://github.com/hansjone/netxops.git
synced 2026-10-09 20:53:56 +08:00
Fix KB settings badge stuck on unconfigured after bind (0.1.38).
Reload Host kbRoot after browse/save so the status badge tracks the live package instead of a stale snapshot. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
0f31a6648a
commit
1b4c402723
9 changed files with 261 additions and 50 deletions
111
lib/client.js
111
lib/client.js
|
|
@ -591,6 +591,7 @@ function unconfiguredKbSnapshot() {
|
|||
// src/client/kb-status-view.ts
|
||||
var NETXOPS_RPC_CHANNEL2 = "/netxops";
|
||||
var KB_STATUS_ENDPOINT = "kb.status";
|
||||
var KB_RELOAD_ENDPOINT = "kb.reload";
|
||||
var KB_RESOLVE_ENDPOINT = "kb.resolve";
|
||||
function asKbSnapshot(value) {
|
||||
const empty = unconfiguredKbSnapshot();
|
||||
|
|
@ -624,22 +625,44 @@ function asKbSnapshot(value) {
|
|||
errorMessage: typeof row.errorMessage === "string" ? row.errorMessage : ""
|
||||
};
|
||||
}
|
||||
function asRpcOk(result) {
|
||||
return result !== null && typeof result === "object" && result.ok === true;
|
||||
}
|
||||
async function fetchKbStatus(call, signal) {
|
||||
const result = await call(NETXOPS_RPC_CHANNEL2, KB_STATUS_ENDPOINT, {}, signal);
|
||||
if (result !== null && typeof result === "object" && result.ok === true) {
|
||||
if (asRpcOk(result)) {
|
||||
return asKbSnapshot(result.value);
|
||||
}
|
||||
return unconfiguredKbSnapshot();
|
||||
if (result !== null && typeof result === "object" && result.ok === false) {
|
||||
const message = String(result.error?.message ?? "kb.status rpc failed");
|
||||
return { ...unconfiguredKbSnapshot(), status: "error", errorMessage: message };
|
||||
}
|
||||
return {
|
||||
...unconfiguredKbSnapshot(),
|
||||
status: "error",
|
||||
errorMessage: "kb.status unavailable"
|
||||
};
|
||||
}
|
||||
async function reloadKbStatus(call, signal) {
|
||||
const result = await call(NETXOPS_RPC_CHANNEL2, KB_RELOAD_ENDPOINT, {}, signal);
|
||||
if (asRpcOk(result)) {
|
||||
return asKbSnapshot(result.value);
|
||||
}
|
||||
return fetchKbStatus(call, signal);
|
||||
}
|
||||
async function resolveKbPath(call, path, signal) {
|
||||
let result = await call(NETXOPS_RPC_CHANNEL2, KB_RESOLVE_ENDPOINT, { path }, signal);
|
||||
if (!(result !== null && typeof result === "object" && result.ok === true)) {
|
||||
if (!asRpcOk(result)) {
|
||||
result = await call(NETXOPS_RPC_CHANNEL2, KB_RESOLVE_ENDPOINT, { args: { path } }, signal);
|
||||
}
|
||||
if (result !== null && typeof result === "object" && result.ok === true) {
|
||||
if (asRpcOk(result)) {
|
||||
return asKbSnapshot(result.value);
|
||||
}
|
||||
return unconfiguredKbSnapshot();
|
||||
return {
|
||||
...unconfiguredKbSnapshot(),
|
||||
status: "error",
|
||||
errorMessage: "kb.resolve unavailable"
|
||||
};
|
||||
}
|
||||
function kbStatusTone(status) {
|
||||
if (status === "configured")
|
||||
|
|
@ -1886,7 +1909,7 @@ class CardForm {
|
|||
}
|
||||
async store(field, value) {
|
||||
await this.scope.set(field, value);
|
||||
return this.userLayer()?.[field] === value;
|
||||
return this.sectionValue(field) === value || this.userLayer()?.[field] === value;
|
||||
}
|
||||
stage(field, edit) {
|
||||
this.staged.set(field, edit);
|
||||
|
|
@ -1945,7 +1968,9 @@ class NetxopsCardController {
|
|||
kbDirectoryPicker;
|
||||
kbBrowseInFlight = false;
|
||||
kbStatusInFlight = false;
|
||||
kbReloadInFlight = false;
|
||||
kbUiError = null;
|
||||
lastSyncedKbRoot = undefined;
|
||||
imDeliveryCatalog = { ...EMPTY_IM_DELIVERY_CATALOG };
|
||||
sessionsExportStatus = null;
|
||||
sessionsExportBusy = false;
|
||||
|
|
@ -1983,6 +2008,7 @@ class NetxopsCardController {
|
|||
scope.subscribe(() => {
|
||||
this.readCredential();
|
||||
this.refreshKbPreview();
|
||||
this.syncKbRootFromSettings();
|
||||
});
|
||||
this.readCredential();
|
||||
}
|
||||
|
|
@ -2026,7 +2052,7 @@ class NetxopsCardController {
|
|||
}
|
||||
this.startStatusPoll();
|
||||
this.refreshAlarmPushStatus();
|
||||
this.refreshKbStatus();
|
||||
this.reloadKbFromHost();
|
||||
this.refreshKbPreview();
|
||||
this.refreshImDeliveryCatalog();
|
||||
this.refreshSessionsExportStatus();
|
||||
|
|
@ -2114,15 +2140,42 @@ class NetxopsCardController {
|
|||
this.kbStatusInFlight = true;
|
||||
try {
|
||||
const next = await fetchKbStatus(call);
|
||||
const prev = this.kbStatus;
|
||||
if (prev && prev.status === next.status && prev.realRoot === next.realRoot && prev.operatorName === next.operatorName && prev.country === next.country && prev.version === next.version && prev.errorMessage === next.errorMessage)
|
||||
return;
|
||||
this.kbStatus = next;
|
||||
this.store.set(this.projection());
|
||||
this.applyKbSnapshot(next);
|
||||
} catch {} finally {
|
||||
this.kbStatusInFlight = false;
|
||||
}
|
||||
}
|
||||
async reloadKbFromHost() {
|
||||
const call = this.rpcCall;
|
||||
if (call === undefined || this.kbReloadInFlight)
|
||||
return;
|
||||
this.kbReloadInFlight = true;
|
||||
try {
|
||||
const next = await reloadKbStatus(call);
|
||||
this.applyKbSnapshot(next);
|
||||
this.lastSyncedKbRoot = (this.scope.getSnapshot().value?.kbRoot ?? "").trim();
|
||||
} catch {
|
||||
this.refreshKbStatus();
|
||||
} finally {
|
||||
this.kbReloadInFlight = false;
|
||||
}
|
||||
}
|
||||
applyKbSnapshot(next) {
|
||||
const prev = this.kbStatus;
|
||||
if (prev && prev.status === next.status && prev.realRoot === next.realRoot && prev.operatorName === next.operatorName && prev.country === next.country && prev.version === next.version && prev.errorMessage === next.errorMessage)
|
||||
return;
|
||||
this.kbStatus = next;
|
||||
this.store.set(this.projection());
|
||||
}
|
||||
async syncKbRootFromSettings() {
|
||||
if (this.rpcCall === undefined)
|
||||
return;
|
||||
const saved = (this.scope.getSnapshot().value?.kbRoot ?? "").trim();
|
||||
if (saved === this.lastSyncedKbRoot)
|
||||
return;
|
||||
this.lastSyncedKbRoot = saved;
|
||||
await this.reloadKbFromHost();
|
||||
}
|
||||
async refreshKbPreview() {
|
||||
const call = this.rpcCall;
|
||||
if (call === undefined)
|
||||
|
|
@ -2145,6 +2198,30 @@ class NetxopsCardController {
|
|||
this.store.set(this.projection());
|
||||
} catch {}
|
||||
}
|
||||
async commitKbRoot(path) {
|
||||
const trimmed = path.trim();
|
||||
this.kbUiError = null;
|
||||
this.store.set(this.projection());
|
||||
await this.form.save();
|
||||
const shell = this.form.shell();
|
||||
const saved = (this.scope.getSnapshot().value?.kbRoot ?? "").trim();
|
||||
if (shell.failed || saved !== trimmed) {
|
||||
this.kbUiError = saved === trimmed ? "kbRoot save failed" : `kbRoot save did not land (saved="${saved || "(empty)"}")`;
|
||||
this.store.set(this.projection());
|
||||
const call = this.rpcCall;
|
||||
if (call !== undefined && trimmed !== "") {
|
||||
try {
|
||||
this.kbPreview = await resolveKbPath(call, trimmed);
|
||||
this.store.set(this.projection());
|
||||
} catch {}
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.kbPreview = null;
|
||||
this.lastSyncedKbRoot = undefined;
|
||||
await this.reloadKbFromHost();
|
||||
this.refreshKbPreview();
|
||||
}
|
||||
browseKbRoot() {
|
||||
if (this.kbBrowseInFlight)
|
||||
return;
|
||||
|
|
@ -2153,11 +2230,7 @@ class NetxopsCardController {
|
|||
this.store.set(this.projection());
|
||||
const applyPath = (path) => {
|
||||
this.form.actions().edit("kbRoot", path);
|
||||
this.refreshKbPreview();
|
||||
this.form.save().then(() => {
|
||||
this.refreshKbStatus();
|
||||
this.refreshKbPreview();
|
||||
});
|
||||
this.commitKbRoot(path);
|
||||
};
|
||||
const fail = (error) => {
|
||||
this.kbUiError = error instanceof Error ? error.message : String(error);
|
||||
|
|
@ -2406,7 +2479,7 @@ var en = {
|
|||
kbBrowse: "Browse…",
|
||||
kbBrowseUnavailable: "Directory picker unavailable — paste an absolute folder path, then Save.",
|
||||
kbBrowseFailed: "Browse failed: {detail}",
|
||||
kbSaveHint: "Browse writes and saves automatically. Manual paste still needs Save.",
|
||||
kbSaveHint: "Browse writes and saves automatically, then reloads Host KB status. Manual paste still needs Save.",
|
||||
kbStatusConfigured: "Knowledge base: {operator} ({country}) v{version}",
|
||||
kbStatusUnconfigured: "Knowledge base: not configured (pure netx)",
|
||||
kbStatusError: "Knowledge base: error — {detail}",
|
||||
|
|
@ -2483,7 +2556,7 @@ var zh = {
|
|||
kbBrowse: "浏览…",
|
||||
kbBrowseUnavailable: "目录选择器不可用 — 请粘贴绝对目录路径后点保存。",
|
||||
kbBrowseFailed: "浏览失败:{detail}",
|
||||
kbSaveHint: "点「浏览」选目录后会自动写入并保存;若是手粘路径仍需点保存。",
|
||||
kbSaveHint: "点「浏览」选目录后会自动写入、保存,并刷新宿主知识库状态;若是手粘路径仍需点保存。",
|
||||
kbStatusConfigured: "知识库: {operator}({country}) v{version}",
|
||||
kbStatusUnconfigured: "知识库: 未配置(纯 netx)",
|
||||
kbStatusError: "知识库: 错误 — {detail}",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue