Security App
?
QR Kód s aktuální adresou
Security Console
Dveře 1
Stav:
—
Dveře 2
Stav:
—
Dveře 3
Stav:
—
Dveře 4
Stav:
—
Dveře 5
Stav:
—
Dveře 6
Stav:
—
Sejf
Stav:
—
Spustit alarm
Vypnout alarm
Upravit obsah stránky
{% extends "templates/base.html" %} {% set game_name = "Security App" %} {% block content %} <div class="container mt-4"> <h2 class="text-center mb-4">Security Console</h2> <div id="doorsGrid" class="row row-cols-2 row-cols-md-3 g-4"> {% for i in [1,2,3,4,5,6] %} <div class="col"> <div class="card door-card locked" id="door{{ i }}Card"> <div class="card-body text-center"> <h5 class="card-title">Dveře {{ i }}</h5> <p class="card-text">Stav: <span id="door{{ i }}Status">—</span></p> </div> </div> </div> {% endfor %} <div class="col"> <div class="card door-card locked" id="safeCard"> <div class="card-body text-center"> <h5 class="card-title">Sejf</h5> <p class="card-text">Stav: <span id="safeStatus">—</span></p> </div> </div> </div> </div> <div class="text-center mt-4"> <button id="triggerAlarmBtn" class="btn btn-warning me-2"> <i class="fa-solid fa-bell"></i> Spustit alarm </button> <button id="disableAlarmBtn" class="btn btn-outline-danger" disabled> <i class="fa-solid fa-bell-slash"></i> Vypnout alarm </button> </div> </div> <audio id="alarmAudio" src="alarm.mp3" loop preload="auto"></audio> <style> .door-card { transition: background-color 0.3s, border-color 0.3s; border-width: 2px; } .door-card.locked { background-color: #f2f2f2; border-color: #6c757d; color: #212529; } .door-card.unlocked { background-color: #d4edda; border-color: #28a745; color: #155724; } .door-card.alarm { background-color: #f8d7da; border-color: #dc3545; color: #721c24; animation: blink 1s step-start infinite; } @keyframes blink {50% { opacity: 0.5; }} #triggerAlarmBtn, #disableAlarmBtn { font-size: 1rem; padding: 0.5rem 1.5rem; } @media(max-width:576px) { .door-card { font-size: 0.9rem; } #triggerAlarmBtn, #disableAlarmBtn { width: 100%; margin-bottom: 0.5rem; } } </style> <script> (async () => { const fh = new ForrestHubLib(); const audio = document.getElementById('alarmAudio'); const triggerBtn = document.getElementById('triggerAlarmBtn'); const disableBtn = document.getElementById('disableAlarmBtn'); // Unlock audio context on first user click anywhere const unlockAudio = () => { audio.play().then(() => { audio.pause(); audio.currentTime = 0; }).catch(()=>{}); document.removeEventListener('click', unlockAudio); }; document.addEventListener('click', unlockAudio); const doorKeys = [1,2,3,4,5,6].map(i => ({ stateKey: `doors${i}State`, statusEl: document.getElementById(`door${i}Status`), cardEl: document.getElementById(`door${i}Card`) })); const safe = { stateKey: 'safeState', statusEl: document.getElementById('safeStatus'), cardEl: document.getElementById('safeCard') }; let alarmActive = false; const alarmDoors = new Set(); function activateAlarm() { if (!alarmActive) { alarmActive = true; disableBtn.disabled = false; audio.currentTime = 0; audio.play().catch(() => {/* may require prior user interaction */}); } } function deactivateAlarm() { alarmActive = false; disableBtn.disabled = true; audio.pause(); audio.currentTime = 0; alarmDoors.clear(); [...doorKeys, safe].forEach(item => { item.cardEl.classList.remove('alarm'); const last = item.lastState || 'locked'; item.cardEl.classList.remove('locked','unlocked'); item.cardEl.classList.add(last); }); } // Manual alarm trigger triggerBtn.addEventListener('click', () => { [...doorKeys, safe].forEach(item => { alarmDoors.add(item.stateKey); item.cardEl.classList.add('alarm'); }); activateAlarm(); }); disableBtn.addEventListener('click', () => { deactivateAlarm(); }); async function refreshStates() { for (const item of [...doorKeys, safe]) { const st = await fh.dbVarGetKey(item.stateKey) || 'locked'; item.statusEl.textContent = st.toUpperCase(); item.lastState = st; if (st === 'alarm' && !alarmDoors.has(item.stateKey)) { alarmDoors.add(item.stateKey); item.cardEl.classList.add('alarm'); activateAlarm(); } if (!alarmDoors.has(item.stateKey)) { item.cardEl.classList.remove('locked','unlocked'); item.cardEl.classList.add(st); } } } await refreshStates(); setInterval(refreshStates, 1000); })(); </script> {% endblock %}