263 lines
7.1 KiB
PHP
263 lines
7.1 KiB
PHP
|
|
<?php
|
||
|
|
declare(strict_types=1);
|
||
|
|
require __DIR__ . "/api/db.php";
|
||
|
|
|
||
|
|
// ---------- SORT ----------
|
||
|
|
$sort = $_GET['sort'] ?? 'state';
|
||
|
|
$orderSql = ($sort === 'uid')
|
||
|
|
? "ORDER BY uid ASC"
|
||
|
|
: "ORDER BY online DESC, uid ASC";
|
||
|
|
|
||
|
|
// ---------- OFFLINE AUTOMÁTICO (20s) ----------
|
||
|
|
$pdo->query("
|
||
|
|
UPDATE contadores
|
||
|
|
SET online = 0
|
||
|
|
WHERE TIMESTAMPDIFF(SECOND, last_seen, NOW()) > 20
|
||
|
|
");
|
||
|
|
|
||
|
|
// ---------- BLOQUEIO ----------
|
||
|
|
if (isset($_GET['toggle'])) {
|
||
|
|
$uid = $_GET['toggle'];
|
||
|
|
$pdo->prepare("UPDATE contadores SET blocked = 1 - blocked WHERE uid = ?")
|
||
|
|
->execute([$uid]);
|
||
|
|
header("Location: console.php");
|
||
|
|
exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------- PRIMEIRA QUERY (para totals) ----------
|
||
|
|
$sql = "
|
||
|
|
SELECT uid, entradas, saidas, online, blocked,
|
||
|
|
last_seen,
|
||
|
|
TIMESTAMPDIFF(SECOND, last_seen, NOW()) AS ago
|
||
|
|
FROM contadores
|
||
|
|
$orderSql
|
||
|
|
";
|
||
|
|
$stmt = $pdo->query($sql);
|
||
|
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||
|
|
|
||
|
|
// Totais
|
||
|
|
$sumEntradas = 0;
|
||
|
|
$sumSaidas = 0;
|
||
|
|
$onlineCount = 0;
|
||
|
|
$offlineCount = 0;
|
||
|
|
|
||
|
|
foreach ($rows as $r) {
|
||
|
|
$sumEntradas += (int)$r['entradas'];
|
||
|
|
$sumSaidas += (int)$r['saidas'];
|
||
|
|
if ($r['online'] == 1) $onlineCount++;
|
||
|
|
else $offlineCount++;
|
||
|
|
}
|
||
|
|
?>
|
||
|
|
<!doctype html>
|
||
|
|
<html lang="pt" data-bs-theme="dark">
|
||
|
|
<head>
|
||
|
|
<meta charset="utf-8">
|
||
|
|
<title>Painel de Contadores</title>
|
||
|
|
<link rel="stylesheet"
|
||
|
|
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
|
||
|
|
|
||
|
|
<style>
|
||
|
|
body {
|
||
|
|
background-color: #0f0f0f;
|
||
|
|
color: #e0e0e0;
|
||
|
|
}
|
||
|
|
.card {
|
||
|
|
background-color: #1a1a1a;
|
||
|
|
border: 1px solid #333;
|
||
|
|
}
|
||
|
|
.table-striped > tbody > tr:nth-of-type(odd) {
|
||
|
|
background-color: #1c1c1c;
|
||
|
|
}
|
||
|
|
.table-striped > tbody > tr:nth-of-type(even) {
|
||
|
|
background-color: #151515;
|
||
|
|
}
|
||
|
|
.table thead {
|
||
|
|
background-color: #222;
|
||
|
|
}
|
||
|
|
h1 {
|
||
|
|
color: #f0f0f0;
|
||
|
|
}
|
||
|
|
a {
|
||
|
|
color: #58a6ff;
|
||
|
|
}
|
||
|
|
a:hover {
|
||
|
|
color: #8cc5ff;
|
||
|
|
}
|
||
|
|
.btn-danger {
|
||
|
|
background-color: #a32424;
|
||
|
|
border-color: #6b0000;
|
||
|
|
}
|
||
|
|
.btn-success {
|
||
|
|
background-color: #1f6f33;
|
||
|
|
border-color: #0f4d1f;
|
||
|
|
}
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
|
||
|
|
<body>
|
||
|
|
|
||
|
|
<div class="container py-4">
|
||
|
|
|
||
|
|
<h1 class="mb-4">Painel de Contadores</h1>
|
||
|
|
|
||
|
|
<!-- RESUMO -->
|
||
|
|
<div class="row mb-4 text-center">
|
||
|
|
<div class="col-md-3">
|
||
|
|
<div class="p-3 card rounded">
|
||
|
|
<h4><?= $onlineCount ?></h4>
|
||
|
|
<div class="text-success fw-bold">Online</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="col-md-3">
|
||
|
|
<div class="p-3 card rounded">
|
||
|
|
<h4><?= $offlineCount ?></h4>
|
||
|
|
<div class="text-danger fw-bold">Offline</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="col-md-3">
|
||
|
|
<div class="p-3 card rounded">
|
||
|
|
<h4><?= $sumEntradas ?></h4>
|
||
|
|
<div class="fw-bold">Entradas</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="col-md-3">
|
||
|
|
<div class="p-3 card rounded">
|
||
|
|
<h4><?= $sumSaidas ?></h4>
|
||
|
|
<div class="fw-bold">Saídas</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- TABELA -->
|
||
|
|
<div class="card shadow-sm">
|
||
|
|
<div class="card-body">
|
||
|
|
|
||
|
|
<table class="table table-striped table-hover align-middle">
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th><a href="?sort=uid">UID</a></th>
|
||
|
|
<th>Entradas</th>
|
||
|
|
<th>Saídas</th>
|
||
|
|
<th><a href="?sort=state">Estado</a></th>
|
||
|
|
<th>Último Sinal</th>
|
||
|
|
<th>Ações</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody id="tabela_corpo">
|
||
|
|
|
||
|
|
<?php foreach ($rows as $r): ?>
|
||
|
|
|
||
|
|
<?php
|
||
|
|
$online = (int)$r['online'] === 1;
|
||
|
|
$blocked = (int)$r['blocked'] === 1;
|
||
|
|
|
||
|
|
if ($blocked) {
|
||
|
|
$state = '<span class="badge bg-danger">Bloqueado</span>';
|
||
|
|
} elseif ($online) {
|
||
|
|
$state = '<span class="badge bg-success">Online</span>';
|
||
|
|
} else {
|
||
|
|
$state = '<span class="badge bg-secondary">Offline</span>';
|
||
|
|
}
|
||
|
|
|
||
|
|
$ago = (int)$r['ago'];
|
||
|
|
$lastSeen = ($ago < 60)
|
||
|
|
? "{$ago}s"
|
||
|
|
: round($ago / 60) . "m";
|
||
|
|
?>
|
||
|
|
|
||
|
|
<tr>
|
||
|
|
<td class="fw-bold"><?= htmlspecialchars($r['uid']) ?></td>
|
||
|
|
<td><?= $r['entradas'] ?></td>
|
||
|
|
<td><?= $r['saidas'] ?></td>
|
||
|
|
<td><?= $state ?></td>
|
||
|
|
<td><?= $lastSeen ?></td>
|
||
|
|
<td>
|
||
|
|
<a href="?toggle=<?= urlencode($r['uid']) ?>"
|
||
|
|
class="btn btn-sm <?= $blocked ? 'btn-success' : 'btn-danger' ?>">
|
||
|
|
<?= $blocked ? 'Desbloquear' : 'Bloquear' ?>
|
||
|
|
</a>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
|
||
|
|
<?php endforeach; ?>
|
||
|
|
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- ------------ AUTO-REFRESH AJAX ---------------- -->
|
||
|
|
<script>
|
||
|
|
async function refreshTable() {
|
||
|
|
try {
|
||
|
|
const res = await fetch('/api/contadores_json.php?x=' + Date.now());
|
||
|
|
|
||
|
|
const data = await res.json();
|
||
|
|
|
||
|
|
let html = "";
|
||
|
|
|
||
|
|
for (const r of data.rows) {
|
||
|
|
|
||
|
|
// Estado
|
||
|
|
// Estado baseado APENAS no valor online vindo da DB
|
||
|
|
let state = "";
|
||
|
|
|
||
|
|
if (r.blocked == 1) {
|
||
|
|
state = '<span class="badge bg-danger">Bloqueado</span>';
|
||
|
|
}
|
||
|
|
else if (r.online == 1) {
|
||
|
|
state = '<span class="badge bg-success">Online</span>';
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
state = '<span class="badge bg-secondary">Offline</span>';
|
||
|
|
}
|
||
|
|
|
||
|
|
// Tempo
|
||
|
|
let lastSeen = (r.ago < 60) ? `${r.ago}s` : `${Math.round(r.ago/60)}m`;
|
||
|
|
|
||
|
|
// Animação pulse quando r.ago <= 2
|
||
|
|
let pulseClass = (r.ago <= 2 && r.online == 1) ? "pulse-cell" : "";
|
||
|
|
|
||
|
|
html += `
|
||
|
|
<tr class="${pulseClass}">
|
||
|
|
<td class="fw-bold">${r.uid}</td>
|
||
|
|
<td>${r.entradas}</td>
|
||
|
|
<td>${r.saidas}</td>
|
||
|
|
<td>${state}</td>
|
||
|
|
<td>${lastSeen}</td>
|
||
|
|
<td>
|
||
|
|
<a href="?toggle=${r.uid}"
|
||
|
|
class="btn btn-sm ${r.blocked ? 'btn-success' : 'btn-danger'}">
|
||
|
|
${r.blocked ? 'Desbloquear' : 'Bloquear'}
|
||
|
|
</a>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
`;
|
||
|
|
}
|
||
|
|
|
||
|
|
document.getElementById("tabela_corpo").innerHTML = html;
|
||
|
|
|
||
|
|
// Atualizar totais
|
||
|
|
document.getElementById("total_online").innerText = data.online;
|
||
|
|
document.getElementById("total_offline").innerText = data.offline;
|
||
|
|
document.getElementById("total_entradas").innerText = data.entradas;
|
||
|
|
document.getElementById("total_saidas").innerText = data.saidas;
|
||
|
|
|
||
|
|
} catch (err) {
|
||
|
|
console.error("Erro AJAX:", err);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
setInterval(refreshTable, 2000);
|
||
|
|
refreshTable();
|
||
|
|
</script>
|
||
|
|
|
||
|
|
|
||
|
|
</body>
|
||
|
|
</html>
|
||
|
|
|
||
|
|
|