274 lines
9.7 KiB
JavaScript
274 lines
9.7 KiB
JavaScript
export const configEditorModule = {
|
|
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();
|
|
this.lastEditorConnectionState = this.isConnected;
|
|
},
|
|
|
|
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 this.apiClient.requestRaw(requestUrl, {
|
|
method: 'GET',
|
|
headers: {
|
|
'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 {
|
|
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();
|
|
if (this.events && typeof this.events.emit === 'function') {
|
|
this.events.emit('config:refresh-requested', { forceRefresh: true });
|
|
}
|
|
} 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 this.apiClient.requestRaw(endpoint, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'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');
|
|
}
|
|
}
|
|
},
|
|
|
|
registerConfigEditorListeners() {
|
|
if (!this.events || typeof this.events.on !== 'function') {
|
|
return;
|
|
}
|
|
this.events.on('data:config-loaded', async (event) => {
|
|
const detail = event?.detail || {};
|
|
try {
|
|
await this.loadConfigFileEditor(detail.forceRefresh || false);
|
|
this.refreshConfigEditor();
|
|
} catch (error) {
|
|
console.error('加载配置文件失败:', error);
|
|
}
|
|
});
|
|
}
|
|
};
|