www/console.php
2026-06-27 21:44:44 +00:00

661 lines
16 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . "/api/protecao.php";
require __DIR__ . "/api/db.php";
// =====================================================
// GUARDAR NOTA
// =====================================================
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save_note'])) {
$uid = $_POST['uid'] ?? '';
$note = trim($_POST['note'] ?? '');
$stmt = $pdo->prepare("
UPDATE contadores
SET note = ?
WHERE uid = ?
");
$stmt->execute([$note, $uid]);
header("Location: console.php");
exit;
}
// =====================================================
// REMOVER
// =====================================================
if (isset($_GET['remove'])) {
$uid = $_GET['remove'];
$pdo->prepare("
DELETE FROM contadores
WHERE uid = ?
")->execute([$uid]);
header("Location: console.php");
exit;
}
// =====================================================
// BLOQUEAR / DESBLOQUEAR
// =====================================================
if (isset($_GET['toggle'])) {
$uid = $_GET['toggle'];
$pdo->prepare("
UPDATE contadores
SET blocked = 1 - blocked
WHERE uid = ?
")->execute([$uid]);
header("Location: console.php");
exit;
}
// =====================================================
// SORT
// =====================================================
$sort = $_GET['sort'] ?? 'state';
$orderSql = ($sort === 'uid')
? "ORDER BY uid ASC"
: "ORDER BY online DESC, uid ASC";
// =====================================================
// QUERY
// =====================================================
$sql = "
SELECT
uid,
note,
entradas,
saidas,
online,
blocked,
current_broker,
rssi,
heap,
uptime,
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 ((int)$r['online'] === 1) {
$onlineCount++;
} else {
$offlineCount++;
}
}
?>
<!doctype html>
<html lang="pt" data-bs-theme="dark">
<head>
<meta charset="utf-8">
<title>MQTT Swarm Console</title>
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
<style>
body {
background: #0b0b0b;
color: #e0e0e0;
font-family: Arial, sans-serif;
}
.card {
background: #151515;
border: 1px solid #333;
}
.table {
color: #ddd;
}
.table-striped > tbody > tr:nth-of-type(odd) {
background-color: #171717;
}
.table-striped > tbody > tr:nth-of-type(even) {
background-color: #101010;
}
.table-hover tbody tr:hover {
background-color: #202020;
}
.note-input {
min-width: 220px;
background: #111;
border: 1px solid #333;
color: #fff;
}
.note-input:focus {
background: #181818;
color: #fff;
border-color: #58a6ff;
box-shadow: none;
}
.pulse-cell {
animation: pulse 1s infinite;
}
@keyframes pulse {
0% {
background-color: rgba(0,255,0,0.03);
}
50% {
background-color: rgba(0,255,0,0.16);
}
100% {
background-color: rgba(0,255,0,0.03);
}
}
.header-title {
color: #00ffcc;
text-shadow: 0 0 8px rgba(0,255,180,0.4);
}
.small-info {
font-size: 12px;
color: #999;
}
</style>
</head>
<body>
<div class="container-fluid py-4">
<h1 class="mb-4 header-title">
🌋 MQTT SWARM CONSOLE
</h1>
<!-- RESUMO -->
<div class="row mb-4 text-center">
<div class="col-md-3">
<div class="p-3 card rounded">
<h4 id="total_online"><?= $onlineCount ?></h4>
<div class="text-success fw-bold">
🟢 ONLINE
</div>
</div>
</div>
<div class="col-md-3">
<div class="p-3 card rounded">
<h4 id="total_offline"><?= $offlineCount ?></h4>
<div class="text-danger fw-bold">
🔴 OFFLINE
</div>
</div>
</div>
<div class="col-md-3">
<div class="p-3 card rounded">
<h4 id="total_entradas"><?= $sumEntradas ?></h4>
<div class="fw-bold">
💰 ENTRADAS
</div>
</div>
</div>
<div class="col-md-3">
<div class="p-3 card rounded">
<h4 id="total_saidas"><?= $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>UID</th>
<th>NOTA</th>
<th>ENTRADAS</th>
<th>SAÍDAS</th>
<th>ESTADO</th>
<th>BROKER</th>
<th>RSSI</th>
<th>HEAP</th>
<th>UPTIME</th>
<th>ÚLTIMO</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>';
}
if (($r['current_broker'] ?? '') === 'recovery') {
$broker =
'<span class="badge bg-warning text-dark">
RECOVERY
</span>';
} else {
$broker =
'<span class="badge bg-primary">
PRIMARY
</span>';
}
$ago = (int)$r['ago'];
$lastSeen = ($ago < 60)
? "{$ago}s"
: round($ago / 60) . "m";
$pulseClass =
($ago <= 2 && $online)
? "pulse-cell"
: "";
?>
<tr class="<?= $pulseClass ?>">
<td class="fw-bold">
<?= htmlspecialchars($r['uid']) ?>
</td>
<td>
<form method="post">
<input type="hidden"
name="uid"
value="<?= htmlspecialchars($r['uid']) ?>">
<div class="d-flex gap-2">
<input type="text"
name="note"
value="<?= htmlspecialchars($r['note'] ?? '') ?>"
class="form-control form-control-sm note-input"
placeholder="nota...">
<button type="submit"
name="save_note"
class="btn btn-sm btn-primary">
Save
</button>
</div>
</form>
</td>
<td><?= (int)$r['entradas'] ?></td>
<td><?= (int)$r['saidas'] ?></td>
<td><?= $state ?></td>
<td><?= $broker ?></td>
<td>
<?= isset($r['rssi'])
? $r['rssi'] . " dBm"
: "-" ?>
</td>
<td>
<?= isset($r['heap'])
? number_format((int)$r['heap'])
: "-" ?>
</td>
<td>
<?php
if (isset($r['uptime'])) {
$up = (int)$r['uptime'];
echo gmdate("H:i:s", $up);
} else {
echo "-";
}
?>
</td>
<td><?= $lastSeen ?></td>
<td>
<div class="d-flex gap-2">
<a href="?toggle=<?= urlencode($r['uid']) ?>"
class="btn btn-sm <?= $blocked ? 'btn-success' : 'btn-danger' ?>">
<?= $blocked
? 'Desbloquear'
: 'Bloquear' ?>
</a>
<a href="?remove=<?= urlencode($r['uid']) ?>"
class="btn btn-sm btn-warning"
onclick="return confirm('Remover device?')">
Remover
</a>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<script>
function esc(s) {
return String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;')
.replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
async function refreshTable() {
// Não actualizar se o utilizador estiver a editar uma nota
if (document.activeElement && document.activeElement.classList.contains('note-input')) {
return;
}
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) {
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>';
}
let broker = "";
if (r.current_broker === 'recovery') {
broker =
'<span class="badge bg-warning text-dark">RECOVERY</span>';
} else {
broker =
'<span class="badge bg-primary">PRIMARY</span>';
}
let lastSeen =
(r.ago < 60)
? `${r.ago}s`
: `${Math.round(r.ago / 60)}m`;
let pulseClass =
(r.ago <= 2 && r.online == 1)
? "pulse-cell"
: "";
let uptime = "-";
if (r.uptime && !isNaN(r.uptime)) {
let sec = parseInt(r.uptime);
let h =
String(Math.floor(sec / 3600))
.padStart(2, '0');
let m =
String(Math.floor((sec % 3600) / 60))
.padStart(2, '0');
let s =
String(sec % 60)
.padStart(2, '0');
uptime = `${h}:${m}:${s}`;
}
html += `
<tr class="${esc(pulseClass)}">
<td class="fw-bold">
${esc(r.uid)}
</td>
<td>
<form method="post">
<input type="hidden"
name="uid"
value="${esc(r.uid)}">
<div class="d-flex gap-2">
<input type="text"
name="note"
value="${esc(r.note)}"
class="form-control form-control-sm note-input"
placeholder="nota...">
<button type="submit"
name="save_note"
class="btn btn-sm btn-primary">
Save
</button>
</div>
</form>
</td>
<td>${esc(r.entradas)}</td>
<td>${esc(r.saidas)}</td>
<td>${state}</td>
<td>${broker}</td>
<td>${r.rssi != null ? esc(r.rssi) + ' dBm' : '-'}</td>
<td>${r.heap != null ? Number(r.heap).toLocaleString() : '-'}</td>
<td>${uptime}</td>
<td>${esc(lastSeen)}</td>
<td>
<div class="d-flex gap-2">
<a href="?toggle=${esc(r.uid)}"
class="btn btn-sm ${r.blocked ? 'btn-success' : 'btn-danger'}">
${r.blocked ? 'Desbloquear' : 'Bloquear'}
</a>
<a href="?remove=${esc(r.uid)}"
class="btn btn-sm btn-warning"
onclick="return confirm('Remover device?')">
Remover
</a>
</div>
</td>
</tr>
`;
}
document.getElementById("tabela_corpo").innerHTML = html;
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>