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
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();
@ -58,20 +64,7 @@ function syncLineNumbers() {
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
function handleEditorKeydown(event) {
@ -150,13 +143,13 @@ function updateEditorStatus() {
try {
if (textarea.value.trim() === '') {
statusElement.textContent = 'Пустая конфигурация';
statusElement.innerHTML = '<span data-key="empty_config">Пустая конфигурация</span>';
statusElement.className = 'status-text warning';
return;
}
JSON.parse(textarea.value);
statusElement.textContent = 'Валидный JSON';
statusElement.innerHTML = '<span data-key="valid_json"></span>';
statusElement.className = 'status-text success';
} catch (error) {
statusElement.textContent = `Ошибка JSON: ${error.message}`;
@ -176,7 +169,7 @@ function updateCursorPosition() {
const line = beforeCursor.split('\n').length;
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
@ -191,7 +184,10 @@ function formatJSON() {
textarea.value = formatted;
updateLineNumbers();
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) {
showNotification(`Ошибка форматирования: ${error.message}`, 'error');
}
@ -205,7 +201,10 @@ function validateJSON() {
try {
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) {
showNotification(`Ошибка валидации JSON: ${error.message}`, 'error');
}
@ -246,9 +245,15 @@ async function saveConfiguration(restart = false) {
if (result.success) {
currentConfigJSON = textarea.value;
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 {
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
} else {
@ -262,11 +267,22 @@ async function saveConfiguration(restart = false) {
// Save configuration and restart server
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);
}
});
}
// Refresh configuration from server
@ -275,18 +291,31 @@ async function refreshConfiguration() {
// Check if there are unsaved changes
if (textarea && textarea.value !== currentConfigJSON) {
const confirmation = confirm('У вас есть несохраненные изменения. Продолжить обновление?');
if (!confirmation) {
return;
}
}
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

View file

@ -55,29 +55,10 @@ function renderConfigEditor() {
<span class="config-path" title="${configMeta.path}">${configMeta.path}</span>
<span class="config-format ${configMeta.format}">${configMeta.format.toUpperCase()}</span>
<span class="config-status ${configMeta.isWritable ? 'writable' : 'readonly'}">
${configMeta.isWritable ? '✏️ Редактируемый' : '🔒 Только чтение'}
${configMeta.isWritable ? '✏️ <span data-key="editable">Редактируемый</span>' : '🔒 <span data-key="readonly">Только чтение</span>'}
</span>
</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 class="config-editor-container">
@ -85,10 +66,25 @@ function renderConfigEditor() {
<div class="editor-header">
<span class="editor-title" data-key="json_configuration">JSON Конфигурация</span>
<div class="editor-controls">
<span class="line-numbers-toggle">
<input type="checkbox" id="line-numbers" checked onchange="toggleLineNumbers()">
<label for="line-numbers" data-key="line_numbers">Номера строк</label>
</span>
<div class="action-buttons-group">
<div onclick="refreshConfiguration()" class="action-btn">
<span data-key="refresh">Обновить</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 class="editor-wrapper">

View file

@ -108,7 +108,7 @@ window.translations.en = {
'format': 'Format',
'validate': 'Validate',
'json_configuration': 'JSON Configuration',
'line_numbers': 'Line Numbers',
'config_save_success': 'Configuration saved successfully',
'config_save_error': 'Error saving configuration',
'config_load_error': 'Error loading configuration',
@ -116,5 +116,27 @@ window.translations.en = {
'config_save_confirm_title': 'Confirm Save',
'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_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': 'Форматировать',
'validate': 'Проверить',
'json_configuration': 'JSON Конфигурация',
'line_numbers': 'Номера строк',
'config_save_success': 'Конфигурация сохранена успешно',
'config_save_error': 'Ошибка сохранения конфигурации',
'config_load_error': 'Ошибка загрузки конфигурации',
@ -116,5 +116,27 @@ window.translations.ru = {
'config_save_confirm_title': 'Подтверждение сохранения',
'config_save_confirm_text': 'Вы уверены, что хотите сохранить изменения в конфигурационный файл?',
'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 */
.config-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
@ -1556,6 +1555,11 @@ button[onclick="copyNodeKey()"]:hover {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.config-info {
display: flex;
flex-direction: column;
}
.config-info h3 {
margin: 0 0 10px 0;
color: var(--text-heading);
@ -1617,31 +1621,7 @@ button[onclick="copyNodeKey()"]:hover {
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 */
.config-groups {
@ -1943,17 +1923,7 @@ input:checked + .slider:before {
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 {
position: relative;
@ -2050,24 +2020,34 @@ input:checked + .slider:before {
font-size: 0.8em;
}
/* Additional action buttons */
.format-btn {
background: var(--bg-nav-active);
color: white;
/* Action buttons group */
.action-buttons-group {
display: flex;
gap: 8px;
flex-wrap: wrap;
justify-content: flex-end;
}
.validate-btn {
background: var(--bg-warning-dark);
color: white;
.action-buttons-group .action-btn {
background: var(--bg-nav-item);
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 {
background: var(--text-error);
color: white;
}
.restart-btn:hover {
background: #c62828;
.action-buttons-group .action-btn:hover {
background: var(--bg-nav-hover);
transform: translateY(-1px);
}
/* JSON Syntax highlighting (basic) */
@ -2105,13 +2085,16 @@ input:checked + .slider:before {
padding-left: 45px;
}
.config-actions {
flex-wrap: wrap;
.action-buttons-group {
flex-direction: row;
gap: 4px;
justify-content: center;
}
.action-btn {
flex: 1;
min-width: 120px;
.action-buttons-group .action-btn {
flex: none;
min-width: auto;
justify-content: center;
}
}
@ -2128,3 +2111,13 @@ input:checked + .slider:before {
background: var(--bg-info-card);
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);
}