Merge pull request #99 from simon-ami/main
Add "Restore Default Claude CLI" button to web console settings
This commit is contained in:
@@ -9,6 +9,7 @@ window.Components.claudeConfig = () => ({
|
|||||||
configPath: '', // Dynamic path from backend
|
configPath: '', // Dynamic path from backend
|
||||||
models: [],
|
models: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
|
restoring: false,
|
||||||
gemini1mSuffix: false,
|
gemini1mSuffix: false,
|
||||||
|
|
||||||
// Model fields that may contain Gemini model names
|
// Model fields that may contain Gemini model names
|
||||||
@@ -141,5 +142,34 @@ window.Components.claudeConfig = () => ({
|
|||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
restoreDefaultClaudeConfig() {
|
||||||
|
document.getElementById('restore_defaults_modal').showModal();
|
||||||
|
},
|
||||||
|
|
||||||
|
async executeRestore() {
|
||||||
|
this.restoring = true;
|
||||||
|
const password = Alpine.store('global').webuiPassword;
|
||||||
|
try {
|
||||||
|
const { response, newPassword } = await window.utils.request('/api/claude/config/restore', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
}, password);
|
||||||
|
if (newPassword) Alpine.store('global').webuiPassword = newPassword;
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||||
|
Alpine.store('global').showToast(Alpine.store('global').t('claudeConfigRestored'), 'success');
|
||||||
|
|
||||||
|
// Close modal
|
||||||
|
document.getElementById('restore_defaults_modal').close();
|
||||||
|
|
||||||
|
// Reload the config to reflect the changes
|
||||||
|
await this.fetchConfig();
|
||||||
|
} catch (e) {
|
||||||
|
Alpine.store('global').showToast(Alpine.store('global').t('restoreConfigFailed') + ': ' + e.message, 'error');
|
||||||
|
} finally {
|
||||||
|
this.restoring = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -157,7 +157,13 @@ document.addEventListener('alpine:init', () => {
|
|||||||
accountsReloaded: "Accounts reloaded",
|
accountsReloaded: "Accounts reloaded",
|
||||||
reloadFailed: "Reload failed",
|
reloadFailed: "Reload failed",
|
||||||
claudeConfigSaved: "Claude configuration saved",
|
claudeConfigSaved: "Claude configuration saved",
|
||||||
|
claudeConfigRestored: "Claude CLI restored to defaults",
|
||||||
saveConfigFailed: "Failed to save configuration",
|
saveConfigFailed: "Failed to save configuration",
|
||||||
|
restoreConfigFailed: "Failed to restore configuration",
|
||||||
|
restoreDefault: "Restore Default",
|
||||||
|
confirmRestoreTitle: "Confirm Restore",
|
||||||
|
confirmRestoreMessage: "Are you sure you want to restore Claude CLI to default settings? This will remove proxy configuration.",
|
||||||
|
confirmRestore: "Confirm Restore",
|
||||||
claudeActive: "Claude Active",
|
claudeActive: "Claude Active",
|
||||||
claudeEmpty: "Claude Empty",
|
claudeEmpty: "Claude Empty",
|
||||||
geminiActive: "Gemini Active",
|
geminiActive: "Gemini Active",
|
||||||
@@ -406,7 +412,13 @@ document.addEventListener('alpine:init', () => {
|
|||||||
accountsReloaded: "账号配置已重载",
|
accountsReloaded: "账号配置已重载",
|
||||||
reloadFailed: "重载失败",
|
reloadFailed: "重载失败",
|
||||||
claudeConfigSaved: "Claude 配置已保存",
|
claudeConfigSaved: "Claude 配置已保存",
|
||||||
|
claudeConfigRestored: "Claude CLI 已恢复默认设置",
|
||||||
saveConfigFailed: "保存配置失败",
|
saveConfigFailed: "保存配置失败",
|
||||||
|
restoreConfigFailed: "恢复配置失败",
|
||||||
|
restoreDefault: "恢复默认",
|
||||||
|
confirmRestoreTitle: "确认恢复",
|
||||||
|
confirmRestoreMessage: "确定要将 Claude CLI 恢复为默认设置吗?这将移除代理配置。",
|
||||||
|
confirmRestore: "确认恢复",
|
||||||
claudeActive: "Claude 活跃",
|
claudeActive: "Claude 活跃",
|
||||||
claudeEmpty: "Claude 耗尽",
|
claudeEmpty: "Claude 耗尽",
|
||||||
geminiActive: "Gemini 活跃",
|
geminiActive: "Gemini 活跃",
|
||||||
|
|||||||
@@ -523,7 +523,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-end pt-2">
|
<div class="flex justify-end pt-2 gap-3">
|
||||||
|
<button class="btn btn-sm btn-ghost border border-space-border/50 hover:border-red-500/30 hover:bg-red-500/5 text-gray-400 hover:text-red-400 px-6 gap-2"
|
||||||
|
@click="restoreDefaultClaudeConfig" :disabled="restoring">
|
||||||
|
<svg x-show="!restoring" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||||
|
</svg>
|
||||||
|
<span x-show="!restoring" x-text="$store.global.t('restoreDefault')">Restore Default</span>
|
||||||
|
<span x-show="restoring" class="loading loading-spinner loading-xs"></span>
|
||||||
|
</button>
|
||||||
<button class="btn btn-sm bg-neon-purple hover:bg-purple-600 border-none text-white px-6 gap-2"
|
<button class="btn btn-sm bg-neon-purple hover:bg-purple-600 border-none text-white px-6 gap-2"
|
||||||
@click="saveClaudeConfig" :disabled="loading">
|
@click="saveClaudeConfig" :disabled="loading">
|
||||||
<svg x-show="!loading" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg x-show="!loading" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
@@ -534,6 +543,33 @@
|
|||||||
<span x-show="loading" class="loading loading-spinner loading-xs"></span>
|
<span x-show="loading" class="loading loading-spinner loading-xs"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Restore Defaults Confirmation Modal -->
|
||||||
|
<dialog id="restore_defaults_modal" class="modal">
|
||||||
|
<div class="modal-box bg-space-900 border-2 border-red-500/50">
|
||||||
|
<h3 class="font-bold text-lg text-red-400 flex items-center gap-2">
|
||||||
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||||
|
</svg>
|
||||||
|
<span x-text="$store.global.t('confirmRestoreTitle')">Confirm Restore</span>
|
||||||
|
</h3>
|
||||||
|
<p class="py-4 text-gray-300" x-text="$store.global.t('confirmRestoreMessage')">
|
||||||
|
Are you sure you want to restore Claude CLI to default settings? This will remove proxy configuration.
|
||||||
|
</p>
|
||||||
|
<div class="modal-action">
|
||||||
|
<button class="btn btn-ghost text-gray-400" onclick="document.getElementById('restore_defaults_modal').close()"
|
||||||
|
x-text="$store.global.t('cancel')">Cancel</button>
|
||||||
|
<button class="btn bg-red-500 hover:bg-red-600 border-none text-white" @click="executeRestore()"
|
||||||
|
:disabled="restoring"
|
||||||
|
:class="{ 'loading': restoring }">
|
||||||
|
<span x-text="$store.global.t('confirmRestore')" x-show="!restoring">Confirm Restore</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form method="dialog" class="modal-backdrop">
|
||||||
|
<button>close</button>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tab 3: Models Configuration -->
|
<!-- Tab 3: Models Configuration -->
|
||||||
|
|||||||
@@ -83,6 +83,35 @@ export async function updateClaudeConfig(updates) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the global Claude CLI configuration entirely
|
||||||
|
* Unlike updateClaudeConfig, this replaces the config instead of merging.
|
||||||
|
*
|
||||||
|
* @param {Object} config - The new configuration to write
|
||||||
|
* @returns {Promise<Object>} The written configuration
|
||||||
|
*/
|
||||||
|
export async function replaceClaudeConfig(config) {
|
||||||
|
const configPath = getClaudeConfigPath();
|
||||||
|
|
||||||
|
// 1. Ensure .claude directory exists
|
||||||
|
const configDir = path.dirname(configPath);
|
||||||
|
try {
|
||||||
|
await fs.mkdir(configDir, { recursive: true });
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore if exists
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Write config directly (no merge)
|
||||||
|
try {
|
||||||
|
await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf8');
|
||||||
|
logger.info(`[ClaudeConfig] Replaced config at ${configPath}`);
|
||||||
|
return config;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[ClaudeConfig] Failed to write config:`, error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple deep merge for objects
|
* Simple deep merge for objects
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { fileURLToPath } from 'url';
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { getPublicConfig, saveConfig, config } from '../config.js';
|
import { getPublicConfig, saveConfig, config } from '../config.js';
|
||||||
import { DEFAULT_PORT, ACCOUNT_CONFIG_PATH } from '../constants.js';
|
import { DEFAULT_PORT, ACCOUNT_CONFIG_PATH } from '../constants.js';
|
||||||
import { readClaudeConfig, updateClaudeConfig, getClaudeConfigPath } from '../utils/claude-config.js';
|
import { readClaudeConfig, updateClaudeConfig, replaceClaudeConfig, getClaudeConfigPath } from '../utils/claude-config.js';
|
||||||
import { logger } from '../utils/logger.js';
|
import { logger } from '../utils/logger.js';
|
||||||
import { getAuthorizationUrl, completeOAuthFlow, startCallbackServer } from '../auth/oauth.js';
|
import { getAuthorizationUrl, completeOAuthFlow, startCallbackServer } from '../auth/oauth.js';
|
||||||
import { loadAccounts, saveAccounts } from '../account-manager/storage.js';
|
import { loadAccounts, saveAccounts } from '../account-manager/storage.js';
|
||||||
@@ -438,6 +438,52 @@ export function mountWebUI(app, dirname, accountManager) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /api/claude/config/restore - Restore Claude CLI to default (remove proxy settings)
|
||||||
|
*/
|
||||||
|
app.post('/api/claude/config/restore', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const claudeConfig = await readClaudeConfig();
|
||||||
|
|
||||||
|
// Proxy-related environment variables to remove when restoring defaults
|
||||||
|
const PROXY_ENV_VARS = [
|
||||||
|
'ANTHROPIC_BASE_URL',
|
||||||
|
'ANTHROPIC_AUTH_TOKEN',
|
||||||
|
'ANTHROPIC_MODEL',
|
||||||
|
'CLAUDE_CODE_SUBAGENT_MODEL',
|
||||||
|
'ANTHROPIC_DEFAULT_OPUS_MODEL',
|
||||||
|
'ANTHROPIC_DEFAULT_SONNET_MODEL',
|
||||||
|
'ANTHROPIC_DEFAULT_HAIKU_MODEL',
|
||||||
|
'ENABLE_EXPERIMENTAL_MCP_CLI'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Remove proxy-related environment variables to restore defaults
|
||||||
|
if (claudeConfig.env) {
|
||||||
|
for (const key of PROXY_ENV_VARS) {
|
||||||
|
delete claudeConfig.env[key];
|
||||||
|
}
|
||||||
|
// Remove env entirely if empty to truly restore defaults
|
||||||
|
if (Object.keys(claudeConfig.env).length === 0) {
|
||||||
|
delete claudeConfig.env;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use replaceClaudeConfig to completely overwrite the config (not merge)
|
||||||
|
const newConfig = await replaceClaudeConfig(claudeConfig);
|
||||||
|
|
||||||
|
logger.info(`[WebUI] Restored Claude CLI config to defaults at ${getClaudeConfigPath()}`);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
status: 'ok',
|
||||||
|
config: newConfig,
|
||||||
|
message: 'Claude CLI configuration restored to defaults'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[WebUI] Error restoring Claude config:', error);
|
||||||
|
res.status(500).json({ status: 'error', error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/models/config - Update model configuration (hidden/pinned/alias)
|
* POST /api/models/config - Update model configuration (hidden/pinned/alias)
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user