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 = `
${buildListItems(addresses)}
${buildListItems(subnets)}
${buildAsnItems(ASN)}
${storageHint}
`; 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(); } };