ams-ai-monitoring-tehnichesk/script.js

304 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 = '<p style="text-align:center;color:white;">Нет АМС. Добавьте на вкладке АМС.</p>';
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 `<div class="ams-point ${a.status === 'online' ? 'online' : 'offline'}"
style="left:${x}%;top:${y}%"
title="${a.name} (${a.status})"></div>`;
}).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 = '<p style="text-align:center;color:var(--gray-500);">Тревог нет</p>';
return;
}
container.innerHTML = alerts.slice(0, 5).map(a => `
<div class="alert-item ${a.status}">
<div class="alert-header">
<span class="alert-title">${a.amc_name || 'АМС #' + a.amc_id}</span>
<span class="alert-time">${new Date(a.created_at).toLocaleString('kk-KZ')}</span>
</div>
<div class="alert-details">📹 ${a.camera_role} • 🎯 ${a.event_type}</div>
</div>
`).join('');
} catch (e) { console.error('Alerts error:', e); }
}
async function loadStatsChart() {
try {
const stats = await api('/api/stats');
document.getElementById('statsChart').innerHTML = `
<div class="stat-row"><span class="label">Всего АМС</span><span class="value">${stats.total_ams}</span></div>
<div class="stat-row"><span class="label">АМС онлайн</span><span class="value">${stats.online_ams}</span></div>
<div class="stat-row"><span class="label">Edge устройств</span><span class="value">${stats.total_edges}</span></div>
<div class="stat-row"><span class="label">Edge онлайн</span><span class="value">${stats.online_edges}</span></div>
<div class="stat-row"><span class="label">Событий сегодня</span><span class="value">${stats.events_today}</span></div>
<div class="stat-row"><span class="label">Тревог сегодня</span><span class="value">${stats.alerts_today}</span></div>
<div class="stat-row"><span class="label">Всего тревог</span><span class="value">${stats.total_alerts}</span></div>
`;
} 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 = '<p style="padding:20px;text-align:center;color:var(--gray-500);">Нет данных</p>';
return;
}
container.innerHTML = `<table>
<thead>
<tr><th>ID</th><th>АМС</th><th>Камера</th><th>Тип</th><th>Время</th><th>Статус</th><th>Действия</th></tr>
</thead>
<tbody>${filtered.map(a => `
<tr>
<td>${a.id}</td>
<td>${a.amc_name || 'АМС #' + a.amc_id}</td>
<td>${a.camera_role}</td>
<td>${a.event_type}</td>
<td>${new Date(a.created_at).toLocaleString('kk-KZ')}</td>
<td><span class="status-badge status-${a.status}">${a.status}</span></td>
<td>
${a.status === 'new' ? `<button onclick="updateAlert(${a.id},'confirmed')" class="btn-primary" style="padding:6px 12px;font-size:12px;">✓</button>` : ''}
${a.status !== 'closed' ? `<button onclick="updateAlert(${a.id},'closed')" class="btn-secondary" style="padding:6px 12px;font-size:12px;">✕</button>` : ''}
</td>
</tr>
`).join('')}</tbody>
</table>`;
} 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 = '<p style="padding:20px;text-align:center;color:var(--gray-500);">Нет АМС. Добавьте первую!</p>';
return;
}
container.innerHTML = `<table>
<thead>
<tr><th>ID</th><th>Название</th><th>Адрес</th><th>Регион</th><th>Статус</th><th>Наряд</th></tr>
</thead>
<tbody>${filtered.map(a => `
<tr>
<td>${a.id}</td>
<td><strong>${a.name}</strong></td>
<td>${a.address || '-'}</td>
<td>${a.region || '-'}</td>
<td><span class="status-badge status-${a.status}">${a.status}</span></td>
<td>${a.has_permit ? '✅' : '❌'}</td>
</tr>
`).join('')}</tbody>
</table>`;
} 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 = '<p style="padding:20px;text-align:center;color:var(--gray-500);">Edge-устройств нет. Запустите симуляцию.</p>';
return;
}
container.innerHTML = `<table>
<thead>
<tr><th>Edge ID</th><th>АМС</th><th>Статус</th><th>Последний heartbeat</th></tr>
</thead>
<tbody>${edges.map(e => `
<tr>
<td><code>${e.edge_id}</code></td>
<td>${e.amc_name || 'АМС #' + e.amc_id}</td>
<td><span class="status-badge status-${e.status}">${e.status}</span></td>
<td>${new Date(e.last_heartbeat).toLocaleString('kk-KZ')}</td>
</tr>
`).join('')}</tbody>
</table>`;
} 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 = '<p>Сначала добавьте АМС на вкладке "АМС"</p>';
return;
}
select.innerHTML = amsList.map(a => `<option value="${a.id}" data-name="${a.name}">${a.name}</option>`).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 = `
<strong>✅ Событие отправлено!</strong><br>
${result.alert_created ? '🚨 <strong>ТРЕВОГА создана!</strong> (нет наряда-допуска)' : '✅ Наряд-допуск есть, тревоги нет'}
`;
} catch (e) {
resultBox.className = 'result-box error';
resultBox.innerHTML = `<strong>❌ Ошибка:</strong> ${e.message}`;
}
});
setInterval(() => {
if (currentTab === 'dashboard') loadDashboard();
if (currentTab === 'alerts') loadAlerts();
if (currentTab === 'ams') loadAms();
if (currentTab === 'edges') loadEdges();
}, 10000);
loadDashboard();