// JSON Editor functionality for configuration
// Initialize JSON editor with basic syntax highlighting and features
function initJSONEditor() {
const textarea = document.getElementById('config-json-textarea');
const lineNumbersContainer = document.getElementById('line-numbers-container');
const statusElement = document.getElementById('editor-status');
const cursorElement = document.getElementById('cursor-position');
if (!textarea) return;
// Set initial content
textarea.value = currentConfigJSON || '';
// Update line numbers
updateLineNumbers();
// Always enable line numbers since toggle was removed
const editorWrapper = document.querySelector('.editor-wrapper');
if (editorWrapper) {
editorWrapper.classList.add('with-line-numbers');
}
// Add event listeners
textarea.addEventListener('input', function() {
updateLineNumbers();
updateEditorStatus();
onConfigChange();
});
textarea.addEventListener('scroll', syncLineNumbers);
textarea.addEventListener('keydown', handleEditorKeydown);
textarea.addEventListener('selectionchange', updateCursorPosition);
textarea.addEventListener('click', updateCursorPosition);
textarea.addEventListener('keyup', updateCursorPosition);
// Initial status update
updateEditorStatus();
updateCursorPosition();
}
// Update line numbers
function updateLineNumbers() {
const textarea = document.getElementById('config-json-textarea');
const lineNumbersContainer = document.getElementById('line-numbers-container');
if (!textarea || !lineNumbersContainer) return;
const lines = textarea.value.split('\n');
const lineNumbers = lines.map((_, index) =>
`${index + 1}`
).join('');
lineNumbersContainer.innerHTML = lineNumbers;
}
// Sync line numbers scroll with textarea
function syncLineNumbers() {
const textarea = document.getElementById('config-json-textarea');
const lineNumbersContainer = document.getElementById('line-numbers-container');
if (!textarea || !lineNumbersContainer) return;
lineNumbersContainer.scrollTop = textarea.scrollTop;
}
// Handle special editor keydown events
function handleEditorKeydown(event) {
const textarea = event.target;
// Tab handling for indentation
if (event.key === 'Tab') {
event.preventDefault();
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
if (event.shiftKey) {
// Remove indentation
const beforeCursor = textarea.value.substring(0, start);
const lineStart = beforeCursor.lastIndexOf('\n') + 1;
const currentLine = textarea.value.substring(lineStart, end);
if (currentLine.startsWith(' ')) {
textarea.value = textarea.value.substring(0, lineStart) +
currentLine.substring(2) +
textarea.value.substring(end);
textarea.selectionStart = Math.max(lineStart, start - 2);
textarea.selectionEnd = end - 2;
}
} else {
// Add indentation
textarea.value = textarea.value.substring(0, start) +
' ' +
textarea.value.substring(end);
textarea.selectionStart = start + 2;
textarea.selectionEnd = start + 2;
}
updateLineNumbers();
updateEditorStatus();
}
// Auto-closing brackets and quotes
if (event.key === '{') {
insertMatchingCharacter(textarea, '{', '}');
event.preventDefault();
} else if (event.key === '[') {
insertMatchingCharacter(textarea, '[', ']');
event.preventDefault();
} else if (event.key === '"') {
insertMatchingCharacter(textarea, '"', '"');
event.preventDefault();
}
}
// Insert matching character (auto-closing)
function insertMatchingCharacter(textarea, open, close) {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selectedText = textarea.value.substring(start, end);
textarea.value = textarea.value.substring(0, start) +
open + selectedText + close +
textarea.value.substring(end);
if (selectedText) {
textarea.selectionStart = start + 1;
textarea.selectionEnd = start + 1 + selectedText.length;
} else {
textarea.selectionStart = start + 1;
textarea.selectionEnd = start + 1;
}
}
// Update editor status (validation)
function updateEditorStatus() {
const textarea = document.getElementById('config-json-textarea');
const statusElement = document.getElementById('editor-status');
if (!textarea || !statusElement) return;
try {
if (textarea.value.trim() === '') {
statusElement.innerHTML = 'Пустая конфигурация';
statusElement.className = 'status-text warning';
return;
}
JSON.parse(textarea.value);
statusElement.innerHTML = '';
statusElement.className = 'status-text success';
} catch (error) {
statusElement.textContent = `Ошибка JSON: ${error.message}`;
statusElement.className = 'status-text error';
}
}
// Update cursor position display
function updateCursorPosition() {
const textarea = document.getElementById('config-json-textarea');
const cursorElement = document.getElementById('cursor-position');
if (!textarea || !cursorElement) return;
const cursorPos = textarea.selectionStart;
const beforeCursor = textarea.value.substring(0, cursorPos);
const line = beforeCursor.split('\n').length;
const column = beforeCursor.length - beforeCursor.lastIndexOf('\n');
cursorElement.innerHTML = `Строка ${line}, Столбец ${column}`;
}
// Format JSON with proper indentation
function formatJSON() {
const textarea = document.getElementById('config-json-textarea');
if (!textarea) return;
try {
const parsed = JSON.parse(textarea.value);
const formatted = JSON.stringify(parsed, null, 2);
textarea.value = formatted;
updateLineNumbers();
updateEditorStatus();
const formattedMessage = window.translations && window.translations[currentLanguage] && window.translations[currentLanguage]['json_formatted']
? window.translations[currentLanguage]['json_formatted']
: 'JSON отформатирован';
showNotification(formattedMessage, 'success');
} catch (error) {
showNotification(`Ошибка форматирования: ${error.message}`, 'error');
}
}
// Validate JSON configuration
function validateJSON() {
const textarea = document.getElementById('config-json-textarea');
if (!textarea) return;
try {
JSON.parse(textarea.value);
const validationMessage = window.translations && window.translations[currentLanguage] && window.translations[currentLanguage]['json_validation_success']
? window.translations[currentLanguage]['json_validation_success']
: 'JSON конфигурация валидна';
showNotification(validationMessage, 'success');
} catch (error) {
showNotification(`Ошибка валидации JSON: ${error.message}`, 'error');
}
}
// Save configuration
async function saveConfiguration(restart = false) {
const textarea = document.getElementById('config-json-textarea');
if (!textarea) {
showNotification('Редактор не найден', 'error');
return;
}
// Validate JSON before saving
try {
JSON.parse(textarea.value);
} catch (error) {
showNotification(`Невозможно сохранить: ${error.message}`, 'error');
return;
}
try {
const response = await fetch('/api/config/set', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'same-origin',
body: JSON.stringify({
config_json: textarea.value,
restart: restart
})
});
const result = await response.json();
if (result.success) {
currentConfigJSON = textarea.value;
if (restart) {
const restartMessage = window.translations && window.translations[currentLanguage] && window.translations[currentLanguage]['config_saved_restarting']
? window.translations[currentLanguage]['config_saved_restarting']
: 'Конфигурация сохранена. Сервер перезапускается...';
showNotification(restartMessage, 'success');
} else {
const successMessage = window.translations && window.translations[currentLanguage] && window.translations[currentLanguage]['config_saved_success']
? window.translations[currentLanguage]['config_saved_success']
: 'Конфигурация сохранена успешно';
showNotification(successMessage, 'success');
}
onConfigChange(); // Update button states
} else {
showNotification(`Ошибка сохранения: ${result.message}`, 'error');
}
} catch (error) {
console.error('Error saving configuration:', error);
showNotification(`Ошибка сохранения: ${error.message}`, 'error');
}
}
// Save configuration and restart server
async function saveAndRestartConfiguration() {
const title = window.translations && window.translations[currentLanguage] && window.translations[currentLanguage]['save_and_restart_title']
? window.translations[currentLanguage]['save_and_restart_title']
: 'Сохранить и перезапустить';
const message = window.translations && window.translations[currentLanguage] && window.translations[currentLanguage]['save_and_restart_message']
? window.translations[currentLanguage]['save_and_restart_message']
: 'Сохранить конфигурацию и перезапустить сервер?\n\nВнимание: Соединение будет прервано на время перезапуска.';
showConfirmModal({
title: title,
message: message,
type: 'danger',
onConfirm: async () => {
await saveConfiguration(true);
}
});
}
// Refresh configuration from server
async function refreshConfiguration() {
const textarea = document.getElementById('config-json-textarea');
// Check if there are unsaved changes
if (textarea && textarea.value !== currentConfigJSON) {
const title = window.translations && window.translations[currentLanguage] && window.translations[currentLanguage]['refresh_unsaved_changes_title']
? window.translations[currentLanguage]['refresh_unsaved_changes_title']
: 'Несохраненные изменения';
const message = window.translations && window.translations[currentLanguage] && window.translations[currentLanguage]['refresh_unsaved_changes_message']
? window.translations[currentLanguage]['refresh_unsaved_changes_message']
: 'У вас есть несохраненные изменения. Продолжить обновление?';
showConfirmModal({
title: title,
message: message,
type: 'warning',
onConfirm: async () => {
try {
await loadConfiguration();
showNotification('Конфигурация обновлена', 'success');
} catch (error) {
showNotification('Ошибка обновления конфигурации', 'error');
}
}
});
return;
}
}
// Update configuration status
function updateConfigStatus() {
// This function can be used to show additional status information
// Currently handled by updateEditorStatus
}
// Handle configuration changes
function onConfigChange() {
// Mark configuration as modified
const saveBtn = document.querySelector('.save-btn');
const restartBtn = document.querySelector('.restart-btn');
const textarea = document.getElementById('config-json-textarea');
const hasChanges = textarea && textarea.value !== currentConfigJSON;
if (saveBtn) {
saveBtn.style.fontWeight = hasChanges ? 'bold' : 'normal';
}
if (restartBtn) {
restartBtn.style.fontWeight = hasChanges ? 'bold' : 'normal';
}
}