diff --git a/js/content.js b/js/content.js
index bb6f63a..7d3c8bb 100644
--- a/js/content.js
+++ b/js/content.js
@@ -6,7 +6,7 @@
} else {
waitForConfigAndInit();
}
-
+
// Ожидание загрузки конфигурации перед инициализацией
function waitForConfigAndInit() {
const core = window.VideoIPRedirect;
@@ -14,22 +14,19 @@
console.error('[CAPS_Enhancer] ❌ CORE не загружен!');
return;
}
-
+
const debugLog = (msg, ...args) => core.debugLog('content', msg, ...args);
-
- // Если конфигурация уже загружена, запускаем инициализацию
+
if (core.configReady) {
debugLog('⚙️ Конфигурация готова, запуск инициализации...');
init();
} else {
- // Иначе ждём события о готовности конфигурации
debugLog('⏳ Ожидание загрузки конфигурации...');
document.addEventListener('VideoIPRedirect:ConfigReady', () => {
debugLog('⚙️ Конфигурация загружена, запуск инициализации...');
init();
}, { once: true });
-
- // Таймаут на случай, если событие не сработает
+
setTimeout(() => {
if (!core.configReady) {
debugLog('⚠️ Таймаут ожидания конфигурации, запуск с настройками по умолчанию...');
@@ -38,25 +35,25 @@
}, 2000);
}
}
-
+
function init() {
const core = window.VideoIPRedirect;
const debugLog = (msg, ...args) => core.debugLog('content', msg, ...args);
-
+
const disabledSites = core.CONFIG.disabledSites || [];
const hostname = window.location.hostname || '';
if (disabledSites.length && disabledSites.includes(hostname)) {
debugLog('⏹ Расширение отключено на этом сайте:', hostname);
return;
}
-
+
debugLog('========== ЗАПУСК ==========');
debugLog('DOM загружен, инициализация...');
debugLog('📋 Текущая конфигурация:', core.CONFIG);
-
+
const pageType = core.checkPageType();
- // Замена логотипа CleverPark на caps.svg (если включено). Лого может появиться позже (SPA), поэтому повторяем попытки + наблюдатель.
+ // Замена логотипа CleverPark на caps.svg (если включено)
const LOGO_SELECTOR = 'img[src*="cleverpark.svg"], img[src*="cleverpark"], img[alt*="CLEVER PARK"], img[alt*="CLEVER"]';
const replaceLogoEnabled = core.CONFIG.replaceLogo !== false;
const hasGetURL = typeof chrome !== 'undefined' && chrome.runtime && typeof chrome.runtime.getURL === 'function';
@@ -66,8 +63,10 @@
const logo = document.querySelector(LOGO_SELECTOR);
if (logo && !logo.dataset.capsReplaced) {
logo.dataset.capsReplaced = '1';
+ logo.setAttribute('data-caps-ui', 'true');
logo.src = chrome.runtime.getURL('icons/caps.svg');
debugLog('🖼️ [LOGO] Заменён на caps.svg', from ? '(' + from + ')' : '');
+ applyVisibilityToElement(logo);
return true;
}
return false;
@@ -87,72 +86,173 @@
setTimeout(() => logoObserver.disconnect(), 10000);
}
}
-
- // ПРИОРИТЕТ 1: Обработка страниц noVNC (переадресация или кнопка)
+
+ // ПРИОРИТЕТ 1: Обработка страниц noVNC
if (pageType.isNoVNCPage && core.NoVNCRedirect) {
const mode = core.CONFIG.novnc.mode;
debugLog(`🖥️ Обнаружена страница noVNC, режим: ${mode}`);
const handled = core.NoVNCRedirect.handleNoVNCPage();
-
- // Если режим redirect, прерываем инициализацию (будет переадресация)
- // Если режим button, продолжаем работу (только добавили кнопку)
if (mode === 'redirect' && handled) {
return;
}
}
-
+
// ПРИОРИТЕТ 2: Автологин на странице входа
- if (core.AutoLogin && core.AutoLogin.isLoginPage()) {
- debugLog('🔐 Обнаружена страница логина');
- core.AutoLogin.tryAutoLogin();
- // Продолжаем инициализацию (не прерываем)
+ if (core.AutoLogin) {
+ if (core.AutoLogin.isLoginPage1()) {
+ debugLog('🔐 Обнаружена страница логина типа 1');
+ core.AutoLogin.tryAutoLogin();
+ } else if (core.AutoLogin.isLoginPage2()) {
+ debugLog('🔐 Обнаружена страница логина типа 2');
+ core.AutoLogin.performAutoLoginPage2();
+ }
}
-
+
if (!pageType.hasAppMarker) {
debugLog('⏹ Не CLEVER PARK, отключаемся');
return;
}
-
+
+ // Кнопка переключения видимости UI (показывается на всех CAPS-страницах)
+ addToggleButton();
+
// Настраиваем Escape
if (core.CloseHandler) {
core.CloseHandler.setupEscapeHandler();
}
-
+
// Видео страница
if (pageType.isVideoPage && core.VideoWindow) {
debugLog('🎬 ВИДЕО СТРАНИЦА');
-
- // Используем утилиту для повторных попыток
core.retryWithBackoff(() => core.VideoWindow.addButtonToVideoWindow());
-
const videoObserver = new MutationObserver(() => {
core.VideoWindow.addButtonToVideoWindow();
});
-
- videoObserver.observe(document.body, {
- childList: true,
- subtree: true
- });
+ videoObserver.observe(document.body, { childList: true, subtree: true });
}
-
+
// Главная страница
if (core.Mnemoschema) {
debugLog('🏠 ГЛАВНАЯ СТРАНИЦА');
-
const addMnemoButton = () => {
if (!core.activeSchemeButton) {
core.Mnemoschema.addSchemeButton();
}
};
-
- // Используем утилиту для повторных попыток
core.retryWithBackoff(addMnemoButton);
}
-
+
+ // Применяем текущий uiVisible после добавления всех элементов
+ setTimeout(() => applyVisibility(core.CONFIG.uiVisible !== false), 200);
+
+ // Слушаем обновления конфигурации для мгновенного применения видимости
+ document.addEventListener('VideoIPRedirect:ConfigUpdated', () => {
+ applyVisibility(core.CONFIG.uiVisible !== false);
+ });
+
+ // Горячая клавиша Shift+F1 — переключение видимости улучшений
+ document.addEventListener('keydown', (e) => {
+ if (e.shiftKey && e.key === 'F1') {
+ e.preventDefault();
+ toggleVisibility();
+ }
+ });
+
debugLog('========== ГОТОВО ==========');
}
-
- // Обработка запроса на однократный вход из popup
+
+ // ---- Управление видимостью UI-улучшений ----
+
+ function applyVisibilityToElement(el) {
+ if (!el) return;
+ const core = window.VideoIPRedirect;
+ const visible = core && core.CONFIG.uiVisible !== false;
+ el.style.display = visible ? '' : 'none';
+ }
+
+ function applyVisibility(visible) {
+ document.querySelectorAll('[data-caps-ui="true"]').forEach(el => {
+ // Кнопка-переключатель не скрывается — иначе не выйти из режима скрытия
+ if (el.classList.contains('caps-ui-toggle-btn')) return;
+ el.style.display = visible ? '' : 'none';
+ });
+
+ // Обновляем иконку кнопки переключателя
+ const toggleBtn = document.querySelector('.caps-ui-toggle-btn');
+ if (toggleBtn) {
+ toggleBtn.title = visible ? 'Скрыть улучшения CAPS (Shift+F1)' : 'Показать улучшения CAPS (Shift+F1)';
+ toggleBtn.textContent = visible ? '👁' : '🙈';
+ }
+ }
+
+ function toggleVisibility() {
+ const core = window.VideoIPRedirect;
+ if (!core) return;
+
+ const newVisible = !(core.CONFIG.uiVisible !== false);
+ core.CONFIG.uiVisible = newVisible;
+
+ applyVisibility(newVisible);
+
+ // Сохраняем в storage и рассылаем другим вкладкам
+ if (typeof chrome !== 'undefined' && chrome.storage) {
+ chrome.storage.sync.get(['config'], (result) => {
+ const cfg = result.config || {};
+ cfg.uiVisible = newVisible;
+ chrome.storage.sync.set({ config: cfg });
+ });
+ }
+
+ if (typeof chrome !== 'undefined' && chrome.runtime) {
+ chrome.runtime.sendMessage({
+ type: 'CONFIG_UPDATED',
+ config: { uiVisible: newVisible }
+ }).catch(() => {});
+ }
+ }
+
+ function addToggleButton() {
+ if (document.querySelector('.caps-ui-toggle-btn')) return;
+
+ const core = window.VideoIPRedirect;
+ const visible = core && core.CONFIG.uiVisible !== false;
+
+ const btn = document.createElement('button');
+ btn.className = 'caps-ui-toggle-btn';
+ btn.setAttribute('data-caps-ui', 'true');
+ btn.textContent = visible ? '👁' : '🙈';
+ btn.title = visible ? 'Скрыть улучшения CAPS (Shift+F1)' : 'Показать улучшения CAPS (Shift+F1)';
+ btn.style.cssText = `
+ position: fixed;
+ bottom: 16px;
+ right: 16px;
+ z-index: 999999;
+ width: 36px;
+ height: 36px;
+ border-radius: 50%;
+ border: none;
+ background: rgba(102, 126, 234, 0.85);
+ color: white;
+ font-size: 18px;
+ cursor: pointer;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.25);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0;
+ line-height: 1;
+ transition: opacity 0.2s;
+ `;
+ btn.onmouseover = () => { btn.style.opacity = '1'; };
+ btn.onmouseout = () => { btn.style.opacity = '0.7'; };
+ btn.style.opacity = '0.7';
+ btn.onclick = () => toggleVisibility();
+
+ document.body.appendChild(btn);
+ }
+
+ // ---- Обработка запросов из popup ----
+
if (typeof chrome !== 'undefined' && chrome.runtime && chrome.runtime.onMessage) {
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'PERFORM_LOGIN') {
@@ -165,6 +265,12 @@
sendResponse(result);
return true;
}
+
+ if (message.type === 'TOGGLE_UI_VISIBILITY') {
+ toggleVisibility();
+ sendResponse({ ok: true });
+ return true;
+ }
});
}
})();
diff --git a/js/options.js b/js/options.js
index 26c17ad..d2fcbd1 100644
--- a/js/options.js
+++ b/js/options.js
@@ -22,7 +22,6 @@ function applyOptionsStrings() {
});
const pageTitle = getStr('options.pageTitle');
if (pageTitle) document.title = pageTitle;
- // Версия — из manifest.json (единый источник)
const versionEl = document.getElementById('app-version');
if (versionEl && typeof chrome !== 'undefined' && chrome.runtime && chrome.runtime.getManifest) {
const manifest = chrome.runtime.getManifest();
@@ -35,69 +34,52 @@ const DEFAULT_CONFIG = {
debug: {
enabled: true,
modules: {
- core: true,
- video: true,
- mnemo: true,
- close: true,
- content: true,
- novnc: true,
- autologin: true
+ core: true, video: true, mnemo: true,
+ close: true, content: true, novnc: true, autologin: true
}
},
- ports: {
- video: '5000',
- scheme: '8080'
- },
- novnc: {
- mode: 'button',
- redirectDelay: 500
- },
- mnemo: {
- mode: 'host-port',
- skipOnDomain: false,
- addVideoButton: true,
- addSchemeButton: true
- },
+ ports: { video: '5000', scheme: '8080' },
+ novnc: { mode: 'button', redirectDelay: 500 },
+ mnemo: { mode: 'host-port', skipOnDomain: false, addVideoButton: true, addSchemeButton: true },
autologin: {
enabled: false,
- username: '',
- password: '',
- autoSubmit: true,
- delay: 500
+ profilePage1: 0,
+ profilePage2: 1,
+ subnetRules: [],
+ profiles: [
+ { name: 'Профиль 1', username: '', password: '', autoSubmit: true, delay: 500 }
+ ]
},
disabledSites: [],
- replaceLogo: true
+ replaceLogo: true,
+ uiVisible: true
};
+// Состояние страницы
+let currentProfiles = [];
+let currentSubnetRules = [];
+
// Элементы формы
const elements = {
- // Порты
portVideo: document.getElementById('port-video'),
portScheme: document.getElementById('port-scheme'),
-
- // noVNC (страница экрана)
novncRedirect: document.getElementById('novnc-redirect'),
redirectDelay: document.getElementById('redirect-delay'),
redirectDelayContainer: document.getElementById('redirect-delay-container'),
-
- // Мнемосхема
mnemoMode: document.getElementById('mnemo-mode'),
mnemoSkipOnDomain: document.getElementById('mnemo-skip-on-domain'),
mnemoAddSchemeButton: document.getElementById('mnemo-add-scheme-button'),
mnemoAddVideoButton: document.getElementById('mnemo-add-video-button'),
-
- // Замена логотипа
replaceLogo: document.getElementById('replace-logo'),
-
- // Автологин
+ uiVisible: document.getElementById('ui-visible'),
autologinEnabled: document.getElementById('autologin-enabled'),
autologinSettings: document.getElementById('autologin-settings'),
- autologinUsername: document.getElementById('autologin-username'),
- autologinPassword: document.getElementById('autologin-password'),
- autologinAutoSubmit: document.getElementById('autologin-autosubmit'),
- autologinDelay: document.getElementById('autologin-delay'),
-
- // Отладка
+ autologinPage1Profile: document.getElementById('autologin-page1-profile'),
+ autologinPage2Profile: document.getElementById('autologin-page2-profile'),
+ autologinProfilesList: document.getElementById('autologin-profiles-list'),
+ autologinAddProfile: document.getElementById('autologin-add-profile'),
+ autologinSubnetRules: document.getElementById('autologin-subnet-rules'),
+ autologinAddSubnetRule: document.getElementById('autologin-add-subnet-rule'),
debugEnabled: document.getElementById('debug-enabled'),
debugModules: document.getElementById('debug-modules'),
debugCore: document.getElementById('debug-core'),
@@ -107,69 +89,312 @@ const elements = {
debugContent: document.getElementById('debug-content'),
debugNovnc: document.getElementById('debug-novnc'),
debugAutologin: document.getElementById('debug-autologin'),
-
- // Кнопки
saveBtn: document.getElementById('save-btn'),
resetBtn: document.getElementById('reset-btn'),
-
- // Статус
status: document.getElementById('status')
};
-// Загрузка настроек при открытии страницы (сначала подставляем строки из strings.js)
+// ---- Рендеринг профилей ----
+
+function renderProfiles() {
+ const container = elements.autologinProfilesList;
+ if (!container) return;
+ container.innerHTML = '';
+
+ currentProfiles.forEach((profile, idx) => {
+ const card = document.createElement('div');
+ card.style.cssText = 'border:1px solid #e2e4ea;border-radius:8px;padding:10px 12px;margin-bottom:8px;background:#fff;';
+
+ const header = document.createElement('div');
+ header.style.cssText = 'display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;';
+
+ const nameInput = document.createElement('input');
+ nameInput.type = 'text';
+ nameInput.value = profile.name || '';
+ nameInput.placeholder = getStr('options.autologinProfileNamePlaceholder');
+ nameInput.style.cssText = 'font-weight:600;border:none;border-bottom:1px solid #ccc;border-radius:0;padding:2px 4px;width:auto;font-size:13px;flex:1;';
+ nameInput.addEventListener('input', () => {
+ currentProfiles[idx].name = nameInput.value;
+ rebuildProfileSelects();
+ });
+
+ const removeBtn = document.createElement('button');
+ removeBtn.type = 'button';
+ removeBtn.textContent = getStr('options.autologinRemoveProfile');
+ removeBtn.title = 'Удалить профиль';
+ removeBtn.style.cssText = 'border:none;background:none;color:#e53935;cursor:pointer;font-size:16px;padding:0 4px;margin-left:8px;';
+ removeBtn.disabled = currentProfiles.length <= 1;
+ removeBtn.addEventListener('click', () => {
+ if (currentProfiles.length <= 1) return;
+ currentProfiles.splice(idx, 1);
+ // Корректируем индексы выбранных профилей
+ const p1 = parseInt(elements.autologinPage1Profile.value) || 0;
+ const p2 = parseInt(elements.autologinPage2Profile.value) || 0;
+ elements.autologinPage1Profile.dataset.value = Math.min(p1, currentProfiles.length - 1);
+ elements.autologinPage2Profile.dataset.value = Math.min(p2, currentProfiles.length - 1);
+ renderProfiles();
+ renderSubnetRules();
+ rebuildProfileSelects();
+ });
+
+ header.appendChild(nameInput);
+ header.appendChild(removeBtn);
+
+ const fields = document.createElement('div');
+ fields.style.cssText = 'display:grid;grid-template-columns:1fr 1fr;gap:8px;';
+
+ fields.appendChild(makeField(
+ getStr('options.autologinUsernameLabel'),
+ 'text', profile.username, getStr('options.autologinUsernamePlaceholder'), 'off',
+ (v) => { currentProfiles[idx].username = v; }
+ ));
+ const pwdField = makeField(
+ getStr('options.autologinPasswordLabel'),
+ 'text', profile.password, '••••••••', 'off',
+ (v) => { currentProfiles[idx].password = v; }
+ );
+ pwdField.querySelector('input').classList.add('password-field');
+ fields.appendChild(pwdField);
+
+ const autoSubmitWrap = document.createElement('div');
+ autoSubmitWrap.style.cssText = 'display:flex;align-items:center;gap:8px;font-size:13px;';
+ const cbLabel = document.createElement('label');
+ cbLabel.style.cssText = 'display:flex;align-items:center;gap:6px;cursor:pointer;';
+ const cb = document.createElement('input');
+ cb.type = 'checkbox';
+ cb.checked = profile.autoSubmit !== false;
+ cb.addEventListener('change', () => { currentProfiles[idx].autoSubmit = cb.checked; });
+ cbLabel.appendChild(cb);
+ cbLabel.appendChild(document.createTextNode(getStr('options.autologinAutoSubmitLabel')));
+ autoSubmitWrap.appendChild(cbLabel);
+
+ const delayWrap = makeField(
+ getStr('options.autologinDelayLabel'),
+ 'number', profile.delay || 500, '500', 'off',
+ (v) => { currentProfiles[idx].delay = parseInt(v) || 500; }
+ );
+ delayWrap.querySelector('input').min = '0';
+ delayWrap.querySelector('input').max = '5000';
+ delayWrap.querySelector('input').step = '100';
+
+ const hint = document.createElement('small');
+ hint.className = 'hint';
+ hint.style.gridColumn = '1/-1';
+ hint.textContent = getStr('options.autologinPasswordHint');
+
+ fields.appendChild(autoSubmitWrap);
+ fields.appendChild(delayWrap);
+ fields.appendChild(hint);
+
+ card.appendChild(header);
+ card.appendChild(fields);
+ container.appendChild(card);
+ });
+}
+
+function makeField(label, type, value, placeholder, autocomplete, onChange) {
+ const wrap = document.createElement('div');
+ const lbl = document.createElement('label');
+ lbl.style.cssText = 'font-size:12px;color:#555;display:block;margin-bottom:3px;';
+ lbl.textContent = label;
+ const inp = document.createElement('input');
+ inp.type = type;
+ inp.value = value !== undefined ? value : '';
+ inp.placeholder = placeholder || '';
+ if (autocomplete) inp.autocomplete = autocomplete;
+ inp.style.cssText = 'width:100%;padding:5px 8px;border:1px solid #ddd;border-radius:6px;font-size:13px;';
+ inp.addEventListener('input', () => onChange(inp.value));
+ wrap.appendChild(lbl);
+ wrap.appendChild(inp);
+ return wrap;
+}
+
+function rebuildProfileSelects() {
+ const saved1 = parseInt(elements.autologinPage1Profile.value);
+ const saved2 = parseInt(elements.autologinPage2Profile.value);
+
+ [elements.autologinPage1Profile, elements.autologinPage2Profile].forEach((sel, selIdx) => {
+ const prev = selIdx === 0 ? (isNaN(saved1) ? 0 : saved1) : (isNaN(saved2) ? 1 : saved2);
+ sel.innerHTML = '';
+ currentProfiles.forEach((p, idx) => {
+ const opt = document.createElement('option');
+ opt.value = idx;
+ opt.textContent = p.name || `Профиль ${idx + 1}`;
+ sel.appendChild(opt);
+ });
+ sel.value = Math.min(prev, currentProfiles.length - 1);
+ });
+
+ // Тоже обновляем selects в правилах подсетей
+ document.querySelectorAll('.subnet-rule-profile-select').forEach((sel) => {
+ const prev = parseInt(sel.value);
+ sel.innerHTML = '';
+ currentProfiles.forEach((p, idx) => {
+ const opt = document.createElement('option');
+ opt.value = idx;
+ opt.textContent = p.name || `Профиль ${idx + 1}`;
+ sel.appendChild(opt);
+ });
+ sel.value = isNaN(prev) ? 0 : Math.min(prev, currentProfiles.length - 1);
+ });
+}
+
+// ---- Рендеринг правил подсетей ----
+
+function renderSubnetRules() {
+ const container = elements.autologinSubnetRules;
+ if (!container) return;
+ container.innerHTML = '';
+
+ if (currentSubnetRules.length === 0) {
+ const empty = document.createElement('p');
+ empty.style.cssText = 'font-size:12px;color:#aaa;margin-bottom:4px;';
+ empty.textContent = 'Нет правил';
+ container.appendChild(empty);
+ return;
+ }
+
+ currentSubnetRules.forEach((rule, idx) => {
+ const row = document.createElement('div');
+ row.style.cssText = 'display:flex;align-items:center;gap:6px;margin-bottom:6px;flex-wrap:wrap;';
+
+ const subnetInput = document.createElement('input');
+ subnetInput.type = 'text';
+ subnetInput.value = rule.subnet || '';
+ subnetInput.placeholder = getStr('options.autologinSubnetPlaceholder');
+ subnetInput.style.cssText = 'width:130px;padding:5px 8px;border:1px solid #ddd;border-radius:6px;font-size:13px;font-family:monospace;';
+ subnetInput.addEventListener('input', () => { currentSubnetRules[idx].subnet = subnetInput.value; });
+
+ const actionSelect = document.createElement('select');
+ actionSelect.style.cssText = 'padding:5px 8px;border:1px solid #ddd;border-radius:6px;font-size:13px;';
+ const optSkip = document.createElement('option');
+ optSkip.value = 'skip';
+ optSkip.textContent = getStr('options.autologinSubnetActionSkip');
+ const optProfile = document.createElement('option');
+ optProfile.value = 'profile';
+ optProfile.textContent = getStr('options.autologinSubnetActionProfile');
+ actionSelect.appendChild(optSkip);
+ actionSelect.appendChild(optProfile);
+ actionSelect.value = rule.action || 'skip';
+
+ const profileSelect = document.createElement('select');
+ profileSelect.className = 'subnet-rule-profile-select';
+ profileSelect.style.cssText = 'padding:5px 8px;border:1px solid #ddd;border-radius:6px;font-size:13px;';
+ currentProfiles.forEach((p, pi) => {
+ const opt = document.createElement('option');
+ opt.value = pi;
+ opt.textContent = p.name || `Профиль ${pi + 1}`;
+ profileSelect.appendChild(opt);
+ });
+ profileSelect.value = rule.profileIndex || 0;
+ profileSelect.style.display = rule.action === 'profile' ? '' : 'none';
+
+ actionSelect.addEventListener('change', () => {
+ currentSubnetRules[idx].action = actionSelect.value;
+ profileSelect.style.display = actionSelect.value === 'profile' ? '' : 'none';
+ });
+ profileSelect.addEventListener('change', () => {
+ currentSubnetRules[idx].profileIndex = parseInt(profileSelect.value);
+ });
+
+ const removeBtn = document.createElement('button');
+ removeBtn.type = 'button';
+ removeBtn.textContent = getStr('options.autologinRemoveSubnetRule');
+ removeBtn.title = 'Удалить правило';
+ removeBtn.style.cssText = 'border:none;background:none;color:#e53935;cursor:pointer;font-size:16px;padding:0 4px;';
+ removeBtn.addEventListener('click', () => {
+ currentSubnetRules.splice(idx, 1);
+ renderSubnetRules();
+ });
+
+ row.appendChild(subnetInput);
+ row.appendChild(actionSelect);
+ row.appendChild(profileSelect);
+ row.appendChild(removeBtn);
+ container.appendChild(row);
+ });
+}
+
+// ---- Загрузка настроек ----
+
document.addEventListener('DOMContentLoaded', () => {
applyOptionsStrings();
loadSettings();
});
-// Обработчики событий
elements.saveBtn.addEventListener('click', saveSettings);
elements.resetBtn.addEventListener('click', resetSettings);
elements.debugEnabled.addEventListener('change', toggleDebugModules);
elements.novncRedirect.addEventListener('change', toggleRedirectDelay);
elements.autologinEnabled.addEventListener('change', toggleAutologinSettings);
+elements.autologinAddProfile.addEventListener('click', () => {
+ currentProfiles.push({ name: `Профиль ${currentProfiles.length + 1}`, username: '', password: '', autoSubmit: true, delay: 500 });
+ renderProfiles();
+ rebuildProfileSelects();
+});
+elements.autologinAddSubnetRule.addEventListener('click', () => {
+ currentSubnetRules.push({ subnet: '', action: 'skip', profileIndex: 0 });
+ renderSubnetRules();
+});
-// Загрузка сохранённых настроек
function loadSettings() {
- console.log('Загрузка настроек...');
-
chrome.storage.sync.get(['config'], (result) => {
if (chrome.runtime.lastError) {
- console.error('Ошибка загрузки:', chrome.runtime.lastError);
showStatus(getStr('options.statusLoadError') + ' ' + chrome.runtime.lastError.message, 'error');
return;
}
-
+
const config = result.config || DEFAULT_CONFIG;
- console.log('Настройки получены из storage:', result);
- console.log('Используемая конфигурация:', config);
-
+
// Порты
elements.portVideo.value = config.ports?.video || DEFAULT_CONFIG.ports.video;
elements.portScheme.value = config.ports?.scheme || DEFAULT_CONFIG.ports.scheme;
-
+
// noVNC
elements.novncRedirect.checked = (config.novnc?.mode || DEFAULT_CONFIG.novnc.mode) === 'redirect';
elements.redirectDelay.value = config.novnc?.redirectDelay || DEFAULT_CONFIG.novnc.redirectDelay;
-
+
// Мнемосхема
const mnemoMode = config.mnemo?.mode;
- const mnemoSkip = config.mnemo?.skipOnDomain === true || mnemoMode === 'skip-on-domain';
elements.mnemoMode.value = (mnemoMode === 'ip-scheme' ? 'host-scheme' : (mnemoMode === 'skip-on-domain' || mnemoMode === 'default' ? 'host-port' : (mnemoMode || 'host-port')));
- elements.mnemoSkipOnDomain.checked = mnemoSkip;
+ elements.mnemoSkipOnDomain.checked = config.mnemo?.skipOnDomain === true || mnemoMode === 'skip-on-domain';
elements.mnemoAddSchemeButton.checked = config.mnemo?.addSchemeButton !== false;
elements.mnemoAddVideoButton.checked = config.mnemo?.addVideoButton !== false;
- // Замена логотипа
+ // Внешний вид
elements.replaceLogo.checked = config.replaceLogo !== false;
+ elements.uiVisible.checked = config.uiVisible !== false;
+
+ // Автологин — загружаем профили (с миграцией старого формата)
+ const al = config.autologin || {};
+ if (Array.isArray(al.profiles) && al.profiles.length > 0) {
+ currentProfiles = al.profiles.map(p => ({ ...p }));
+ } else if (al.username || al.password) {
+ // Миграция старого формата
+ currentProfiles = [
+ { name: 'Профиль 1', username: al.username || '', password: al.password || '', autoSubmit: al.autoSubmit !== false, delay: al.delay || 500 }
+ ];
+ if (al.username2 || al.password2) {
+ currentProfiles.push({ name: 'Профиль 2', username: al.username2 || '', password: al.password2 || '', autoSubmit: true, delay: 500 });
+ }
+ } else {
+ currentProfiles = [{ name: 'Профиль 1', username: '', password: '', autoSubmit: true, delay: 500 }];
+ }
+
+ currentSubnetRules = Array.isArray(al.subnetRules) ? al.subnetRules.map(r => ({ ...r })) : [];
+
+ elements.autologinEnabled.checked = al.enabled || false;
+
+ renderProfiles();
+ renderSubnetRules();
+ rebuildProfileSelects();
+
+ // Выставляем выбранные профили
+ const p1 = typeof al.profilePage1 === 'number' ? al.profilePage1 : 0;
+ const p2 = typeof al.profilePage2 === 'number' ? al.profilePage2 : Math.min(1, currentProfiles.length - 1);
+ elements.autologinPage1Profile.value = Math.min(p1, currentProfiles.length - 1);
+ elements.autologinPage2Profile.value = Math.min(p2, currentProfiles.length - 1);
- // Автологин
- elements.autologinEnabled.checked = config.autologin?.enabled || false;
- elements.autologinUsername.value = config.autologin?.username || '';
- elements.autologinPassword.value = config.autologin?.password || '';
- elements.autologinAutoSubmit.checked = config.autologin?.autoSubmit !== false;
- elements.autologinDelay.value = config.autologin?.delay || DEFAULT_CONFIG.autologin.delay;
-
// Отладка
elements.debugEnabled.checked = config.debug?.enabled !== false;
elements.debugCore.checked = config.debug?.modules?.core !== false;
@@ -179,36 +404,28 @@ function loadSettings() {
elements.debugContent.checked = config.debug?.modules?.content !== false;
elements.debugNovnc.checked = config.debug?.modules?.novnc !== false;
elements.debugAutologin.checked = config.debug?.modules?.autologin !== false;
-
- // Обновляем UI
+
toggleDebugModules();
toggleRedirectDelay();
toggleAutologinSettings();
-
- console.log('✅ Настройки загружены успешно');
});
}
-// Сохранение настроек
+// ---- Сохранение настроек ----
+
function saveSettings() {
- // Валидация портов
const portVideo = elements.portVideo.value.trim();
const portScheme = elements.portScheme.value.trim();
-
+
if (!portVideo || isNaN(portVideo) || portVideo < 1 || portVideo > 65535) {
showStatus(getStr('options.errorPortVideo'), 'error');
return;
}
-
if (!portScheme || isNaN(portScheme) || portScheme < 1 || portScheme > 65535) {
showStatus(getStr('options.errorPortScheme'), 'error');
return;
}
-
- const redirectDelay = parseInt(elements.redirectDelay.value) || 500;
- const autologinDelay = parseInt(elements.autologinDelay.value) || 500;
-
- // Собираем конфигурацию
+
const config = {
debug: {
enabled: elements.debugEnabled.checked,
@@ -222,13 +439,10 @@ function saveSettings() {
autologin: elements.debugAutologin.checked
}
},
- ports: {
- video: portVideo,
- scheme: portScheme
- },
+ ports: { video: portVideo, scheme: portScheme },
novnc: {
mode: elements.novncRedirect.checked ? 'redirect' : 'button',
- redirectDelay: redirectDelay
+ redirectDelay: parseInt(elements.redirectDelay.value) || 500
},
mnemo: {
mode: elements.mnemoMode.value,
@@ -237,58 +451,46 @@ function saveSettings() {
addSchemeButton: elements.mnemoAddSchemeButton.checked
},
replaceLogo: elements.replaceLogo.checked,
+ uiVisible: elements.uiVisible.checked,
autologin: {
enabled: elements.autologinEnabled.checked,
- username: elements.autologinUsername.value.trim(),
- password: elements.autologinPassword.value,
- autoSubmit: elements.autologinAutoSubmit.checked,
- delay: autologinDelay
+ profilePage1: parseInt(elements.autologinPage1Profile.value) || 0,
+ profilePage2: parseInt(elements.autologinPage2Profile.value) || 0,
+ profiles: currentProfiles.map(p => ({ ...p })),
+ subnetRules: currentSubnetRules.map(r => ({ ...r }))
}
};
- // Сохраняем список отключённых сайтов из текущего storage
+
chrome.storage.sync.get(['config'], (prev) => {
if (prev.config && Array.isArray(prev.config.disabledSites)) {
config.disabledSites = prev.config.disabledSites;
} else {
config.disabledSites = [];
}
-
- // Сохраняем в chrome.storage
- chrome.storage.sync.set({ config }, () => {
- if (chrome.runtime.lastError) {
- console.error('Ошибка сохранения:', chrome.runtime.lastError);
- showStatus(getStr('options.statusSaveError') + ' ' + chrome.runtime.lastError.message, 'error');
- return;
- }
-
- console.log('Настройки сохранены:', config);
- showStatus(getStr('options.statusSaveOk'), 'success');
-
- // Отправляем сообщение всем вкладкам для обновления конфига
- chrome.tabs.query({}, (tabs) => {
- tabs.forEach(tab => {
- chrome.tabs.sendMessage(tab.id, {
- type: 'CONFIG_UPDATED',
- config: config
- }, (response) => {
- // Игнорируем ошибки для вкладок, где нет content script
- if (chrome.runtime.lastError) {
- // Это нормально, не все вкладки имеют content script
- }
+
+ chrome.storage.sync.set({ config }, () => {
+ if (chrome.runtime.lastError) {
+ showStatus(getStr('options.statusSaveError') + ' ' + chrome.runtime.lastError.message, 'error');
+ return;
+ }
+ showStatus(getStr('options.statusSaveOk'), 'success');
+ chrome.tabs.query({}, (tabs) => {
+ tabs.forEach(tab => {
+ chrome.tabs.sendMessage(tab.id, { type: 'CONFIG_UPDATED', config }).catch(() => {});
});
});
});
});
- });
}
-// Сброс настроек к значениям по умолчанию
+// ---- Сброс настроек ----
+
function resetSettings() {
- if (!confirm(getStr('options.confirmReset'))) {
- return;
- }
-
- // Устанавливаем значения по умолчанию
+ if (!confirm(getStr('options.confirmReset'))) return;
+
+ currentProfiles = DEFAULT_CONFIG.autologin.profiles.map(p => ({ ...p }));
+ currentSubnetRules = [];
+
elements.portVideo.value = DEFAULT_CONFIG.ports.video;
elements.portScheme.value = DEFAULT_CONFIG.ports.scheme;
elements.novncRedirect.checked = DEFAULT_CONFIG.novnc.mode === 'redirect';
@@ -298,11 +500,8 @@ function resetSettings() {
elements.mnemoAddSchemeButton.checked = DEFAULT_CONFIG.mnemo.addSchemeButton;
elements.mnemoAddVideoButton.checked = DEFAULT_CONFIG.mnemo.addVideoButton;
elements.replaceLogo.checked = DEFAULT_CONFIG.replaceLogo !== false;
+ elements.uiVisible.checked = DEFAULT_CONFIG.uiVisible !== false;
elements.autologinEnabled.checked = DEFAULT_CONFIG.autologin.enabled;
- elements.autologinUsername.value = DEFAULT_CONFIG.autologin.username;
- elements.autologinPassword.value = DEFAULT_CONFIG.autologin.password;
- elements.autologinAutoSubmit.checked = DEFAULT_CONFIG.autologin.autoSubmit;
- elements.autologinDelay.value = DEFAULT_CONFIG.autologin.delay;
elements.debugEnabled.checked = DEFAULT_CONFIG.debug.enabled;
elements.debugCore.checked = DEFAULT_CONFIG.debug.modules.core;
elements.debugVideo.checked = DEFAULT_CONFIG.debug.modules.video;
@@ -311,10 +510,12 @@ function resetSettings() {
elements.debugContent.checked = DEFAULT_CONFIG.debug.modules.content;
elements.debugNovnc.checked = DEFAULT_CONFIG.debug.modules.novnc;
elements.debugAutologin.checked = DEFAULT_CONFIG.debug.modules.autologin;
-
- // Сохраняем
+
+ renderProfiles();
+ renderSubnetRules();
+ rebuildProfileSelects();
+
chrome.storage.sync.set({ config: DEFAULT_CONFIG }, () => {
- console.log('Настройки сброшены к значениям по умолчанию');
showStatus(getStr('options.statusResetDone'), 'success');
toggleDebugModules();
toggleRedirectDelay();
@@ -322,7 +523,8 @@ function resetSettings() {
});
}
-// Показать/скрыть модули отладки (при загрузке без .expanded блок изначально скрыт)
+// ---- Вспомогательные функции ----
+
function toggleDebugModules() {
if (elements.debugEnabled.checked) {
elements.debugModules.classList.add('expanded');
@@ -331,16 +533,10 @@ function toggleDebugModules() {
}
}
-// Показать/скрыть поле задержки переадресации
function toggleRedirectDelay() {
- if (elements.novncRedirect.checked) {
- elements.redirectDelayContainer.style.display = 'block';
- } else {
- elements.redirectDelayContainer.style.display = 'none';
- }
+ elements.redirectDelayContainer.style.display = elements.novncRedirect.checked ? 'block' : 'none';
}
-// Показать/скрыть настройки автологина
function toggleAutologinSettings() {
if (elements.autologinEnabled.checked) {
elements.autologinSettings.classList.remove('disabled');
@@ -349,48 +545,13 @@ function toggleAutologinSettings() {
}
}
-// Показать статус сообщение
function showStatus(message, type = 'success') {
elements.status.textContent = message;
elements.status.className = `status ${type}`;
elements.status.classList.remove('hidden');
-
- // Автоматически скрыть через 5 секунд
- setTimeout(() => {
- elements.status.classList.add('hidden');
- }, 5000);
+ setTimeout(() => { elements.status.classList.add('hidden'); }, 5000);
}
-// Валидация при вводе (только цифры для портов)
-elements.portVideo.addEventListener('input', (e) => {
- e.target.value = e.target.value.replace(/[^0-9]/g, '');
-});
-
-elements.portScheme.addEventListener('input', (e) => {
- e.target.value = e.target.value.replace(/[^0-9]/g, '');
-});
-
-// Тестирование chrome.storage при загрузке страницы
-console.log('=== Проверка chrome.storage API ===');
-console.log('chrome.storage доступен:', typeof chrome.storage !== 'undefined');
-console.log('chrome.storage.sync доступен:', typeof chrome.storage?.sync !== 'undefined');
-
-// Тестовое сохранение и чтение
-if (typeof chrome.storage !== 'undefined' && chrome.storage.sync) {
- chrome.storage.sync.set({ test: 'test_value' }, () => {
- if (chrome.runtime.lastError) {
- console.error('❌ Тест сохранения FAILED:', chrome.runtime.lastError);
- } else {
- console.log('✅ Тест сохранения OK');
- chrome.storage.sync.get(['test'], (result) => {
- if (chrome.runtime.lastError) {
- console.error('❌ Тест чтения FAILED:', chrome.runtime.lastError);
- } else {
- console.log('✅ Тест чтения OK:', result);
- }
- });
- }
- });
-} else {
- console.error('❌ chrome.storage.sync недоступен!');
-}
+// Валидация при вводе портов
+elements.portVideo.addEventListener('input', (e) => { e.target.value = e.target.value.replace(/[^0-9]/g, ''); });
+elements.portScheme.addEventListener('input', (e) => { e.target.value = e.target.value.replace(/[^0-9]/g, ''); });
diff --git a/js/popup.js b/js/popup.js
index f6a4beb..fa14c44 100644
--- a/js/popup.js
+++ b/js/popup.js
@@ -3,13 +3,15 @@
const DEFAULT_CONFIG = {
debug: { enabled: true },
disabledSites: [],
- autologin: { username: '', password: '', delay: 500 }
+ uiVisible: true,
+ autologin: { profiles: [], profilePage1: 0 }
};
const el = {
disableOnSite: document.getElementById('disable-on-site'),
disableOnSiteLabel: document.getElementById('disable-on-site-label'),
debug: document.getElementById('debug'),
+ uiVisible: document.getElementById('ui-visible'),
btnLogin: document.getElementById('btn-login'),
loginStatus: document.getElementById('login-status'),
reloadRow: document.getElementById('reload-row'),
@@ -93,6 +95,8 @@ async function init() {
const debug = config.debug || DEFAULT_CONFIG.debug;
el.debug.checked = debug.enabled !== false;
+ el.uiVisible.checked = config.uiVisible !== false;
+
function showReloadButton() {
if (el.reloadRow) el.reloadRow.classList.remove('hidden');
}
@@ -117,6 +121,15 @@ async function init() {
showReloadButton();
});
+ el.uiVisible.addEventListener('change', async () => {
+ await saveConfig({ uiVisible: el.uiVisible.checked });
+ // Немедленно применяем на активной вкладке через сообщение
+ const tab = await getActiveTab();
+ if (tab && tab.id) {
+ chrome.tabs.sendMessage(tab.id, { type: 'TOGGLE_UI_VISIBILITY' }).catch(() => {});
+ }
+ });
+
el.btnReload.addEventListener('click', async () => {
const tab = await getActiveTab();
if (tab && tab.id) {
@@ -132,9 +145,11 @@ async function init() {
return;
}
const config = await loadConfig();
- const user = (config.autologin || {}).username;
- const pass = (config.autologin || {}).password;
- if (!user || !pass) {
+ const al = config.autologin || {};
+ const profiles = Array.isArray(al.profiles) ? al.profiles : [];
+ const profileIdx = al.profilePage1 || 0;
+ const profile = profiles[profileIdx] || profiles[0];
+ if (!profile || !profile.username || !profile.password) {
showLoginStatus(getStr('popup.statusNoCredentials'), 'error');
return;
}
@@ -143,7 +158,8 @@ async function init() {
try {
const response = await chrome.tabs.sendMessage(tab.id, { type: 'PERFORM_LOGIN' });
if (response && response.ok) {
- showLoginStatus(getStr('popup.statusLoginOk'), 'success');
+ const profileName = response.profileName ? ` (${response.profileName})` : '';
+ showLoginStatus(getStr('popup.statusLoginOk') + profileName, 'success');
} else {
const err = (response && response.error) || 'error';
const msg = err === 'not_login_page' ? getStr('popup.statusNotLoginPage') : err === 'no_credentials' ? getStr('popup.statusNoCredentialsShort') : getStr('popup.statusLoginError');
diff --git a/js/strings.js b/js/strings.js
index 38644d4..390f08d 100644
--- a/js/strings.js
+++ b/js/strings.js
@@ -9,6 +9,7 @@ window.CAPS_STRINGS = {
siteWorking: "Расширение работает на этом сайте",
siteDisabled: "Расширение отключено на этом сайте",
debug: "Дебаг",
+ uiVisible: "Показывать улучшения",
login: "Автологин",
reload: "🔄 Перезагрузить страницу",
fullSettings: "⚙️ Полные настройки",
@@ -61,7 +62,7 @@ window.CAPS_STRINGS = {
// Автологин
autologinTitle: "🔐 Автологин",
autologinEnabledLabel: "Включить автозаполнение",
- autologinEnabledDesc: "Автоматически заполнять логин и пароль на :5000/login",
+ autologinEnabledDesc: "Автоматически заполнять логин и пароль на странице входа",
autologinUsernameLabel: "Логин",
autologinUsernameDesc: "",
autologinUsernamePlaceholder: "admin",
@@ -73,6 +74,28 @@ window.CAPS_STRINGS = {
autologinDelayLabel: "Задержка (мс)",
autologinDelayPlaceholder: "500",
+ // Профили автологина
+ autologinProfilesTitle: "Профили учётных данных",
+ autologinProfileNameLabel: "Имя профиля",
+ autologinProfileNamePlaceholder: "Профиль 1",
+ autologinAddProfile: "➕ Добавить профиль",
+ autologinRemoveProfile: "✕",
+ autologinPage1ProfileLabel: "Профиль для страницы входа (тип 1, порт 5000)",
+ autologinPage2ProfileLabel: "Профиль для страницы входа (тип 2, auth-logo)",
+
+ // Правила подсетей
+ autologinSubnetRulesTitle: "Правила для подсетей",
+ autologinSubnetRulesDesc: "При совпадении хоста с подсетью — пропустить или использовать другой профиль",
+ autologinSubnetPlaceholder: "192.168.1.",
+ autologinSubnetActionSkip: "Пропустить",
+ autologinSubnetActionProfile: "Использовать профиль",
+ autologinAddSubnetRule: "➕ Добавить правило",
+ autologinRemoveSubnetRule: "✕",
+
+ // Видимость UI
+ appearanceUiVisibleLabel: "Показывать улучшения расширения",
+ appearanceUiVisibleDesc: "Кнопки, замена логотипа. Можно также переключить через Shift+F1",
+
// Отладка
debugTitle: "🐛 Отладка",
debugEnabledLabel: "Включить логи в консоль",
diff --git a/modules/auto-login.js b/modules/auto-login.js
index 4651dd4..43d008d 100644
--- a/modules/auto-login.js
+++ b/modules/auto-login.js
@@ -6,93 +6,82 @@
console.error('[VideoIPRedirect] CORE не загружен!');
return;
}
-
+
const debugLog = (msg, ...args) => core.debugLog('autologin', msg, ...args);
-
+
debugLog('🔐 Загрузка модуля...');
-
- // Проверка, является ли страница страницей логина
- function isLoginPage() {
- // Проверка 1: Порт 5000
- const port = window.location.port;
- if (port !== '5000') {
- return false;
- }
-
- // Проверка 2: URL содержит /login
- const path = window.location.pathname;
- if (!path.includes('/login')) {
- return false;
- }
-
- // Проверка 3: Наличие полей логина и пароля
+
+ // Получить профиль по индексу
+ function getProfile(index) {
+ const profiles = core.CONFIG.autologin.profiles;
+ if (!Array.isArray(profiles) || profiles.length === 0) return null;
+ const idx = (typeof index === 'number' && index >= 0 && index < profiles.length) ? index : 0;
+ return profiles[idx] || profiles[0];
+ }
+
+ // Найти правило подсети для текущего хоста
+ function getSubnetRule() {
+ const host = window.location.hostname;
+ const rules = core.CONFIG.autologin.subnetRules || [];
+ return rules.find(r => r.subnet && host.startsWith(r.subnet)) || null;
+ }
+
+ // Проверка страницы логина типа 1 (порт 5000, /login, поля username/password)
+ function isLoginPage1() {
+ if (window.location.port !== '5000') return false;
+ if (!window.location.pathname.includes('/login')) return false;
const usernameField = document.querySelector('input[name="username"]');
const passwordField = document.querySelector('input[name="password"]');
-
- if (!usernameField || !passwordField) {
- return false;
- }
-
+ return !!(usernameField && passwordField);
+ }
+
+ // Проверка страницы логина типа 2 (auth-logo.png + react-notification-root + noscript)
+ function isLoginPage2() {
+ const hasNoscript = Array.from(document.querySelectorAll('noscript'))
+ .some(n => n.textContent.includes('You need to enable JavaScript to run this app'));
+ if (!hasNoscript) return false;
+
+ const hasNotificationRoot = !!document.querySelector('div.react-notification-root, div[class*="react-notification-root"]');
+ if (!hasNotificationRoot) return false;
+
+ const hasAuthLogo = !!document.querySelector('img[src="/images/auth-logo.png"]');
+ if (!hasAuthLogo) return false;
+
return true;
}
-
- // Главная функция автологина
- function performAutoLogin() {
- debugLog('========== ПРОВЕРКА АВТОЛОГИНА ==========');
-
- // Проверяем, включен ли автологин
- if (!core.CONFIG.autologin.enabled) {
- debugLog('⏹ Автологин отключен в настройках');
+
+ // Заполнить форму логина данными профиля и нажать кнопку входа
+ function performLoginWithProfile(profile) {
+ if (!profile || !profile.username || !profile.password) {
+ debugLog('⚠️ Профиль не задан или пустые учётные данные');
return false;
}
-
- // Проверяем, что это страница логина
- if (!isLoginPage()) {
- debugLog('❌ Это не страница логина');
- return false;
- }
-
- debugLog('✅ Обнаружена страница логина');
-
- // Получаем данные для входа
- const username = core.CONFIG.autologin.username;
- const password = core.CONFIG.autologin.password;
-
- if (!username || !password) {
- debugLog('⚠️ Логин или пароль не заданы в настройках');
- return false;
- }
-
- debugLog('🔑 Данные для входа найдены');
-
- // Находим поля
+
const usernameField = document.querySelector('input[name="username"]');
const passwordField = document.querySelector('input[name="password"]');
const submitButton = document.querySelector('button[type="submit"]');
-
+
if (!usernameField || !passwordField) {
debugLog('❌ Не удалось найти поля ввода');
return false;
}
-
- // Заполняем поля
- debugLog('✍️ Заполнение полей...');
- usernameField.value = username;
- passwordField.value = password;
-
- // Генерируем события для совместимости с JS-фреймворками
+
+ debugLog(`✍️ Заполнение полей (профиль: ${profile.name || 'без имени'})...`);
+
+ // React перехватывает нативный сеттер value — используем его напрямую,
+ // чтобы обновление дошло до React-state компонента
+ const nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
+ nativeSetter.call(usernameField, profile.username);
usernameField.dispatchEvent(new Event('input', { bubbles: true }));
- usernameField.dispatchEvent(new Event('change', { bubbles: true }));
+ nativeSetter.call(passwordField, profile.password);
passwordField.dispatchEvent(new Event('input', { bubbles: true }));
- passwordField.dispatchEvent(new Event('change', { bubbles: true }));
-
+
debugLog('✅ Поля заполнены');
-
- // Автоматический вход, если включен
- if (core.CONFIG.autologin.autoSubmit && submitButton) {
- const delay = core.CONFIG.autologin.delay || 500;
+
+ const autoSubmit = typeof profile.autoSubmit === 'boolean' ? profile.autoSubmit : true;
+ if (autoSubmit && submitButton) {
+ const delay = profile.delay || 500;
debugLog(`⏱️ Вход через ${delay}мс...`);
-
setTimeout(() => {
debugLog('🚀 Нажатие кнопки "Вход"');
submitButton.click();
@@ -100,58 +89,141 @@
} else {
debugLog('ℹ️ Автоматический вход отключен');
}
-
- debugLog('========== АВТОЛОГИН ЗАВЕРШЕН ==========');
+
return true;
}
-
- // Функция с повторными попытками (на случай если DOM еще не загружен)
+
+ // Главная функция автологина для страницы типа 1 (с retry-логикой)
+ function performAutoLogin() {
+ debugLog('========== ПРОВЕРКА АВТОЛОГИНА (тип 1) ==========');
+
+ if (!core.CONFIG.autologin.enabled) {
+ debugLog('⏹ Автологин отключен в настройках');
+ return false;
+ }
+
+ if (!isLoginPage1()) {
+ debugLog('❌ Это не страница логина типа 1');
+ return false;
+ }
+
+ debugLog('✅ Обнаружена страница логина типа 1');
+
+ // Проверяем правила подсетей
+ const rule = getSubnetRule();
+ if (rule) {
+ debugLog(`📋 Найдено правило подсети: "${rule.subnet}", действие: "${rule.action}"`);
+ if (rule.action === 'skip') {
+ debugLog('⏭ Автологин пропущен по правилу подсети');
+ return false;
+ }
+ if (rule.action === 'profile') {
+ const overrideProfile = getProfile(rule.profileIndex);
+ debugLog(`🔄 Используем профиль по правилу подсети: ${overrideProfile?.name}`);
+ return performLoginWithProfile(overrideProfile);
+ }
+ }
+
+ const profile = getProfile(core.CONFIG.autologin.profilePage1);
+ debugLog(`🔑 Профиль для типа 1: ${profile?.name}`);
+ return performLoginWithProfile(profile);
+ }
+
+ // Автологин для страницы типа 1 — с повторными попытками
function tryAutoLogin(attempts = 3) {
- debugLog(`🔄 Попытка автологина (осталось попыток: ${attempts})`);
-
+ debugLog(`🔄 Попытка автологина типа 1 (осталось попыток: ${attempts})`);
+
const success = performAutoLogin();
-
+
if (!success && attempts > 0) {
setTimeout(() => tryAutoLogin(attempts - 1), 500);
}
}
-
- // Однократный вход по кнопке (без проверки enabled, всегда отправка формы)
+
+ // Автологин для страницы типа 2 — одна попытка, без retry
+ function performAutoLoginPage2() {
+ debugLog('========== ПРОВЕРКА АВТОЛОГИНА (тип 2) ==========');
+
+ if (!core.CONFIG.autologin.enabled) {
+ debugLog('⏹ Автологин отключен в настройках');
+ return false;
+ }
+
+ if (!isLoginPage2()) {
+ debugLog('❌ Это не страница логина типа 2');
+ return false;
+ }
+
+ debugLog('✅ Обнаружена страница логина типа 2');
+
+ // Проверяем правила подсетей
+ const rule = getSubnetRule();
+ if (rule) {
+ debugLog(`📋 Найдено правило подсети: "${rule.subnet}", действие: "${rule.action}"`);
+ if (rule.action === 'skip') {
+ debugLog('⏭ Автологин пропущен по правилу подсети');
+ return false;
+ }
+ if (rule.action === 'profile') {
+ const overrideProfile = getProfile(rule.profileIndex);
+ debugLog(`🔄 Используем профиль по правилу подсети: ${overrideProfile?.name}`);
+ return performLoginWithProfile(overrideProfile);
+ }
+ }
+
+ const profile = getProfile(core.CONFIG.autologin.profilePage2);
+ debugLog(`🔑 Профиль для типа 2: ${profile?.name}`);
+ return performLoginWithProfile(profile);
+ // Намеренно без retry — одна попытка
+ }
+
+ // Однократный вход по кнопке из попапа (без проверки enabled, всегда отправка формы)
function performLoginOnce() {
- if (!isLoginPage()) {
+ if (!isLoginPage1() && !isLoginPage2()) {
return { ok: false, error: 'not_login_page' };
}
- const username = core.CONFIG.autologin.username;
- const password = core.CONFIG.autologin.password;
- if (!username || !password) {
+
+ const isPage2 = isLoginPage2();
+ const profileIndex = isPage2
+ ? core.CONFIG.autologin.profilePage2
+ : core.CONFIG.autologin.profilePage1;
+ const profile = getProfile(profileIndex);
+
+ if (!profile || !profile.username || !profile.password) {
return { ok: false, error: 'no_credentials' };
}
+
const usernameField = document.querySelector('input[name="username"]');
const passwordField = document.querySelector('input[name="password"]');
- const submitButton = document.querySelector('button[type="submit"]');
if (!usernameField || !passwordField) {
return { ok: false, error: 'no_fields' };
}
- usernameField.value = username;
- passwordField.value = password;
+
+ const nativeSetter2 = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
+ nativeSetter2.call(usernameField, profile.username);
usernameField.dispatchEvent(new Event('input', { bubbles: true }));
- usernameField.dispatchEvent(new Event('change', { bubbles: true }));
+ nativeSetter2.call(passwordField, profile.password);
passwordField.dispatchEvent(new Event('input', { bubbles: true }));
- passwordField.dispatchEvent(new Event('change', { bubbles: true }));
+
+ const submitButton = document.querySelector('button[type="submit"]');
if (submitButton) {
- const delay = core.CONFIG.autologin.delay || 500;
+ const delay = profile.delay || 500;
setTimeout(() => submitButton.click(), delay);
}
- return { ok: true };
+
+ return { ok: true, profileName: profile.name || '' };
}
-
+
// Публичный API
window.VideoIPRedirect.AutoLogin = {
- performAutoLogin: performAutoLogin,
tryAutoLogin: tryAutoLogin,
- isLoginPage: isLoginPage,
- performLoginOnce: performLoginOnce
+ performAutoLoginPage2: performAutoLoginPage2,
+ isLoginPage1: isLoginPage1,
+ isLoginPage2: isLoginPage2,
+ performLoginOnce: performLoginOnce,
+ getProfile: getProfile,
+ getSubnetRule: getSubnetRule
};
-
+
debugLog('✅ Модуль загружен');
})();
diff --git a/modules/core.js b/modules/core.js
index 9cea9b7..b1f545f 100644
--- a/modules/core.js
+++ b/modules/core.js
@@ -40,17 +40,22 @@ let CONFIG = {
// Настройки автологина
autologin: {
enabled: false, // Включить/выключить автологин
- username: '', // Логин для автозаполнения
- password: '', // Пароль для автозаполнения
- autoSubmit: true, // Автоматически нажимать кнопку "Вход"
- delay: 500 // Задержка перед автовходом (мс)
+ profilePage1: 0, // Индекс профиля для страницы логина типа 1 (порт 5000, /login)
+ profilePage2: 1, // Индекс профиля для страницы логина типа 2 (auth-logo.png)
+ subnetRules: [], // Правила по подсетям: [{ subnet, action: 'skip'|'profile', profileIndex }]
+ profiles: [ // Массив профилей учётных данных
+ { name: 'Профиль 1', username: '', password: '', autoSubmit: true, delay: 500 }
+ ]
},
// Сайты, на которых расширение отключено (массив hostname, например ['example.com'])
disabledSites: [],
// Замена логотипа CleverPark на иконку CAPS (icons/caps.svg)
- replaceLogo: true
+ replaceLogo: true,
+
+ // Показывать ли улучшения расширения (кнопки, логотип)
+ uiVisible: true
};
// Обновление конфигурации (изменяет существующий объект, а не создает новый)
@@ -87,7 +92,25 @@ function updateConfig(newConfig) {
// Обновляем autologin
if (newConfig.autologin) {
- Object.assign(CONFIG.autologin, newConfig.autologin);
+ const a = newConfig.autologin;
+
+ // Миграция старого формата (username/password) → profiles[0]
+ if ((a.username || a.password) && !Array.isArray(a.profiles)) {
+ CONFIG.autologin.profiles = [{
+ name: 'Профиль 1',
+ username: a.username || '',
+ password: a.password || '',
+ autoSubmit: typeof a.autoSubmit === 'boolean' ? a.autoSubmit : true,
+ delay: a.delay || 500
+ }];
+ } else if (Array.isArray(a.profiles)) {
+ CONFIG.autologin.profiles = a.profiles;
+ }
+
+ if (typeof a.enabled === 'boolean') CONFIG.autologin.enabled = a.enabled;
+ if (typeof a.profilePage1 === 'number') CONFIG.autologin.profilePage1 = a.profilePage1;
+ if (typeof a.profilePage2 === 'number') CONFIG.autologin.profilePage2 = a.profilePage2;
+ if (Array.isArray(a.subnetRules)) CONFIG.autologin.subnetRules = a.subnetRules;
}
// Обновляем disabledSites
@@ -100,6 +123,11 @@ function updateConfig(newConfig) {
CONFIG.replaceLogo = newConfig.replaceLogo;
}
+ // Обновляем uiVisible
+ if (typeof newConfig.uiVisible === 'boolean') {
+ CONFIG.uiVisible = newConfig.uiVisible;
+ }
+
// Обновляем константы портов
window.VideoIPRedirect.TARGET_PORT_VIDEO = CONFIG.ports.video;
window.VideoIPRedirect.TARGET_PORT_SCHEME = CONFIG.ports.scheme;
diff --git a/modules/mnemoschema.js b/modules/mnemoschema.js
index c9e34f1..18e0346 100644
--- a/modules/mnemoschema.js
+++ b/modules/mnemoschema.js
@@ -139,6 +139,7 @@
wrapper.className = 'scheme-buttons-wrapper';
wrapper.style.display = 'inline-block';
wrapper.style.verticalAlign = 'middle';
+ wrapper.setAttribute('data-caps-ui', 'true');
if (addVideo) wrapper.appendChild(createVideoButton(host));
if (addScheme) wrapper.appendChild(createButton(mnemoLink, host));
mnemoLink.parentNode.replaceChild(wrapper, mnemoLink);
diff --git a/modules/novnc-redirect.js b/modules/novnc-redirect.js
index e69f7be..e372471 100644
--- a/modules/novnc-redirect.js
+++ b/modules/novnc-redirect.js
@@ -104,6 +104,8 @@
'novnc-settings-btn'
);
+ button.setAttribute('data-caps-ui', 'true');
+
// Дополнительные стили для позиционирования
button.style.position = 'fixed';
button.style.top = '20px';
diff --git a/modules/video-window.js b/modules/video-window.js
index 5267ef4..0f74ab8 100644
--- a/modules/video-window.js
+++ b/modules/video-window.js
@@ -80,6 +80,7 @@
},
'video-redirect-btn'
);
+ button.setAttribute('data-caps-ui', 'true');
const p = header.querySelector('p');
if (p) {
diff --git a/options.html b/options.html
index 2e448a6..c1756f9 100644
--- a/options.html
+++ b/options.html
@@ -99,19 +99,29 @@
-
+
🖼️ Внешний вид
При совпадении хоста с подсетью — пропустить или использовать другой профиль
+ +