mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 16:30:57 +00:00
fix(ui): clear pending config updates
This commit is contained in:
@@ -42,6 +42,7 @@ import {
|
||||
findAgentConfigEntryIndex,
|
||||
loadConfig,
|
||||
openConfigFile,
|
||||
resetConfigPendingChanges,
|
||||
runUpdate,
|
||||
saveConfig,
|
||||
updateConfigFormValue,
|
||||
@@ -856,6 +857,7 @@ export function renderApp(state: AppViewState) {
|
||||
onFormPatch: (path: Array<string | number>, value: unknown) =>
|
||||
updateConfigFormValue(state, path, value),
|
||||
onReload: () => loadConfig(state),
|
||||
onReset: () => resetConfigPendingChanges(state),
|
||||
onSave: () => saveConfig(state),
|
||||
onApply: () => applyConfig(state),
|
||||
onUpdate: () => runUpdate(state),
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
applyConfig,
|
||||
ensureAgentConfigEntry,
|
||||
findAgentConfigEntryIndex,
|
||||
resetConfigPendingChanges,
|
||||
runUpdate,
|
||||
saveConfig,
|
||||
updateConfigFormValue,
|
||||
@@ -163,6 +164,29 @@ describe("updateConfigFormValue", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("resetConfigPendingChanges", () => {
|
||||
it("restores the original form and raw config snapshot", () => {
|
||||
const state = createState();
|
||||
state.configSnapshot = {
|
||||
config: { gateway: { mode: "local" } },
|
||||
valid: true,
|
||||
issues: [],
|
||||
raw: '{\n "gateway": { "mode": "local" }\n}\n',
|
||||
};
|
||||
state.configFormOriginal = { gateway: { mode: "local" } };
|
||||
state.configRawOriginal = '{\n "gateway": { "mode": "local" }\n}\n';
|
||||
state.configForm = { gateway: { mode: "remote", port: 3000 } };
|
||||
state.configRaw = '{\n "gateway": { "mode": "remote", "port": 3000 }\n}\n';
|
||||
state.configFormDirty = true;
|
||||
|
||||
resetConfigPendingChanges(state);
|
||||
|
||||
expect(state.configFormDirty).toBe(false);
|
||||
expect(state.configForm).toEqual({ gateway: { mode: "local" } });
|
||||
expect(state.configRaw).toBe('{\n "gateway": { "mode": "local" }\n}\n');
|
||||
});
|
||||
});
|
||||
|
||||
describe("agent config helpers", () => {
|
||||
it("finds explicit agent entries", () => {
|
||||
expect(
|
||||
|
||||
@@ -218,6 +218,16 @@ export function updateConfigFormValue(
|
||||
mutateConfigForm(state, (draft) => setPathValue(draft, path, value));
|
||||
}
|
||||
|
||||
export function resetConfigPendingChanges(state: ConfigState) {
|
||||
state.configForm = cloneConfigObject(
|
||||
state.configFormOriginal ?? state.configSnapshot?.config ?? {},
|
||||
);
|
||||
state.configRaw =
|
||||
state.configRawOriginal ||
|
||||
serializeConfigForm(state.configFormOriginal ?? state.configSnapshot?.config ?? {});
|
||||
state.configFormDirty = false;
|
||||
}
|
||||
|
||||
export function removeConfigFormValue(state: ConfigState, path: Array<string | number>) {
|
||||
mutateConfigForm(state, (draft) => removePathValue(draft, path));
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ describe("config view", () => {
|
||||
onSearchChange: vi.fn(),
|
||||
onSectionChange: vi.fn(),
|
||||
onReload: vi.fn(),
|
||||
onReset: vi.fn(),
|
||||
onSave: vi.fn(),
|
||||
onApply: vi.fn(),
|
||||
onUpdate: vi.fn(),
|
||||
@@ -49,11 +50,13 @@ describe("config view", () => {
|
||||
});
|
||||
|
||||
function findActionButtons(container: HTMLElement): {
|
||||
clearButton?: HTMLButtonElement;
|
||||
saveButton?: HTMLButtonElement;
|
||||
applyButton?: HTMLButtonElement;
|
||||
} {
|
||||
const buttons = Array.from(container.querySelectorAll("button"));
|
||||
return {
|
||||
clearButton: buttons.find((btn) => btn.textContent?.trim() === "Clear pending updates"),
|
||||
saveButton: buttons.find((btn) => btn.textContent?.trim() === "Save"),
|
||||
applyButton: buttons.find((btn) => btn.textContent?.trim() === "Apply"),
|
||||
};
|
||||
@@ -129,22 +132,30 @@ describe("config view", () => {
|
||||
raw: "{\n}\n",
|
||||
originalRaw: "{\n}\n",
|
||||
});
|
||||
({ saveButton, applyButton } = findActionButtons(container));
|
||||
let { clearButton, saveButton, applyButton } = findActionButtons(container);
|
||||
expect(clearButton).not.toBeUndefined();
|
||||
expect(saveButton).not.toBeUndefined();
|
||||
expect(applyButton).not.toBeUndefined();
|
||||
expect(clearButton?.disabled).toBe(true);
|
||||
expect(saveButton?.disabled).toBe(true);
|
||||
expect(applyButton?.disabled).toBe(true);
|
||||
|
||||
const onReset = vi.fn();
|
||||
renderCase({
|
||||
formMode: "raw",
|
||||
raw: '{\n gateway: { mode: "local" }\n}\n',
|
||||
originalRaw: "{\n}\n",
|
||||
onReset,
|
||||
});
|
||||
({ saveButton, applyButton } = findActionButtons(container));
|
||||
({ clearButton, saveButton, applyButton } = findActionButtons(container));
|
||||
expect(saveButton).not.toBeUndefined();
|
||||
expect(applyButton).not.toBeUndefined();
|
||||
expect(clearButton?.disabled).toBe(false);
|
||||
expect(saveButton?.disabled).toBe(false);
|
||||
expect(applyButton?.disabled).toBe(false);
|
||||
|
||||
clearButton?.click();
|
||||
expect(onReset).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("switches mode via the sidebar toggle", () => {
|
||||
|
||||
@@ -52,6 +52,7 @@ export type ConfigProps = {
|
||||
onSectionChange: (section: string | null) => void;
|
||||
onSubsectionChange: (section: string | null) => void;
|
||||
onReload: () => void;
|
||||
onReset: () => void;
|
||||
onSave: () => void;
|
||||
onApply: () => void;
|
||||
onUpdate: () => void;
|
||||
@@ -959,6 +960,9 @@ export function renderConfig(props: ConfigProps) {
|
||||
<button class="btn btn--sm" ?disabled=${props.loading} @click=${props.onReload}>
|
||||
${props.loading ? t("common.loading") : t("common.reload")}
|
||||
</button>
|
||||
<button class="btn btn--sm" ?disabled=${!hasChanges} @click=${props.onReset}>
|
||||
Clear pending updates
|
||||
</button>
|
||||
<button class="btn btn--sm primary" ?disabled=${!canSave} @click=${props.onSave}>
|
||||
${props.saving ? "Saving…" : "Save"}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user