let currentTab = 'dashboard'; document.querySelectorAll('.tab-btn').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); btn.classList.add('active'); document.getElementById(btn.dataset.tab).classList.add('active'); currentTab = btn.dataset.tab; if (btn.dataset.tab === 'dashboard') loadDashboard(); if (btn.dataset.tab === 'alerts') loadAlerts(); if (btn.dataset.tab === 'ams') loadAms(); if (btn.dataset.tab === 'edges') loadEdges(); if (btn.dataset.tab === 'simulate') loadSimulate(); }); }); async function api(url) { const r = await fetch(url); if (!r.ok) throw new Error(r.status); return r.json(); } async function loadStats() { try { const stats = await api('/api/stats'); document.getElementById('statAlerts').textContent = stats.alerts_today; document.getElementById('statOnline').textContent = `${stats.online_ams}/${stats.total_ams}`; document.getElementById('statEdges').textContent = `${stats.online_edges}/${stats.total_edges}`; } catch (e) { console.error('Stats error:', e); } } async function loadDashboard() { await loadStats(); await loadAmsMap(); await loadRecentAlerts(); await loadStatsChart(); } async function loadAmsMap() { try { const amsList = await api('/api/ams'); const container = document.getElementById('amsMap'); if (!amsList.length) { container.innerHTML = '

Нет АМС. Добавьте на вкладке АМС.

'; return; } container.style.position = 'relative'; container.innerHTML = amsList.map((a, i) => { const x = 10 + (i % 3) * 35; const y = 20 + Math.floor(i / 3) * 25; return `
`; }).join(''); } catch (e) { console.error('Map error:', e); } } async function loadRecentAlerts() { try { const alerts = await api('/api/alerts'); const container = document.getElementById('recentAlerts'); if (!alerts.length) { container.innerHTML = '

Тревог нет

'; return; } container.innerHTML = alerts.slice(0, 5).map(a => `
${a.amc_name || 'АМС #' + a.amc_id} ${new Date(a.created_at).toLocaleString('kk-KZ')}
📹 ${a.camera_role} • 🎯 ${a.event_type}
`).join(''); } catch (e) { console.error('Alerts error:', e); } } async function loadStatsChart() { try { const stats = await api('/api/stats'); document.getElementById('statsChart').innerHTML = `
Всего АМС${stats.total_ams}
АМС онлайн${stats.online_ams}
Edge устройств${stats.total_edges}
Edge онлайн${stats.online_edges}
Событий сегодня${stats.events_today}
Тревог сегодня${stats.alerts_today}
Всего тревог${stats.total_alerts}
`; } catch (e) { console.error('Stats error:', e); } } async function loadAlerts() { try { const alerts = await api('/api/alerts'); const statusFilter = document.getElementById('alertStatusFilter').value; const search = document.getElementById('alertSearch').value.toLowerCase(); let filtered = alerts; if (statusFilter !== 'all') filtered = alerts.filter(a => a.status === statusFilter); if (search) filtered = alerts.filter(a => (a.amc_name || '').toLowerCase().includes(search)); const container = document.getElementById('alertsTable'); if (!filtered.length) { container.innerHTML = '

Нет данных

'; return; } container.innerHTML = `${filtered.map(a => ` `).join('')}
IDАМСКамераТипВремяСтатусДействия
${a.id} ${a.amc_name || 'АМС #' + a.amc_id} ${a.camera_role} ${a.event_type} ${new Date(a.created_at).toLocaleString('kk-KZ')} ${a.status} ${a.status === 'new' ? `` : ''} ${a.status !== 'closed' ? `` : ''}
`; } catch (e) { console.error('Alerts error:', e); } } document.getElementById('alertStatusFilter').addEventListener('change', loadAlerts); document.getElementById('alertSearch').addEventListener('input', loadAlerts); async function updateAlert(id, status) { try { await fetch(`/api/alerts/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status }) }); loadAlerts(); } catch (e) { console.error('Update error:', e); } } async function loadAms() { try { const amsList = await api('/api/ams'); const search = document.getElementById('amsSearch').value.toLowerCase(); let filtered = amsList; if (search) filtered = amsList.filter(a => (a.name || '').toLowerCase().includes(search)); const container = document.getElementById('amsTable'); if (!filtered.length) { container.innerHTML = '

Нет АМС. Добавьте первую!

'; return; } container.innerHTML = `${filtered.map(a => ` `).join('')}
IDНазваниеАдресРегионСтатусНаряд
${a.id} ${a.name} ${a.address || '-'} ${a.region || '-'} ${a.status} ${a.has_permit ? '✅' : '❌'}
`; } catch (e) { console.error('AMS error:', e); } } document.getElementById('amsSearch').addEventListener('input', loadAms); function showAddAmsModal() { document.getElementById('addAmsModal').classList.add('active'); } function closeAddAmsModal() { document.getElementById('addAmsModal').classList.remove('active'); document.getElementById('addAmsForm').reset(); } document.getElementById('addAmsForm').addEventListener('submit', async (e) => { e.preventDefault(); const form = e.target; const ams = { name: form.name.value, address: form.address.value, region: form.region.value, lat: form.lat.value, lon: form.lon.value, has_permit: false }; try { await fetch('/api/ams', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(ams) }); closeAddAmsModal(); loadAms(); loadAmsMap(); } catch (e) { console.error('Add AMS error:', e); } }); async function loadEdges() { try { const edges = await api('/api/edges'); const container = document.getElementById('edgesTable'); if (!edges.length) { container.innerHTML = '

Edge-устройств нет. Запустите симуляцию.

'; return; } container.innerHTML = `${edges.map(e => ` `).join('')}
Edge IDАМССтатусПоследний heartbeat
${e.edge_id} ${e.amc_name || 'АМС #' + e.amc_id} ${e.status} ${new Date(e.last_heartbeat).toLocaleString('kk-KZ')}
`; } catch (e) { console.error('Edges error:', e); } } async function loadSimulate() { try { const amsList = await api('/api/ams'); const select = document.getElementById('simAms'); if (!amsList.length) { document.getElementById('simulateForm').innerHTML = '

Сначала добавьте АМС на вкладке "АМС"

'; return; } select.innerHTML = amsList.map(a => ``).join(''); } catch (e) { console.error('Simulate load error:', e); } } document.getElementById('simulateForm').addEventListener('submit', async (e) => { e.preventDefault(); const resultBox = document.getElementById('simulateResult'); try { const amsSelect = document.getElementById('simAms'); const amsName = amsSelect.options[amsSelect.selectedIndex].dataset.name; const amsId = parseInt(amsSelect.value); const event = { event_id: 'evt-' + Date.now(), amc_id: amsId, amc_name: amsName, camera_id: document.getElementById('simCamera').value, camera_role: document.getElementById('simCamera').value === 'ladder' ? 'ladder' : 'perimeter', event_type: document.getElementById('simEventType').value, confidence: 0.75 + Math.random() * 0.2, detected_at: new Date().toISOString(), snapshot_url: null, clip_url: null }; const hasPermit = document.getElementById('simPermit').value === 'true'; if (hasPermit) { const data = await fetch('/api/ams').then(r => r.json()); const ams = data.find(a => a.id === amsId); if (ams) { ams.has_permit = true; await fetch('/api/ams', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: ams }); } } const r = await fetch('/api/events', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(event) }); const result = await r.json(); resultBox.className = 'result-box success'; resultBox.innerHTML = ` ✅ Событие отправлено!
${result.alert_created ? '🚨 ТРЕВОГА создана! (нет наряда-допуска)' : '✅ Наряд-допуск есть, тревоги нет'} `; } catch (e) { resultBox.className = 'result-box error'; resultBox.innerHTML = `❌ Ошибка: ${e.message}`; } }); setInterval(() => { if (currentTab === 'dashboard') loadDashboard(); if (currentTab === 'alerts') loadAlerts(); if (currentTab === 'ams') loadAms(); if (currentTab === 'edges') loadEdges(); }, 10000); loadDashboard();