Refactor configuration editor to always display line numbers and update UI elements for better user experience. Replace toggle functionality with a fixed display of line numbers. Enhance notification messages and confirmation dialogs with translation support for improved localization.

This commit is contained in:
Andy Oknen 2025-08-15 19:22:07 +00:00
parent 9e11f76fc3
commit 1c61269877
5 changed files with 179 additions and 117 deletions

View file

@ -15,6 +15,12 @@ function initJSONEditor() {
// Update line numbers // Update line numbers
updateLineNumbers(); 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 // Add event listeners
textarea.addEventListener('input', function() { textarea.addEventListener('input', function() {
updateLineNumbers(); updateLineNumbers();
@ -58,20 +64,7 @@ function syncLineNumbers() {
lineNumbersContainer.scrollTop = textarea.scrollTop; lineNumbersContainer.scrollTop = textarea.scrollTop;
} }
// Toggle line numbers visibility
function toggleLineNumbers() {
const checkbox = document.getElementById('line-numbers');
const lineNumbersContainer = document.getElementById('line-numbers-container');
const editorWrapper = document.querySelector('.editor-wrapper');
if (checkbox.checked) {
lineNumbersContainer.style.display = 'block';
editorWrapper.classList.add('with-line-numbers');
} else {
lineNumbersContainer.style.display = 'none';
editorWrapper.classList.remove('with-line-numbers');
}
}
// Handle special editor keydown events // Handle special editor keydown events
function handleEditorKeydown(event) { function handleEditorKeydown(event) {
@ -150,13 +143,13 @@ function updateEditorStatus() {
try { try {
if (textarea.value.trim() === '') { if (textarea.value.trim() === '') {
statusElement.textContent = 'Пустая конфигурация'; statusElement.innerHTML = '<span data-key="empty_config">Пустая конфигурация</span>';
statusElement.className = 'status-text warning'; statusElement.className = 'status-text warning';
return; return;
} }
JSON.parse(textarea.value); JSON.parse(textarea.value);
statusElement.textContent = 'Валидный JSON'; statusElement.innerHTML = '<span data-key="valid_json"></span>';
statusElement.className = 'status-text success'; statusElement.className = 'status-text success';
} catch (error) { } catch (error) {
statusElement.textContent = `Ошибка JSON: ${error.message}`; statusElement.textContent = `Ошибка JSON: ${error.message}`;
@ -176,7 +169,7 @@ function updateCursorPosition() {
const line = beforeCursor.split('\n').length; const line = beforeCursor.split('\n').length;
const column = beforeCursor.length - beforeCursor.lastIndexOf('\n'); const column = beforeCursor.length - beforeCursor.lastIndexOf('\n');
cursorElement.textContent = `Строка ${line}, Столбец ${column}`; cursorElement.innerHTML = `<span data-key="line">Строка</span> ${line}, <span data-key="column">Столбец</span> ${column}`;
} }
// Format JSON with proper indentation // Format JSON with proper indentation
@ -191,7 +184,10 @@ function formatJSON() {
textarea.value = formatted; textarea.value = formatted;
updateLineNumbers(); updateLineNumbers();
updateEditorStatus(); updateEditorStatus();
showNotification('JSON отформатирован', 'success'); const formattedMessage = window.translations && window.translations[currentLanguage] && window.translations[currentLanguage]['json_formatted']
? window.translations[currentLanguage]['json_formatted']
: 'JSON отформатирован';
showNotification(formattedMessage, 'success');
} catch (error) { } catch (error) {
showNotification(`Ошибка форматирования: ${error.message}`, 'error'); showNotification(`Ошибка форматирования: ${error.message}`, 'error');
} }
@ -205,7 +201,10 @@ function validateJSON() {
try { try {
JSON.parse(textarea.value); JSON.parse(textarea.value);
showNotification('JSON конфигурация валидна', 'success'); 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) { } catch (error) {
showNotification(`Ошибка валидации JSON: ${error.message}`, 'error'); showNotification(`Ошибка валидации JSON: ${error.message}`, 'error');
} }
@ -246,9 +245,15 @@ async function saveConfiguration(restart = false) {
if (result.success) { if (result.success) {
currentConfigJSON = textarea.value; currentConfigJSON = textarea.value;
if (restart) { if (restart) {
showNotification('Конфигурация сохранена. Сервер перезапускается...', 'success'); const restartMessage = window.translations && window.translations[currentLanguage] && window.translations[currentLanguage]['config_saved_restarting']
? window.translations[currentLanguage]['config_saved_restarting']
: 'Конфигурация сохранена. Сервер перезапускается...';
showNotification(restartMessage, 'success');
} else { } else {
showNotification('Конфигурация сохранена успешно', 'success'); 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 onConfigChange(); // Update button states
} else { } else {
@ -262,11 +267,22 @@ async function saveConfiguration(restart = false) {
// Save configuration and restart server // Save configuration and restart server
async function saveAndRestartConfiguration() { async function saveAndRestartConfiguration() {
const confirmation = confirm('Сохранить конфигурацию и перезапустить сервер?\n\nВнимание: Соединение будет прервано на время перезапуска.'); const title = window.translations && window.translations[currentLanguage] && window.translations[currentLanguage]['save_and_restart_title']
? window.translations[currentLanguage]['save_and_restart_title']
: 'Сохранить и перезапустить';
if (confirmation) { 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); await saveConfiguration(true);
} }
});
} }
// Refresh configuration from server // Refresh configuration from server
@ -275,18 +291,31 @@ async function refreshConfiguration() {
// Check if there are unsaved changes // Check if there are unsaved changes
if (textarea && textarea.value !== currentConfigJSON) { if (textarea && textarea.value !== currentConfigJSON) {
const confirmation = confirm('У вас есть несохраненные изменения. Продолжить обновление?'); const title = window.translations && window.translations[currentLanguage] && window.translations[currentLanguage]['refresh_unsaved_changes_title']
if (!confirmation) { ? window.translations[currentLanguage]['refresh_unsaved_changes_title']
return; : 'Несохраненные изменения';
}
}
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 { try {
await loadConfiguration(); await loadConfiguration();
showNotification('Конфигурация обновлена', 'success'); showNotification('Конфигурация обновлена', 'success');
} catch (error) { } catch (error) {
showNotification('Ошибка обновления конфигурации', 'error'); showNotification('Ошибка обновления конфигурации', 'error');
} }
}
});
return;
}
} }
// Update configuration status // Update configuration status

View file

@ -55,29 +55,10 @@ function renderConfigEditor() {
<span class="config-path" title="${configMeta.path}">${configMeta.path}</span> <span class="config-path" title="${configMeta.path}">${configMeta.path}</span>
<span class="config-format ${configMeta.format}">${configMeta.format.toUpperCase()}</span> <span class="config-format ${configMeta.format}">${configMeta.format.toUpperCase()}</span>
<span class="config-status ${configMeta.isWritable ? 'writable' : 'readonly'}"> <span class="config-status ${configMeta.isWritable ? 'writable' : 'readonly'}">
${configMeta.isWritable ? '✏️ Редактируемый' : '🔒 Только чтение'} ${configMeta.isWritable ? '✏️ <span data-key="editable">Редактируемый</span>' : '🔒 <span data-key="readonly">Только чтение</span>'}
</span> </span>
</div> </div>
</div> </div>
<div class="config-actions">
<button onclick="refreshConfiguration()" class="action-btn refresh-btn" data-key="refresh">
🔄 Обновить
</button>
<button onclick="formatJSON()" class="action-btn format-btn" data-key="format">
📝 Форматировать
</button>
<button onclick="validateJSON()" class="action-btn validate-btn" data-key="validate">
Проверить
</button>
${configMeta.isWritable ? `
<button onclick="saveConfiguration()" class="action-btn save-btn" data-key="save_config">
💾 Сохранить
</button>
<button onclick="saveAndRestartConfiguration()" class="action-btn restart-btn" data-key="save_and_restart">
🔄 Сохранить и перезапустить
</button>
` : ''}
</div>
</div> </div>
<div class="config-editor-container"> <div class="config-editor-container">
@ -85,10 +66,25 @@ function renderConfigEditor() {
<div class="editor-header"> <div class="editor-header">
<span class="editor-title" data-key="json_configuration">JSON Конфигурация</span> <span class="editor-title" data-key="json_configuration">JSON Конфигурация</span>
<div class="editor-controls"> <div class="editor-controls">
<span class="line-numbers-toggle"> <div class="action-buttons-group">
<input type="checkbox" id="line-numbers" checked onchange="toggleLineNumbers()"> <div onclick="refreshConfiguration()" class="action-btn">
<label for="line-numbers" data-key="line_numbers">Номера строк</label> <span data-key="refresh">Обновить</span>
</span> </div>
<div onclick="formatJSON()" class="action-btn">
<span data-key="format">Форматировать</span>
</div>
<div onclick="validateJSON()" class="action-btn">
<span data-key="validate">Проверить</span>
</div>
${configMeta.isWritable ? `
<div onclick="saveConfiguration()" class="action-btn">
<span data-key="save_config">Сохранить</span>
</div>
<div onclick="saveAndRestartConfiguration()" class="action-btn">
<span data-key="save_and_restart">Сохранить и перезапустить</span>
</div>
` : ''}
</div>
</div> </div>
</div> </div>
<div class="editor-wrapper"> <div class="editor-wrapper">

View file

@ -108,7 +108,7 @@ window.translations.en = {
'format': 'Format', 'format': 'Format',
'validate': 'Validate', 'validate': 'Validate',
'json_configuration': 'JSON Configuration', 'json_configuration': 'JSON Configuration',
'line_numbers': 'Line Numbers',
'config_save_success': 'Configuration saved successfully', 'config_save_success': 'Configuration saved successfully',
'config_save_error': 'Error saving configuration', 'config_save_error': 'Error saving configuration',
'config_load_error': 'Error loading configuration', 'config_load_error': 'Error loading configuration',
@ -116,5 +116,27 @@ window.translations.en = {
'config_save_confirm_title': 'Confirm Save', 'config_save_confirm_title': 'Confirm Save',
'config_save_confirm_text': 'Are you sure you want to save changes to the configuration file?', 'config_save_confirm_text': 'Are you sure you want to save changes to the configuration file?',
'config_backup_info': 'Backup will be created automatically', 'config_backup_info': 'Backup will be created automatically',
'config_warning': '⚠️ Warning: Incorrect configuration may cause node failure!' 'config_warning': '⚠️ Warning: Incorrect configuration may cause node failure!',
// Editor status translations
'editable': 'Editable',
'readonly': 'Read Only',
'empty_config': 'Empty Configuration',
'valid_json': 'Valid JSON',
'line': 'Line',
'column': 'Column',
// Configuration save messages
'config_saved_restarting': 'Configuration saved. Server is restarting...',
'config_saved_success': 'Configuration saved successfully',
// Validation messages
'json_validation_success': 'JSON configuration is valid',
'json_formatted': 'JSON formatted',
// Confirmation dialogs
'save_and_restart_title': 'Save and Restart',
'save_and_restart_message': 'Save configuration and restart server?\n\nWarning: Connection will be interrupted during restart.',
'refresh_unsaved_changes_title': 'Unsaved Changes',
'refresh_unsaved_changes_message': 'You have unsaved changes. Continue refreshing?'
}; };

View file

@ -108,7 +108,7 @@ window.translations.ru = {
'format': 'Форматировать', 'format': 'Форматировать',
'validate': 'Проверить', 'validate': 'Проверить',
'json_configuration': 'JSON Конфигурация', 'json_configuration': 'JSON Конфигурация',
'line_numbers': 'Номера строк',
'config_save_success': 'Конфигурация сохранена успешно', 'config_save_success': 'Конфигурация сохранена успешно',
'config_save_error': 'Ошибка сохранения конфигурации', 'config_save_error': 'Ошибка сохранения конфигурации',
'config_load_error': 'Ошибка загрузки конфигурации', 'config_load_error': 'Ошибка загрузки конфигурации',
@ -116,5 +116,27 @@ window.translations.ru = {
'config_save_confirm_title': 'Подтверждение сохранения', 'config_save_confirm_title': 'Подтверждение сохранения',
'config_save_confirm_text': 'Вы уверены, что хотите сохранить изменения в конфигурационный файл?', 'config_save_confirm_text': 'Вы уверены, что хотите сохранить изменения в конфигурационный файл?',
'config_backup_info': 'Резервная копия будет создана автоматически', 'config_backup_info': 'Резервная копия будет создана автоматически',
'config_warning': '⚠️ Внимание: Неправильная конфигурация может привести к сбою работы узла!' 'config_warning': '⚠️ Внимание: Неправильная конфигурация может привести к сбою работы узла!',
// Editor status translations
'editable': 'Редактируемый',
'readonly': 'Только чтение',
'empty_config': 'Пустая конфигурация',
'valid_json': 'Валидный JSON',
'line': 'Строка',
'column': 'Столбец',
// Configuration save messages
'config_saved_restarting': 'Конфигурация сохранена. Сервер перезапускается...',
'config_saved_success': 'Конфигурация сохранена успешно',
// Validation messages
'json_validation_success': 'JSON конфигурация валидна',
'json_formatted': 'JSON отформатирован',
// Confirmation dialogs
'save_and_restart_title': 'Сохранить и перезапустить',
'save_and_restart_message': 'Сохранить конфигурацию и перезапустить сервер?\n\nВнимание: Соединение будет прервано на время перезапуска.',
'refresh_unsaved_changes_title': 'Несохраненные изменения',
'refresh_unsaved_changes_message': 'У вас есть несохраненные изменения. Продолжить обновление?'
}; };

View file

@ -1539,7 +1539,6 @@ button[onclick="copyNodeKey()"]:hover {
/* Configuration Editor Styles */ /* Configuration Editor Styles */
.config-container { .config-container {
max-width: 1200px;
margin: 0 auto; margin: 0 auto;
padding: 20px; padding: 20px;
} }
@ -1556,6 +1555,11 @@ button[onclick="copyNodeKey()"]:hover {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
} }
.config-info {
display: flex;
flex-direction: column;
}
.config-info h3 { .config-info h3 {
margin: 0 0 10px 0; margin: 0 0 10px 0;
color: var(--text-heading); color: var(--text-heading);
@ -1617,31 +1621,7 @@ button[onclick="copyNodeKey()"]:hover {
color: var(--text-warning); color: var(--text-warning);
} }
.config-actions {
display: flex;
gap: 10px;
}
.refresh-btn {
background: var(--bg-nav-active);
color: white;
}
.save-btn {
background: var(--bg-success-dark);
color: white;
}
.save-btn.modified {
background: var(--bg-warning-dark);
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.7; }
100% { opacity: 1; }
}
/* Configuration Groups */ /* Configuration Groups */
.config-groups { .config-groups {
@ -1943,17 +1923,7 @@ input:checked + .slider:before {
gap: 15px; gap: 15px;
} }
.line-numbers-toggle {
display: flex;
align-items: center;
gap: 6px;
font-size: 0.9em;
color: var(--text-muted);
}
.line-numbers-toggle input[type="checkbox"] {
margin: 0;
}
.editor-wrapper { .editor-wrapper {
position: relative; position: relative;
@ -2050,24 +2020,34 @@ input:checked + .slider:before {
font-size: 0.8em; font-size: 0.8em;
} }
/* Additional action buttons */ /* Action buttons group */
.format-btn { .action-buttons-group {
background: var(--bg-nav-active); display: flex;
color: white; gap: 8px;
flex-wrap: wrap;
justify-content: flex-end;
} }
.validate-btn { .action-buttons-group .action-btn {
background: var(--bg-warning-dark); background: var(--bg-nav-item);
color: white; color: var(--text-nav);
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
font-weight: 500;
transition: all 0.2s ease;
margin: 0;
user-select: none;
display: flex;
align-items: center;
justify-content: center;
} }
.restart-btn { .action-buttons-group .action-btn:hover {
background: var(--text-error); background: var(--bg-nav-hover);
color: white; transform: translateY(-1px);
}
.restart-btn:hover {
background: #c62828;
} }
/* JSON Syntax highlighting (basic) */ /* JSON Syntax highlighting (basic) */
@ -2105,13 +2085,16 @@ input:checked + .slider:before {
padding-left: 45px; padding-left: 45px;
} }
.config-actions { .action-buttons-group {
flex-wrap: wrap; flex-direction: row;
gap: 4px;
justify-content: center;
} }
.action-btn { .action-buttons-group .action-btn {
flex: 1; flex: none;
min-width: 120px; min-width: auto;
justify-content: center;
} }
} }
@ -2128,3 +2111,13 @@ input:checked + .slider:before {
background: var(--bg-info-card); background: var(--bg-info-card);
color: var(--text-body); color: var(--text-body);
} }
/* Dark theme support for action buttons */
[data-theme="dark"] .action-buttons-group .action-btn {
color: var(--text-nav);
}
[data-theme="dark"] .action-buttons-group .action-btn:hover {
background: var(--bg-nav-hover);
transform: translateY(-1px);
}