215 lines
6.7 KiB
JavaScript
215 lines
6.7 KiB
JavaScript
// Verstak Bridge — Popup Script
|
||
// Shows connection status, queue count, recent events, and settings.
|
||
|
||
const STORAGE_KEY = 'verstak_queue';
|
||
const BRIDGE_KEY = 'verstak_bridge_config';
|
||
const RECENT_KEY = 'verstak_recent';
|
||
const DEFAULT_PORT = 9786;
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
updateUI();
|
||
restoreToggleState();
|
||
restoreSettingsPanel();
|
||
|
||
document.getElementById('sync-btn').addEventListener('click', forceSync);
|
||
document.getElementById('track-toggle').addEventListener('change', onToggle);
|
||
document.getElementById('settings-btn').addEventListener('click', toggleSettings);
|
||
document.getElementById('save-port-btn').addEventListener('click', savePort);
|
||
document.getElementById('test-port-btn').addEventListener('click', testPort);
|
||
});
|
||
|
||
function updateUI() {
|
||
chrome.storage.local.get([STORAGE_KEY, BRIDGE_KEY, RECENT_KEY], (data) => {
|
||
const queue = data[STORAGE_KEY] || [];
|
||
const config = data[BRIDGE_KEY] || {};
|
||
const recent = data[RECENT_KEY] || [];
|
||
|
||
// Queue count
|
||
document.getElementById('queue-count').textContent = queue.length;
|
||
|
||
// Bridge status
|
||
const statusEl = document.getElementById('bridge-status');
|
||
const portEl = document.getElementById('bridge-port');
|
||
const dotEl = document.getElementById('status-dot');
|
||
|
||
if (config.bridgeReachable) {
|
||
statusEl.textContent = 'Доступен';
|
||
statusEl.className = 'value online';
|
||
dotEl.className = 'dot online';
|
||
portEl.textContent = '127.0.0.1:' + (config.port || DEFAULT_PORT);
|
||
} else {
|
||
statusEl.textContent = 'Недоступен';
|
||
statusEl.className = 'value offline';
|
||
dotEl.className = 'dot offline';
|
||
portEl.textContent = config.lastPing ? '—' : 'проверка...';
|
||
}
|
||
|
||
// Update settings panel port field
|
||
const portInput = document.getElementById('port-input');
|
||
if (portInput && !portInput.dataset.userEdited) {
|
||
portInput.value = config.port || DEFAULT_PORT;
|
||
}
|
||
|
||
// Recent events
|
||
renderRecent(recent, config.bridgeReachable);
|
||
});
|
||
}
|
||
|
||
function renderRecent(events, reachable) {
|
||
const list = document.getElementById('event-list');
|
||
list.innerHTML = '';
|
||
|
||
if (!events || events.length === 0) {
|
||
list.innerHTML = '<p class="empty">Нет событий</p>';
|
||
return;
|
||
}
|
||
|
||
const lastFive = events.slice(-5).reverse();
|
||
for (const ev of lastFive) {
|
||
const item = document.createElement('div');
|
||
item.className = 'event-item';
|
||
item.innerHTML = `
|
||
<div class="domain">${escapeHtml(ev.domain || '?')}</div>
|
||
<div class="title">${escapeHtml(truncate(ev.title || ev.url, 60))}</div>
|
||
<div class="duration">${ev.active_seconds || 0}с${reachable ? '' : ' (ожидает)'}</div>
|
||
`;
|
||
list.appendChild(item);
|
||
}
|
||
}
|
||
|
||
function forceSync() {
|
||
const btn = document.getElementById('sync-btn');
|
||
btn.disabled = true;
|
||
btn.textContent = '⏳ Отправка...';
|
||
|
||
chrome.runtime.sendMessage({ type: 'FORCE_FLUSH' }, () => {
|
||
setTimeout(() => {
|
||
updateUI();
|
||
btn.disabled = false;
|
||
btn.textContent = '🔄 Отправить сейчас';
|
||
}, 1000);
|
||
});
|
||
}
|
||
|
||
function onToggle(e) {
|
||
const enabled = e.target.checked;
|
||
chrome.storage.local.set({ 'verstak_tracking_enabled': enabled });
|
||
chrome.runtime.sendMessage({ type: 'SET_TRACKING', enabled });
|
||
}
|
||
|
||
function restoreToggleState() {
|
||
chrome.storage.local.get('verstak_tracking_enabled', (data) => {
|
||
const enabled = data.verstak_tracking_enabled !== false;
|
||
document.getElementById('track-toggle').checked = enabled;
|
||
});
|
||
}
|
||
|
||
// --- Settings panel ---
|
||
|
||
function toggleSettings() {
|
||
const panel = document.getElementById('settings-panel');
|
||
const main = document.getElementById('main-panel');
|
||
const btn = document.getElementById('settings-btn');
|
||
|
||
if (panel.style.display === 'none' || !panel.style.display) {
|
||
panel.style.display = 'block';
|
||
main.style.display = 'none';
|
||
btn.textContent = '← Назад';
|
||
} else {
|
||
panel.style.display = 'none';
|
||
main.style.display = 'block';
|
||
btn.textContent = '⚙';
|
||
}
|
||
}
|
||
|
||
function restoreSettingsPanel() {
|
||
chrome.storage.local.get(BRIDGE_KEY, (data) => {
|
||
const config = data[BRIDGE_KEY] || {};
|
||
const portInput = document.getElementById('port-input');
|
||
if (portInput) {
|
||
portInput.value = config.port || DEFAULT_PORT;
|
||
}
|
||
});
|
||
}
|
||
|
||
function savePort() {
|
||
const portInput = document.getElementById('port-input');
|
||
const port = parseInt(portInput.value, 10);
|
||
const statusEl = document.getElementById('port-status');
|
||
|
||
if (isNaN(port) || port < 1024 || port > 65535) {
|
||
statusEl.textContent = 'Порт: 1024–65535';
|
||
statusEl.className = 'port-status error';
|
||
return;
|
||
}
|
||
|
||
chrome.storage.local.get(BRIDGE_KEY, (data) => {
|
||
const config = data[BRIDGE_KEY] || {};
|
||
config.port = port;
|
||
chrome.storage.local.set({ [BRIDGE_KEY]: config }, () => {
|
||
statusEl.textContent = 'Сохранено';
|
||
statusEl.className = 'port-status ok';
|
||
// Trigger immediate ping with new port
|
||
chrome.runtime.sendMessage({ type: 'FORCE_FLUSH' });
|
||
setTimeout(() => { updateUI(); statusEl.textContent = ''; }, 1500);
|
||
});
|
||
});
|
||
}
|
||
|
||
function testPort() {
|
||
const portInput = document.getElementById('port-input');
|
||
const port = parseInt(portInput.value, 10);
|
||
const statusEl = document.getElementById('port-status');
|
||
|
||
if (isNaN(port) || port < 1024 || port > 65535) {
|
||
statusEl.textContent = 'Порт: 1024–65535';
|
||
statusEl.className = 'port-status error';
|
||
return;
|
||
}
|
||
|
||
statusEl.textContent = 'Проверка...';
|
||
statusEl.className = 'port-status';
|
||
|
||
fetch(`http://127.0.0.1:${port}/api/ping`, { signal: AbortSignal.timeout(3000) })
|
||
.then((res) => {
|
||
if (res.ok) {
|
||
statusEl.textContent = '✓ Сервер отвечает';
|
||
statusEl.className = 'port-status ok';
|
||
} else {
|
||
statusEl.textContent = `✗ Ошибка ${res.status}`;
|
||
statusEl.className = 'port-status error';
|
||
}
|
||
})
|
||
.catch((err) => {
|
||
if (err.name === 'TimeoutError' || err.name === 'AbortError') {
|
||
statusEl.textContent = '✗ Нет ответа (таймаут). Возможно, порт занят другим приложением или Verstak не запущен';
|
||
} else {
|
||
statusEl.textContent = '✗ Сервер недоступен';
|
||
}
|
||
statusEl.className = 'port-status error';
|
||
});
|
||
}
|
||
|
||
// Listen for updates from background
|
||
chrome.runtime.onMessage.addListener((msg) => {
|
||
if (msg.type === 'UI_UPDATE') {
|
||
updateUI();
|
||
}
|
||
});
|
||
|
||
// --- Helpers ---
|
||
|
||
function truncate(s, n) {
|
||
if (!s) return '';
|
||
return s.length > n ? s.substring(0, n) + '...' : s;
|
||
}
|
||
|
||
function escapeHtml(s) {
|
||
if (!s) return '';
|
||
return s
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"');
|
||
}
|