import $ from 'jquery';
import { JSONRPC } from '@/json-rpc.js';
const FIELD_IDS = {
container: 'unlock-site-container',
form: 'unlock-site-form',
domainInput: 'unlock-site-domain',
serverInput: 'unlock-site-server',
searchButton: 'unlock-site-search',
results: 'unlock-site-results',
status: 'unlock-site-status',
addressesCheckbox: 'unlock-site-addresses-checkbox',
subnetsCheckbox: 'unlock-site-subnets-checkbox',
asnCheckbox: 'unlock-site-asn-checkbox',
nameInput: 'unlock-site-name',
storageSelect: 'unlock-site-storage',
addButton: 'unlock-site-add-button',
nameError: 'unlock-site-name-error',
domainError: 'unlock-site-domain-error',
serverError: 'unlock-site-server-error'
};
const SELECTORS = Object.entries(FIELD_IDS).reduce((acc, [key, value]) => {
acc[key] = `#${value}`;
return acc;
}, {});
const DEFAULT_DOMAIN = 'google.com';
const DEFAULT_SERVER = '8.8.8.8';
let currentResults = { addresses: [], subnets: [], ASN: [] };
let availableStorages = [];
let currentStorage = '';
function escapeHtml(value) {
return String(value)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function normalizeListing(values) {
if (!Array.isArray(values)) {
return [];
}
return values
.map(item => (item === null || item === undefined) ? '' : String(item))
.filter(item => item.length > 0);
}
async function loadLocalRepositories() {
try {
const repositories = await JSONRPC.SubscriptionManager.getLocalRepositories();
return normalizeListing(repositories);
} catch (error) {
console.error('Ошибка получения списка хранилищ:', error);
return [];
}
}
function setStatus(message, type) {
const $status = $(SELECTORS.status);
if (!$status.length) {
return;
}
if (!message) {
$status.hide().text('').removeClass('success-message error-message');
return;
}
$status
.removeClass('success-message error-message')
.addClass(type === 'success' ? 'success-message' : 'error-message')
.text(message)
.show();
}
function showFieldError(selector, message) {
const $error = $(selector);
if (!$error.length) {
return;
}
$error.text(message || '').toggle(!!message);
}
function clearFieldError(selector) {
showFieldError(selector, '');
}
function validateDomain(value) {
const trimmed = value.trim();
if (!trimmed) {
return { valid: false, message: 'Укажите домен.' };
}
const domainPattern = /^(?=.{1,253}$)([A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z]{2,}$/;
if (!domainPattern.test(trimmed)) {
return { valid: false, message: 'Некорректный домен.' };
}
return { valid: true, message: '' };
}
function validateServer(value) {
const trimmed = value.trim();
if (!trimmed) {
return { valid: false, message: 'Укажите DNS сервер.' };
}
const ipv4Pattern = /^(25[0-5]|2[0-4]\d|1?\d?\d)(\.(25[0-5]|2[0-4]\d|1?\d?\d)){3}$/;
const domainPattern = /^(?=.{1,253}$)([A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z]{2,}$/;
if (!ipv4Pattern.test(trimmed) && !domainPattern.test(trimmed)) {
return { valid: false, message: 'Введите IP или домен DNS сервера.' };
}
return { valid: true, message: '' };
}
function validateListName(value) {
const trimmed = value.trim();
if (!trimmed) {
return { valid: false, message: 'Имя списка не может быть пустым.' };
}
const namePattern = /^[A-Za-z0-9.]+$/;
if (!namePattern.test(trimmed)) {
return { valid: false, message: 'Допустимы только символы A-Z, a-z, 0-9 и точка.' };
}
return { valid: true, message: '' };
}
function buildListItems(items) {
if (!items.length) {
return '
Список пуст.
';
}
return `${items.map(item => `
${escapeHtml(item)}
`).join('')}
`;
}
function buildAsnItems(items) {
if (!items.length) {
return 'Список пуст.
';
}
return `${items.map(item => escapeHtml(item)).join(', ')}
`;
}
function renderResults(domainValue, storages) {
const addresses = normalizeListing(currentResults.addresses);
const subnets = normalizeListing(currentResults.subnets);
const ASN = normalizeListing(currentResults.ASN);
const storageOptions = Array.isArray(storages) ? storages : [];
if (!storageOptions.includes(currentStorage)) {
currentStorage = storageOptions.length ? storageOptions[0] : '';
}
const storageSelectDisabled = storageOptions.length === 0;
const storageOptionsHtml = storageOptions.length
? storageOptions.map(storage => ``).join('')
: '';
const storageHint = storageOptions.length ? '' : 'Не найдено доступных хранилищ.
';
const addressesDisabled = addresses.length === 0;
const subnetsDisabled = subnets.length === 0;
const asnDisabled = ASN.length === 0;
const html = `
`;
const $results = $(SELECTORS.results);
$results.html(html).show();
}
function updateAddButtonState() {
const $addButton = $(SELECTORS.addButton);
if (!$addButton.length) {
return;
}
const $nameInput = $(SELECTORS.nameInput);
const $storageSelect = $(SELECTORS.storageSelect);
const { valid } = validateListName($nameInput.val() || '');
const hasSelection = [
$(SELECTORS.addressesCheckbox),
$(SELECTORS.subnetsCheckbox),
$(SELECTORS.asnCheckbox)
].some($checkbox => $checkbox.length && !$checkbox.prop('disabled') && $checkbox.prop('checked'));
const storageValue = $storageSelect.length && !$storageSelect.prop('disabled') ? ($storageSelect.val() || '') : '';
$addButton.prop('disabled', !(hasSelection && valid && storageValue.length > 0));
}
async function handleSearch(event) {
event.preventDefault();
setStatus('', '');
const $domainInput = $(SELECTORS.domainInput);
const $serverInput = $(SELECTORS.serverInput);
const domainValidation = validateDomain($domainInput.val() || '');
const serverValidation = validateServer($serverInput.val() || '');
if (!domainValidation.valid) {
showFieldError(SELECTORS.domainError, domainValidation.message);
} else {
clearFieldError(SELECTORS.domainError);
}
if (!serverValidation.valid) {
showFieldError(SELECTORS.serverError, serverValidation.message);
} else {
clearFieldError(SELECTORS.serverError);
}
if (!domainValidation.valid || !serverValidation.valid) {
return;
}
const $button = $(SELECTORS.searchButton);
$button.prop('disabled', true).text('Поиск...');
$(SELECTORS.results).hide().empty();
try {
const result = await JSONRPC.NetworkManager.discoverBlockedDomainResources(
$domainInput.val().trim(),
$serverInput.val().trim()
);
currentResults = {
addresses: normalizeListing(result?.addresses),
subnets: normalizeListing(result?.subnets),
ASN: normalizeListing(result?.ASN)
};
availableStorages = await loadLocalRepositories();
renderResults($domainInput.val().trim(), availableStorages);
updateAddButtonState();
clearFieldError(SELECTORS.nameError);
} catch (error) {
console.error('Ошибка поиска ресурсов домена:', error);
currentResults = { addresses: [], subnets: [], ASN: [] };
setStatus('Не удалось получить данные. Попробуйте позже.', 'error');
} finally {
$button.prop('disabled', false).text('Найти и разблокировать');
}
}
function handleAdd() {
const $nameInput = $(SELECTORS.nameInput);
const validation = validateListName($nameInput.val() || '');
if (!validation.valid) {
showFieldError(SELECTORS.nameError, validation.message);
updateAddButtonState();
return;
}
showFieldError(SELECTORS.nameError, '');
const $addButton = $(SELECTORS.addButton);
$addButton.prop('disabled', true).text('Добавление...');
setStatus('', '');
const domain = ($(SELECTORS.domainInput).val() || '').trim();
const selectedAddresses = $(SELECTORS.addressesCheckbox).prop('checked') ? currentResults.addresses : [];
const selectedSubnets = $(SELECTORS.subnetsCheckbox).prop('checked') ? currentResults.subnets : [];
const selectedAsn = $(SELECTORS.asnCheckbox).prop('checked') ? currentResults.ASN : [];
const storage = $(SELECTORS.storageSelect).val() || '';
if (!storage) {
setStatus('Выберите хранилище.', 'error');
$addButton.prop('disabled', false).text('Добавить');
updateAddButtonState();
return;
}
JSONRPC.SubscriptionManager.writeLocalResourceFile(
$nameInput.val().trim(),
"Unlocked resource: " + $nameInput.val().trim() + " (" + domain + ")",
[domain],
selectedAsn,
selectedSubnets,
selectedAddresses,
storage,
true
).then(() => {
setStatus('Ресурсы успешно добавлены.', 'success');
}).catch(error => {
console.error('Ошибка добавления ресурсов:', error);
setStatus('Не удалось добавить ресурсы.', 'error');
}).finally(() => {
$addButton.prop('disabled', false).text('Добавить');
updateAddButtonState();
});
}
function handleCheckboxChange() {
updateAddButtonState();
}
function handleNameInputChange() {
const $nameInput = $(SELECTORS.nameInput);
const validation = validateListName($nameInput.val() || '');
if (!validation.valid) {
showFieldError(SELECTORS.nameError, validation.message);
} else {
clearFieldError(SELECTORS.nameError);
}
updateAddButtonState();
}
function handleStorageChange() {
currentStorage = $(SELECTORS.storageSelect).val() || '';
updateAddButtonState();
}
function attachEventHandlers() {
$(SELECTORS.form).on('submit', handleSearch);
$(SELECTORS.domainInput).on('input', () => clearFieldError(SELECTORS.domainError));
$(SELECTORS.serverInput).on('input', () => clearFieldError(SELECTORS.serverError));
$(SELECTORS.results).on('change', `input[type="checkbox"]`, handleCheckboxChange);
$(SELECTORS.results).on('change', SELECTORS.storageSelect, handleStorageChange);
$(SELECTORS.results).on('input', SELECTORS.nameInput, handleNameInputChange);
$(SELECTORS.results).on('click', SELECTORS.addButton, handleAdd);
}
function detachEventHandlers() {
$(SELECTORS.form).off('submit', handleSearch);
$(SELECTORS.domainInput).off('input');
$(SELECTORS.serverInput).off('input');
$(SELECTORS.results).off('change');
$(SELECTORS.results).off('input');
$(SELECTORS.results).off('click');
}
function resetState() {
currentResults = { addresses: [], subnets: [], ASN: [] };
currentStorage = '';
availableStorages = [];
setStatus('', '');
}
export const UnlockSitePage = {
render: () => `
`,
mount: () => {
attachEventHandlers();
},
unmount: () => {
detachEventHandlers();
resetState();
$(SELECTORS.results).empty().hide();
}
};