pf-sdn/webui/src/pages/ROS.js

294 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import $ from 'jquery';
import { JSONRPC } from '@/json-rpc.js';
const ROS_COMPONENT_NAME = 'ru.kirillius.pf.sdn.External.API.Components.ROS';
const FIELD_IDS = {
container: 'ros-config-container',
instancesList: 'ros-instances-list',
addInstanceButton: 'ros-add-instance-btn',
saveButton: 'save-ros-btn',
status: 'ros-status-message'
};
const CLASS_NAMES = {
instance: 'ros-instance-entry',
removeInstanceButton: 'ros-remove-instance-btn',
host: 'ros-host',
username: 'ros-username',
password: 'ros-password',
vrf: 'ros-vrf',
gateway: 'ros-gateway'
};
const SELECTORS = {
container: `#${FIELD_IDS.container}`,
instancesList: `#${FIELD_IDS.instancesList}`,
addInstanceButton: `#${FIELD_IDS.addInstanceButton}`,
saveButton: `#${FIELD_IDS.saveButton}`,
status: `#${FIELD_IDS.status}`
};
let currentConfig = { instances: [] };
let statusTimeoutId = null;
let instanceCounter = 0;
const getStatusElement = () => $(SELECTORS.status);
function normalizeConfig(config) {
if (!config || !Array.isArray(config.instances)) {
return { instances: [] };
}
return {
instances: config.instances.map(instance => ({
host: instance?.host || '',
username: instance?.username || '',
password: instance?.password || '',
VRF: instance?.VRF || 'main',
gateway: instance?.gateway || ''
}))
};
}
function clearStatus() {
const $status = getStatusElement();
if (!$status.length) {
return;
}
if (statusTimeoutId) {
clearTimeout(statusTimeoutId);
statusTimeoutId = null;
}
$status.stop(true, true).hide().text('').removeClass('success-message error-message');
}
function updateStatus(message, type) {
const $status = getStatusElement();
if (!$status.length) {
return;
}
if (statusTimeoutId) {
clearTimeout(statusTimeoutId);
}
$status
.removeClass('success-message error-message')
.addClass(type === 'success' ? 'success-message' : 'error-message')
.text(message)
.show();
statusTimeoutId = window.setTimeout(() => {
$status.fadeOut();
}, 5000);
}
async function runAction($button, pendingText, action, messages) {
if (!$button.length) {
return;
}
const originalText = $button.text();
$button.prop('disabled', true).text(pendingText);
clearStatus();
try {
const result = await action();
if (messages?.success) {
const successMessage = typeof messages.success === 'function'
? messages.success(result)
: messages.success;
if (successMessage) {
updateStatus(successMessage, 'success');
}
}
} catch (error) {
console.error(messages?.log || 'Ошибка выполнения действия ROS:', error);
if (messages?.error) {
updateStatus(messages.error, 'error');
}
} finally {
$button.prop('disabled', false).text(originalText);
}
}
function resetCounters() {
instanceCounter = 0;
}
function getNextInstanceId() {
instanceCounter += 1;
return instanceCounter;
}
function createInstanceRow(instance = {}) {
const instanceId = getNextInstanceId();
return `
<div class="${CLASS_NAMES.instance}" data-instance-id="${instanceId}" style="border: 1px solid var(--color-border); padding: 20px; border-radius: 12px; margin-bottom: 20px; background: var(--color-surface, #1f2333);">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<h4 style="margin: 0;">Инстанс</h4>
<button type="button" class="btn-link ${CLASS_NAMES.removeInstanceButton}">Удалить</button>
</div>
<div class="form-group">
<label for="ros-host-${instanceId}">Хост</label>
<input type="text" id="ros-host-${instanceId}" class="form-control ${CLASS_NAMES.host}" value="${instance.host || ''}">
</div>
<div class="form-group">
<label for="ros-username-${instanceId}">Имя пользователя</label>
<input type="text" id="ros-username-${instanceId}" class="form-control ${CLASS_NAMES.username}" value="${instance.username || ''}">
</div>
<div class="form-group">
<label for="ros-password-${instanceId}">Пароль</label>
<input type="password" id="ros-password-${instanceId}" class="form-control ${CLASS_NAMES.password}" placeholder="Оставьте пустым для сохранения текущего пароля" data-original-password="${instance.password || ''}">
</div>
<div class="form-group">
<label for="ros-vrf-${instanceId}">VRF (роутинг-таблица)</label>
<input type="text" id="ros-vrf-${instanceId}" class="form-control ${CLASS_NAMES.vrf}" value="${instance.VRF || 'main'}">
</div>
<div class="form-group">
<label for="ros-gateway-${instanceId}">Шлюз</label>
<input type="text" id="ros-gateway-${instanceId}" class="form-control ${CLASS_NAMES.gateway}" placeholder="127.0.0.1" value="${instance.gateway || ''}">
</div>
</div>
`;
}
function populateInstances() {
const instances = currentConfig.instances.length ? currentConfig.instances : [];
const $list = $(SELECTORS.instancesList);
resetCounters();
$list.empty();
instances.forEach(instance => {
$list.append(createInstanceRow(instance));
});
}
function renderROSForm() {
const $container = $(SELECTORS.container);
$container.html(`
<div class="component-config-form">
<h3 class="config-section-title">Инстансы RouterOS</h3>
<p class="hint-text" style="margin-bottom: 20px;">Настройте параметры подключения к MikroTik RouterOS для синхронизации маршрутов. Можно добавить несколько инстансов.</p>
<div id="${FIELD_IDS.instancesList}"></div>
<div class="form-group" style="margin-top: 10px;">
<button type="button" id="${FIELD_IDS.addInstanceButton}" class="btn-secondary" style="width: 220px;">Добавить инстанс</button>
</div>
<div class="action-buttons" style="margin-top: 40px;">
<button id="${FIELD_IDS.saveButton}" class="btn-primary" style="width: 220px;">Применить Конфигурацию</button>
</div>
<div id="${FIELD_IDS.status}" class="error-message" style="display: none; margin-top: 20px;"></div>
</div>
`);
populateInstances();
attachEventHandlers();
}
function collectConfigFromForm() {
const instances = [];
$(SELECTORS.instancesList).find(`.${CLASS_NAMES.instance}`).each((_, element) => {
const $instance = $(element);
const host = $instance.find(`.${CLASS_NAMES.host}`).val().trim();
const username = $instance.find(`.${CLASS_NAMES.username}`).val().trim();
const $passwordField = $instance.find(`.${CLASS_NAMES.password}`);
const newPassword = $passwordField.val();
const originalPassword = $passwordField.data('original-password') || '';
const password = newPassword ? newPassword : originalPassword;
const VRF = $instance.find(`.${CLASS_NAMES.vrf}`).val().trim();
const gateway = $instance.find(`.${CLASS_NAMES.gateway}`).val().trim();
instances.push({
host,
username,
password,
VRF,
gateway
});
});
return { instances };
}
async function loadConfig() {
try {
const fullConfig = await JSONRPC.System.getComponentConfig(ROS_COMPONENT_NAME);
currentConfig = normalizeConfig(fullConfig);
return true;
} catch (error) {
console.error('Ошибка при загрузке конфига ROS:', error);
currentConfig = { instances: [] };
return false;
}
}
function handleAddInstance() {
const $list = $(SELECTORS.instancesList);
$list.append(createInstanceRow({
host: '',
username: '',
password: '',
VRF: 'main',
gateway: ''
}));
}
function handleRemoveInstance(event) {
event.preventDefault();
$(event.currentTarget).closest(`.${CLASS_NAMES.instance}`).remove();
}
async function handleSave() {
const $button = $(SELECTORS.saveButton);
clearStatus();
const newConfig = collectConfigFromForm();
await runAction($button, 'Применение...', async () => {
await JSONRPC.System.setComponentConfig(ROS_COMPONENT_NAME, newConfig);
currentConfig = normalizeConfig(newConfig);
populateInstances();
}, {
success: 'Конфигурация ROS успешно сохранена.',
error: 'Ошибка при сохранении конфигурации ROS.',
log: 'Ошибка сохранения конфига ROS'
});
}
function attachEventHandlers() {
$(SELECTORS.saveButton).off('click').on('click', handleSave);
$(SELECTORS.addInstanceButton).off('click').on('click', handleAddInstance);
$(SELECTORS.instancesList)
.off('click', `.${CLASS_NAMES.removeInstanceButton}`).on('click', `.${CLASS_NAMES.removeInstanceButton}`, handleRemoveInstance);
}
function detachEventHandlers() {
$(SELECTORS.saveButton).off('click');
$(SELECTORS.addInstanceButton).off('click');
$(SELECTORS.instancesList)
.off('click', `.${CLASS_NAMES.removeInstanceButton}`);
}
export const ROSConfig = {
render: () => `
<h1 class="page-title">Настройка ROS</h1>
<div id="${FIELD_IDS.container}">
<p>Загрузка конфигурации...</p>
</div>
`,
mount: async () => {
const success = await loadConfig();
if (success) {
renderROSForm();
} else {
$(SELECTORS.container).html('<p class="error-message">Не удалось загрузить конфигурацию ROS.</p>');
}
},
unmount: () => {
detachEventHandlers();
clearStatus();
currentConfig = { instances: [] };
}
};