diff --git a/src/pages/services.js b/src/pages/services.js index 9bb3a20..540617b 100644 --- a/src/pages/services.js +++ b/src/pages/services.js @@ -34,6 +34,17 @@ export async function render() {
npm 源设置
+
配置备份
备份范围:openclaw.json 主配置文件(含模型、Provider、Gateway 设置)。Agent 数据和记忆文件不在此备份范围内。
@@ -56,7 +67,7 @@ export async function render() { } async function loadAll(page) { - const tasks = [loadVersion(page), loadServices(page), loadBackups(page)] + const tasks = [loadVersion(page), loadServices(page), loadBackups(page), loadConfigEditor(page)] if (!isInDocker()) tasks.push(loadRegistry(page)) await Promise.all(tasks) } @@ -269,6 +280,15 @@ function bindEvents(page) { case 'restart': await handleServiceAction(action, btn.dataset.label, page) break + case 'save-config': + await handleSaveConfig(page, true) + break + case 'save-config-only': + await handleSaveConfig(page, false) + break + case 'reload-config': + await loadConfigEditor(page) + break case 'create-backup': await handleCreateBackup(page) break @@ -423,6 +443,100 @@ async function handleDeleteBackup(name, page) { await loadBackups(page) } +// ===== 配置文件编辑器 ===== + +let _configOriginal = '' + +async function loadConfigEditor(page) { + const section = page.querySelector('#config-editor-section') + const area = page.querySelector('#config-editor-area') + const status = page.querySelector('#config-editor-status') + const btnSave = page.querySelector('[data-action="save-config"]') + const btnSaveOnly = page.querySelector('[data-action="save-config-only"]') + + try { + const config = await api.readOpenclawConfig() + const json = JSON.stringify(config, null, 2) + _configOriginal = json + area.value = json + area.disabled = false + btnSave.disabled = false + btnSaveOnly.disabled = false + section.style.display = '' + status.innerHTML = `已加载 · ${(json.length / 1024).toFixed(1)} KB` + + // 实时检测 JSON 语法 + area.oninput = () => { + try { + JSON.parse(area.value) + const changed = area.value !== _configOriginal + status.innerHTML = changed + ? '● 有未保存的修改' + : '无修改' + btnSave.disabled = !changed + btnSaveOnly.disabled = !changed + } catch (e) { + status.innerHTML = `JSON 语法错误: ${e.message.split(' at ')[0]}` + btnSave.disabled = true + btnSaveOnly.disabled = true + } + } + } catch { + // openclaw.json 不存在,隐藏编辑器 + section.style.display = 'none' + } +} + +async function handleSaveConfig(page, restart) { + const area = page.querySelector('#config-editor-area') + const status = page.querySelector('#config-editor-status') + + let config + try { + config = JSON.parse(area.value) + } catch (e) { + toast('JSON 格式错误,无法保存', 'error') + return + } + + status.innerHTML = '自动备份中...' + + try { + // 保存前自动备份 + await api.createBackup() + } catch (e) { + const yes = await showConfirm('自动备份失败: ' + e + '\n\n是否仍然继续保存?') + if (!yes) return + } + + status.innerHTML = '保存中...' + + try { + await api.writeOpenclawConfig(config) + _configOriginal = area.value + toast('配置已保存' + (restart ? ',正在重启 Gateway...' : ''), 'success') + status.innerHTML = '已保存' + + page.querySelector('[data-action="save-config"]').disabled = true + page.querySelector('[data-action="save-config-only"]').disabled = true + + if (restart) { + try { + await api.restartGateway() + toast('Gateway 已重启', 'success') + } catch (e) { + toast('配置已保存,但 Gateway 重启失败: ' + e, 'warning') + } + await loadServices(page) + } + + await loadBackups(page) + } catch (e) { + toast('保存失败: ' + e, 'error') + status.innerHTML = `保存失败: ${e}` + } +} + // ===== 升级操作 ===== async function doUpgradeWithModal(source, page) {