00:00:00
● Система работает
База авто ← Меню
0
На стоянке
0
Въехало сегодня
0
Выехало сегодня
0
Месячных
0
Шиномонтаж
104
Свободно мест
ВЪЕЗД
Ожидание камеры...
ВЫЕЗД
Ожидание камеры...
ДЕЙСТВИЯ ОХРАННИКА
СХЕМА ПАРКОВКИ  ·  На стоянке: 0  ·  Свободно: 104  ·  Месячных: 0
Клик по месту — поставить авто или открыть карточку. Номера как на плане (104 места).
Свободно Суточный Месячный

ЛЕНТА СОБЫТИЙ

Событий пока нет — ожидание камеры
/* ═══ ROI SELECTION ════════════════════════════════════════════════════════ */ var _roiState = { 'in': {active:false, x1:0.05, y1:0.25, x2:0.95, y2:1.0, drawing:false}, 'out': {active:false, x1:0.05, y1:0.25, x2:0.95, y2:1.0, drawing:false} }; var _roiDraw = null; /* {id, sx, sy} — начало рисования */ function _roiEl(id) { return document.getElementById('roi-'+id); } function _roiDraw_(id) { var r = _roiState[id], el = _roiEl(id); if (!el) return; var feed = document.getElementById('feed-'+id); if (!feed) return; var fw = feed.offsetWidth, fh = feed.offsetHeight; /* Переводим [0..1] → px с учётом текущего зума */ var s = _camState[id], vw = fw * s.scale, vh = fh * s.scale; var ox = (fw - vw)/2 + s.tx, oy = (fh - vh)/2 + s.ty; var px1 = ox + r.x1 * vw, py1 = oy + r.y1 * vh; var px2 = ox + r.x2 * vw, py2 = oy + r.y2 * vh; el.style.left = Math.min(px1,px2) + 'px'; el.style.top = Math.min(py1,py2) + 'px'; el.style.width = Math.abs(px2-px1) + 'px'; el.style.height = Math.abs(py2-py1) + 'px'; el.style.display = r.active ? 'block' : 'none'; } function camToggleRoi(id) { var r = _roiState[id]; r.active = !r.active; _roiDraw_(id); var btn = document.querySelector('#feed-'+id+' .gc-cam-roi-btn button'); if (btn) { btn.style.background = r.active ? 'rgba(88,166,255,.5)' : ''; btn.style.borderColor = r.active ? '#58a6ff' : ''; } if (!r.active) _roiSave(id); } function _roiSave(id) { var r = _roiState[id]; var camName = id === 'in' ? 'entry' : 'exit'; var payload = {}; payload[camName] = {x1:r.x1, y1:r.y1, x2:r.x2, y2:r.y2}; fetch('/api/roi', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(payload)}) .then(function(r){return r.json();}) .then(function(d){ if(d.ok) addToast && addToast('blue','▪','ROI сохранён для камеры '+(id==='in'?'въезд':'выезд')); }) .catch(function(){}); } function _initRoi() { /* Загружаем текущий ROI с сервера */ fetch('/api/roi').then(function(r){return r.json();}).then(function(d){ if (!d.ok) return; ['entry','exit'].forEach(function(cam){ var id = cam === 'entry' ? 'in' : 'out'; if (d.roi && d.roi[cam]) { var r=d.roi[cam]; _roiState[id].x1=r.x1;_roiState[id].y1=r.y1;_roiState[id].x2=r.x2;_roiState[id].y2=r.y2; } }); }).catch(function(){}); ['in','out'].forEach(function(id){ var feed = document.getElementById('feed-'+id); if (!feed) return; /* Рисование ROI мышью (только когда active) */ feed.addEventListener('mousedown', function(e){ var r = _roiState[id]; if (!r.active) return; if (e.target.closest && e.target.closest('.gc-cam-controls')) return; e.preventDefault(); e.stopPropagation(); var rect = feed.getBoundingClientRect(); var s = _camState[id]; var fw = feed.offsetWidth, fh = feed.offsetHeight; var vw = fw * s.scale, vh = fh * s.scale; var ox = (fw-vw)/2 + s.tx, oy = (fh-vh)/2 + s.ty; var mx = (e.clientX - rect.left - ox) / vw; var my = (e.clientY - rect.top - oy) / vh; r.x1 = Math.max(0, Math.min(1, mx)); r.y1 = Math.max(0, Math.min(1, my)); r.x2 = r.x1; r.y2 = r.y1; _roiDraw = {id:id, ox:ox, oy:oy, vw:vw, vh:vh, rect:rect}; _roiDraw_(id); }); document.addEventListener('mousemove', function(e){ if (!_roiDraw || _roiDraw.id !== id) return; var r = _roiState[id], d = _roiDraw; var mx = (e.clientX - d.rect.left - d.ox) / d.vw; var my = (e.clientY - d.rect.top - d.oy) / d.vh; r.x2 = Math.max(0, Math.min(1, mx)); r.y2 = Math.max(0, Math.min(1, my)); _roiDraw_(id); }); document.addEventListener('mouseup', function(e){ if (!_roiDraw || _roiDraw.id !== id) return; _roiDraw = null; _roiSave(id); }); }); } /* ════════════════════════════════════════════════════════════════════════ */