mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-03 03:10:50 +08:00
feat(config-ui): add YAML config editor with save/reload support
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -23,3 +23,4 @@ Thumbs.db
|
||||
|
||||
CLAUDE.md
|
||||
AGENTS.md
|
||||
.serena
|
||||
293
app.js
293
app.js
@@ -26,6 +26,18 @@ class CLIProxyManager {
|
||||
// 主题管理
|
||||
this.currentTheme = 'light';
|
||||
|
||||
// 配置文件编辑器状态
|
||||
this.configYamlCache = '';
|
||||
this.isConfigEditorDirty = false;
|
||||
this.configEditorElements = {
|
||||
textarea: null,
|
||||
editorInstance: null,
|
||||
saveBtn: null,
|
||||
reloadBtn: null,
|
||||
statusEl: null
|
||||
};
|
||||
this.lastConfigFetchUrl = null;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
@@ -113,6 +125,8 @@ class CLIProxyManager {
|
||||
this.setupNavigation();
|
||||
this.setupLanguageSwitcher();
|
||||
this.setupThemeSwitcher();
|
||||
this.setupConfigEditor();
|
||||
this.updateConfigEditorAvailability();
|
||||
// loadSettings 将在登录成功后调用
|
||||
this.updateLoginConnectionInfo();
|
||||
// 检查主机名,如果不是 localhost 或 127.0.0.1,则隐藏 OAuth 登录框
|
||||
@@ -840,6 +854,9 @@ class CLIProxyManager {
|
||||
// 如果点击的是日志查看页面,自动加载日志
|
||||
if (sectionId === 'logs') {
|
||||
this.refreshLogs(false);
|
||||
} else if (sectionId === 'config-management') {
|
||||
this.loadConfigFileEditor();
|
||||
this.refreshConfigEditor();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -871,6 +888,272 @@ class CLIProxyManager {
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化配置文件编辑器
|
||||
setupConfigEditor() {
|
||||
const textarea = document.getElementById('config-editor');
|
||||
const saveBtn = document.getElementById('config-save-btn');
|
||||
const reloadBtn = document.getElementById('config-reload-btn');
|
||||
const statusEl = document.getElementById('config-editor-status');
|
||||
|
||||
this.configEditorElements = {
|
||||
textarea,
|
||||
editorInstance: null,
|
||||
saveBtn,
|
||||
reloadBtn,
|
||||
statusEl
|
||||
};
|
||||
|
||||
if (!textarea || !saveBtn || !reloadBtn || !statusEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.CodeMirror) {
|
||||
const editorInstance = window.CodeMirror.fromTextArea(textarea, {
|
||||
mode: 'yaml',
|
||||
theme: 'default',
|
||||
lineNumbers: true,
|
||||
indentUnit: 2,
|
||||
tabSize: 2,
|
||||
lineWrapping: true,
|
||||
autoCloseBrackets: true,
|
||||
extraKeys: {
|
||||
'Ctrl-/': 'toggleComment',
|
||||
'Cmd-/': 'toggleComment'
|
||||
}
|
||||
});
|
||||
|
||||
editorInstance.setSize('100%', '100%');
|
||||
editorInstance.on('change', () => {
|
||||
this.isConfigEditorDirty = true;
|
||||
this.updateConfigEditorStatus('info', i18n.t('config_management.status_dirty'));
|
||||
});
|
||||
|
||||
this.configEditorElements.editorInstance = editorInstance;
|
||||
} else {
|
||||
textarea.addEventListener('input', () => {
|
||||
this.isConfigEditorDirty = true;
|
||||
this.updateConfigEditorStatus('info', i18n.t('config_management.status_dirty'));
|
||||
});
|
||||
}
|
||||
|
||||
saveBtn.addEventListener('click', () => this.saveConfigFile());
|
||||
reloadBtn.addEventListener('click', () => this.loadConfigFileEditor(true));
|
||||
|
||||
this.refreshConfigEditor();
|
||||
}
|
||||
|
||||
// 更新配置编辑器可用状态
|
||||
updateConfigEditorAvailability() {
|
||||
const { textarea, editorInstance, saveBtn, reloadBtn } = this.configEditorElements;
|
||||
if ((!textarea && !editorInstance) || !saveBtn || !reloadBtn) {
|
||||
return;
|
||||
}
|
||||
|
||||
const disabled = !this.isConnected;
|
||||
if (editorInstance) {
|
||||
editorInstance.setOption('readOnly', disabled ? 'nocursor' : false);
|
||||
const wrapper = editorInstance.getWrapperElement();
|
||||
if (wrapper) {
|
||||
wrapper.classList.toggle('cm-readonly', disabled);
|
||||
}
|
||||
} else if (textarea) {
|
||||
textarea.disabled = disabled;
|
||||
}
|
||||
|
||||
saveBtn.disabled = disabled;
|
||||
reloadBtn.disabled = disabled;
|
||||
|
||||
if (disabled) {
|
||||
this.updateConfigEditorStatus('info', i18n.t('config_management.status_disconnected'));
|
||||
}
|
||||
|
||||
this.refreshConfigEditor();
|
||||
}
|
||||
|
||||
refreshConfigEditor() {
|
||||
const instance = this.configEditorElements && this.configEditorElements.editorInstance;
|
||||
if (instance && typeof instance.refresh === 'function') {
|
||||
setTimeout(() => instance.refresh(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新配置编辑器状态显示
|
||||
updateConfigEditorStatus(type, message) {
|
||||
const statusEl = (this.configEditorElements && this.configEditorElements.statusEl) || document.getElementById('config-editor-status');
|
||||
if (!statusEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
statusEl.textContent = message;
|
||||
statusEl.classList.remove('success', 'error');
|
||||
|
||||
if (type === 'success') {
|
||||
statusEl.classList.add('success');
|
||||
} else if (type === 'error') {
|
||||
statusEl.classList.add('error');
|
||||
}
|
||||
}
|
||||
|
||||
// 加载配置文件内容
|
||||
async loadConfigFileEditor(forceRefresh = false) {
|
||||
const { textarea, editorInstance, reloadBtn } = this.configEditorElements;
|
||||
if (!textarea && !editorInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isConnected) {
|
||||
this.updateConfigEditorStatus('info', i18n.t('config_management.status_disconnected'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (reloadBtn) {
|
||||
reloadBtn.disabled = true;
|
||||
}
|
||||
this.updateConfigEditorStatus('info', i18n.t('config_management.status_loading'));
|
||||
|
||||
try {
|
||||
const yamlText = await this.fetchConfigFile(forceRefresh);
|
||||
|
||||
if (editorInstance) {
|
||||
editorInstance.setValue(yamlText || '');
|
||||
if (typeof editorInstance.markClean === 'function') {
|
||||
editorInstance.markClean();
|
||||
}
|
||||
} else if (textarea) {
|
||||
textarea.value = yamlText || '';
|
||||
}
|
||||
|
||||
this.isConfigEditorDirty = false;
|
||||
this.updateConfigEditorStatus('success', i18n.t('config_management.status_loaded'));
|
||||
this.refreshConfigEditor();
|
||||
} catch (error) {
|
||||
console.error('加载配置文件失败:', error);
|
||||
this.updateConfigEditorStatus('error', `${i18n.t('config_management.status_load_failed')}: ${error.message}`);
|
||||
} finally {
|
||||
if (reloadBtn) {
|
||||
reloadBtn.disabled = !this.isConnected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取配置文件内容
|
||||
async fetchConfigFile(forceRefresh = false) {
|
||||
if (!forceRefresh && this.configYamlCache) {
|
||||
return this.configYamlCache;
|
||||
}
|
||||
|
||||
const requestUrl = '/config.yaml';
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.apiUrl}${requestUrl}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.managementKey}`,
|
||||
'Accept': 'application/yaml'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text().catch(() => '');
|
||||
const message = errorText || `HTTP ${response.status}`;
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('content-type') || '';
|
||||
if (!/yaml/i.test(contentType)) {
|
||||
throw new Error(i18n.t('config_management.error_yaml_not_supported'));
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
this.lastConfigFetchUrl = requestUrl;
|
||||
this.configYamlCache = text;
|
||||
return text;
|
||||
} catch (error) {
|
||||
throw error instanceof Error ? error : new Error(String(error));
|
||||
}
|
||||
}
|
||||
|
||||
// 保存配置文件
|
||||
async saveConfigFile() {
|
||||
const { textarea, editorInstance, saveBtn, reloadBtn } = this.configEditorElements;
|
||||
if ((!textarea && !editorInstance) || !saveBtn) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isConnected) {
|
||||
this.updateConfigEditorStatus('error', i18n.t('config_management.status_disconnected'));
|
||||
return;
|
||||
}
|
||||
|
||||
const yamlText = editorInstance ? editorInstance.getValue() : (textarea ? textarea.value : '');
|
||||
|
||||
saveBtn.disabled = true;
|
||||
if (reloadBtn) {
|
||||
reloadBtn.disabled = true;
|
||||
}
|
||||
this.updateConfigEditorStatus('info', i18n.t('config_management.status_saving'));
|
||||
|
||||
try {
|
||||
try {
|
||||
await this.writeConfigFile('/config.yaml', yamlText);
|
||||
this.lastConfigFetchUrl = '/config.yaml';
|
||||
this.configYamlCache = yamlText;
|
||||
this.isConfigEditorDirty = false;
|
||||
if (editorInstance && typeof editorInstance.markClean === 'function') {
|
||||
editorInstance.markClean();
|
||||
}
|
||||
this.showNotification(i18n.t('config_management.save_success'), 'success');
|
||||
this.updateConfigEditorStatus('success', i18n.t('config_management.status_saved'));
|
||||
this.clearCache();
|
||||
await this.loadAllData(true);
|
||||
return;
|
||||
} catch (error) {
|
||||
const errorMessage = `${i18n.t('config_management.status_save_failed')}: ${error.message}`;
|
||||
this.updateConfigEditorStatus('error', errorMessage);
|
||||
this.showNotification(errorMessage, 'error');
|
||||
this.isConfigEditorDirty = true;
|
||||
}
|
||||
} finally {
|
||||
saveBtn.disabled = !this.isConnected;
|
||||
if (reloadBtn) {
|
||||
reloadBtn.disabled = !this.isConnected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 写入配置文件到指定端点
|
||||
async writeConfigFile(endpoint, yamlText) {
|
||||
const response = await fetch(`${this.apiUrl}${endpoint}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.managementKey}`,
|
||||
'Content-Type': 'application/yaml',
|
||||
'Accept': 'application/json, text/plain, */*'
|
||||
},
|
||||
body: yamlText
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const contentType = response.headers.get('content-type') || '';
|
||||
let errorText = '';
|
||||
if (contentType.includes('application/json')) {
|
||||
const data = await response.json().catch(() => ({}));
|
||||
errorText = data.message || data.error || '';
|
||||
} else {
|
||||
errorText = await response.text().catch(() => '');
|
||||
}
|
||||
throw new Error(errorText || `HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('content-type') || '';
|
||||
if (contentType.includes('application/json')) {
|
||||
const data = await response.json().catch(() => null);
|
||||
if (data && data.ok === false) {
|
||||
throw new Error(data.message || data.error || 'Server rejected the update');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 切换语言
|
||||
toggleLanguage() {
|
||||
const currentLang = i18n.currentLanguage;
|
||||
@@ -1044,6 +1327,8 @@ class CLIProxyManager {
|
||||
|
||||
lastUpdate.textContent = new Date().toLocaleString('zh-CN');
|
||||
|
||||
this.updateConfigEditorAvailability();
|
||||
|
||||
// 更新连接信息显示
|
||||
this.updateConnectionInfo();
|
||||
}
|
||||
@@ -1109,6 +1394,7 @@ class CLIProxyManager {
|
||||
clearCache() {
|
||||
this.configCache = null;
|
||||
this.cacheTimestamp = null;
|
||||
this.configYamlCache = '';
|
||||
}
|
||||
|
||||
// 启动状态更新定时器
|
||||
@@ -1147,6 +1433,10 @@ class CLIProxyManager {
|
||||
// 使用统计需要单独加载
|
||||
await this.loadUsageStats();
|
||||
|
||||
// 加载配置文件编辑器内容
|
||||
await this.loadConfigFileEditor(forceRefresh);
|
||||
this.refreshConfigEditor();
|
||||
|
||||
console.log('配置加载完成,使用缓存:', !forceRefresh && this.isCacheValid());
|
||||
} catch (error) {
|
||||
console.error('加载配置失败:', error);
|
||||
@@ -1241,6 +1531,9 @@ class CLIProxyManager {
|
||||
this.loadOpenAIProviders(),
|
||||
this.loadAuthFiles()
|
||||
]);
|
||||
|
||||
await this.loadConfigFileEditor(true);
|
||||
this.refreshConfigEditor();
|
||||
}
|
||||
|
||||
// 加载调试设置
|
||||
|
||||
38
i18n.js
38
i18n.js
@@ -82,6 +82,7 @@ const i18n = {
|
||||
'nav.ai_providers': 'AI 提供商',
|
||||
'nav.auth_files': '认证文件',
|
||||
'nav.usage_stats': '使用统计',
|
||||
'nav.config_management': '配置管理',
|
||||
'nav.logs': '日志查看',
|
||||
'nav.system_info': '系统信息',
|
||||
|
||||
@@ -329,6 +330,24 @@ const i18n = {
|
||||
'logs.upgrade_required_title': '需要升级 CLI Proxy API',
|
||||
'logs.upgrade_required_desc': '当前服务器版本不支持日志查看功能,请升级到最新版本的 CLI Proxy API 以使用此功能。',
|
||||
|
||||
// 配置管理
|
||||
'config_management.title': '配置管理',
|
||||
'config_management.editor_title': '配置文件',
|
||||
'config_management.reload': '重新加载',
|
||||
'config_management.save': '保存',
|
||||
'config_management.description': '查看并编辑服务器上的 config.yaml 配置文件。保存前请确认语法正确。',
|
||||
'config_management.status_idle': '等待操作',
|
||||
'config_management.status_loading': '加载配置中...',
|
||||
'config_management.status_loaded': '配置已加载',
|
||||
'config_management.status_dirty': '有未保存的更改',
|
||||
'config_management.status_disconnected': '请先连接服务器以加载配置',
|
||||
'config_management.status_load_failed': '加载失败',
|
||||
'config_management.status_saving': '正在保存配置...',
|
||||
'config_management.status_saved': '配置保存完成',
|
||||
'config_management.status_save_failed': '保存失败',
|
||||
'config_management.save_success': '配置已保存',
|
||||
'config_management.error_yaml_not_supported': '服务器未返回 YAML 格式,请确认 /config.yaml 接口可用',
|
||||
|
||||
// 系统信息
|
||||
'system_info.title': '系统信息',
|
||||
'system_info.connection_status_title': '连接状态',
|
||||
@@ -478,6 +497,7 @@ const i18n = {
|
||||
'nav.ai_providers': 'AI Providers',
|
||||
'nav.auth_files': 'Auth Files',
|
||||
'nav.usage_stats': 'Usage Statistics',
|
||||
'nav.config_management': 'Config Management',
|
||||
'nav.logs': 'Logs Viewer',
|
||||
'nav.system_info': 'System Info',
|
||||
|
||||
@@ -724,6 +744,24 @@ const i18n = {
|
||||
'logs.upgrade_required_title': 'Please Upgrade CLI Proxy API',
|
||||
'logs.upgrade_required_desc': 'The current server version does not support the logs viewing feature. Please upgrade to the latest version of CLI Proxy API to use this feature.',
|
||||
|
||||
// Config management
|
||||
'config_management.title': 'Config Management',
|
||||
'config_management.editor_title': 'Configuration File',
|
||||
'config_management.reload': 'Reload',
|
||||
'config_management.save': 'Save',
|
||||
'config_management.description': 'View and edit the server-side config.yaml file. Validate the syntax before saving.',
|
||||
'config_management.status_idle': 'Waiting for action',
|
||||
'config_management.status_loading': 'Loading configuration...',
|
||||
'config_management.status_loaded': 'Configuration loaded',
|
||||
'config_management.status_dirty': 'Unsaved changes',
|
||||
'config_management.status_disconnected': 'Connect to the server to load the configuration',
|
||||
'config_management.status_load_failed': 'Load failed',
|
||||
'config_management.status_saving': 'Saving configuration...',
|
||||
'config_management.status_saved': 'Configuration saved',
|
||||
'config_management.status_save_failed': 'Save failed',
|
||||
'config_management.save_success': 'Configuration saved successfully',
|
||||
'config_management.error_yaml_not_supported': 'Server did not return YAML. Verify the /config.yaml endpoint is available.',
|
||||
|
||||
// System info
|
||||
'system_info.title': 'System Information',
|
||||
'system_info.connection_status_title': 'Connection Status',
|
||||
|
||||
35
index.html
35
index.html
@@ -7,7 +7,13 @@
|
||||
<title data-i18n="title.login">CLI Proxy API Management Center</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/yaml/yaml.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/edit/closebrackets.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/comment/comment.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/js-yaml@4.1.0/dist/js-yaml.min.js"></script>
|
||||
<script src="i18n.js"></script>
|
||||
</head>
|
||||
|
||||
@@ -171,6 +177,9 @@
|
||||
<li data-tooltip="使用统计"><a href="#usage-stats" class="nav-item" data-section="usage-stats">
|
||||
<i class="fas fa-chart-line"></i> <span data-i18n="nav.usage_stats">使用统计</span>
|
||||
</a></li>
|
||||
<li data-tooltip="配置管理"><a href="#config-management" class="nav-item" data-section="config-management">
|
||||
<i class="fas fa-cog"></i> <span data-i18n="nav.config_management">配置管理</span>
|
||||
</a></li>
|
||||
<li id="logs-nav-item" data-tooltip="日志查看" style="display: none;"><a href="#logs" class="nav-item" data-section="logs">
|
||||
<i class="fas fa-scroll"></i> <span data-i18n="nav.logs">日志查看</span>
|
||||
</a></li>
|
||||
@@ -766,6 +775,32 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 配置管理 -->
|
||||
<section id="config-management" class="content-section">
|
||||
<h2 data-i18n="config_management.title">配置管理</h2>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-file-code"></i> <span data-i18n="config_management.editor_title">配置文件</span></h3>
|
||||
<div class="editor-actions">
|
||||
<button id="config-reload-btn" class="btn btn-secondary">
|
||||
<i class="fas fa-sync-alt"></i> <span data-i18n="config_management.reload">重新加载</span>
|
||||
</button>
|
||||
<button id="config-save-btn" class="btn btn-primary">
|
||||
<i class="fas fa-save"></i> <span data-i18n="config_management.save">保存</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<p class="form-hint" data-i18n="config_management.description">查看并编辑服务器上的 config.yaml 配置文件。保存前请确认语法正确。</p>
|
||||
<div class="yaml-editor-container">
|
||||
<textarea id="config-editor" class="yaml-editor" spellcheck="false" placeholder="key: value"></textarea>
|
||||
<div id="config-editor-status" class="editor-status" data-i18n="config_management.status_idle">等待操作</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 系统信息 -->
|
||||
<section id="system-info" class="content-section">
|
||||
<h2 data-i18n="system_info.title">系统信息</h2>
|
||||
|
||||
130
styles.css
130
styles.css
@@ -1278,6 +1278,133 @@ textarea::placeholder {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* 配置管理编辑器 */
|
||||
.editor-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.editor-actions .btn {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.yaml-editor-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
#config-management .card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: calc(100vh - 360px);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
#config-management .card-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#config-management .yaml-editor-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.yaml-editor {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
min-height: 360px;
|
||||
border: 1px solid var(--border-primary);
|
||||
border-radius: 6px;
|
||||
padding: 12px 14px;
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
overflow-y: auto;
|
||||
font-family: 'SFMono-Regular', 'Consolas', 'Liberation Mono', 'Courier New', monospace;
|
||||
line-height: 1.5;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
#config-management .CodeMirror {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
font-family: 'SFMono-Regular', 'Consolas', 'Liberation Mono', 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-primary);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#config-management .CodeMirror-scroll {
|
||||
min-height: 0;
|
||||
max-height: calc(100vh - 440px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#config-management .CodeMirror.cm-s-default {
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
#config-management .CodeMirror-gutters {
|
||||
background: var(--bg-secondary);
|
||||
border-right: 1px solid var(--border-primary);
|
||||
}
|
||||
|
||||
#config-management .CodeMirror-linenumber {
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
#config-management .CodeMirror .CodeMirror-lines {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
#config-management .CodeMirror .cm-comment {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
[data-theme="dark"] #config-management .CodeMirror.cm-s-default {
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] #config-management .CodeMirror-gutters {
|
||||
background: var(--bg-secondary);
|
||||
border-right: 1px solid var(--border-secondary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] #config-management .CodeMirror .cm-comment {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
#config-management .CodeMirror.cm-readonly {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.editor-status {
|
||||
font-size: 13px;
|
||||
color: var(--text-quaternary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-height: 18px;
|
||||
}
|
||||
|
||||
.editor-status.success {
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
.editor-status.error {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
@@ -2925,3 +3052,6 @@ input:checked+.slider:before {
|
||||
font-size: 11px !important;
|
||||
}
|
||||
}
|
||||
#config-management .CodeMirror .CodeMirror-lines {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user