diff --git a/api/contadores_json.php b/api/contadores_json.php index 75c3bec..343d73c 100644 --- a/api/contadores_json.php +++ b/api/contadores_json.php @@ -3,7 +3,8 @@ declare(strict_types=1); require __DIR__ . "/db.php"; $rows = $pdo->query(" - SELECT uid, entradas, saidas, online, blocked, + SELECT uid, note, entradas, saidas, online, blocked, + current_broker, rssi, heap, uptime, TIMESTAMPDIFF(SECOND, last_seen, NOW()) AS ago FROM contadores ORDER BY online DESC, uid ASC diff --git a/api/listener_recovery.php b/api/listener_recovery.php index 3c414f5..511f252 100644 --- a/api/listener_recovery.php +++ b/api/listener_recovery.php @@ -9,17 +9,27 @@ $LOG_LEVEL = LOG_DEBUG; //$LOG_LEVEL = LOG_INFO; error_reporting(E_ALL & ~E_DEPRECATED & ~E_NOTICE); + ini_set('display_errors', '0'); ini_set('log_errors', '1'); ini_set('error_log', '/var/log/mqtt-listener/recovery-error.log'); + set_time_limit(0); ignore_user_abort(true); -function logmsg(string $msg, int $level = LOG_INFO): void { +function logmsg(string $msg, int $level = LOG_INFO): void +{ global $LOG_LEVEL; - if ($level > $LOG_LEVEL) return; + + if ($level > $LOG_LEVEL) { + return; + } + echo '[' . date('Y-m-d H:i:s') . "] $msg\n"; - if (function_exists('flush')) @flush(); + + if (function_exists('flush')) { + @flush(); + } } require __DIR__ . '/../vendor/autoload.php'; @@ -27,28 +37,53 @@ require __DIR__ . '/../vendor/autoload.php'; use PhpMqtt\Client\MqttClient; use PhpMqtt\Client\ConnectionSettings; +// ===================================================== // DB +// ===================================================== + $db = new PDO( "mysql:host=localhost;dbname=cagalhao;charset=utf8", "master", "master", - [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION] + [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION + ] ); -// RECOVERY BROKER +// ===================================================== +// RECOVERY MQTT +// ===================================================== + $recoveryHost = 'mqtt-recovery.xupas.mywire.org'; $recoveryPort = 8884; + $recoveryUser = 'recovery'; $recoveryPass = 'recovery123'; -// MAIN BROKER TEST +// ===================================================== +// MAIN MQTT +// ===================================================== + $mainHost = 'mqtt.xupas.mywire.org'; $mainPort = 8883; -// Cert principal a enviar aos ESPs -$certPath = "/etc/mosquitto/certs/server.crt"; +// ===================================================== +// CERT +// ===================================================== + +$certPath = '/etc/mosquitto/certs/chain.pem'; // cert Let's Encrypt do broker principal +$certUrl = 'https://mqtt.xupas.mywire.org/cert.pem'; + +// ===================================================== +// GLOBAL SECRET +// ===================================================== + +$authSecret = 'K82A91F0'; + +// ===================================================== +// MQTT SETTINGS +// ===================================================== -// MQTT recovery settings $settings = (new ConnectionSettings()) ->setUsername($recoveryUser) ->setPassword($recoveryPass) @@ -59,22 +94,38 @@ $settings = (new ConnectionSettings()) ->setSocketTimeout(1) ->setReconnectAutomatically(true); +// ===================================================== +// MQTT CLIENT +// ===================================================== + $mqtt = new MqttClient( $recoveryHost, $recoveryPort, 'listener-recovery-' . getmypid() ); -function main_broker_online(string $host, int $port): bool { +// ===================================================== +// MAIN BROKER CHECK +// ===================================================== + +function main_broker_online(string $host, int $port): bool +{ $fp = @fsockopen($host, $port, $errno, $errstr, 2); + if ($fp) { fclose($fp); return true; } + return false; } -function send_switch_primary(MqttClient $mqtt, PDO $db): void { +// ===================================================== +// SEND SWITCH PRIMARY +// ===================================================== + +function send_switch_primary(MqttClient $mqtt, PDO $db): void +{ $stmt = $db->query(" SELECT uid FROM contadores @@ -82,11 +133,14 @@ function send_switch_primary(MqttClient $mqtt, PDO $db): void { "); while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { + $uid = $row['uid']; $mqtt->publish( "esp/$uid/cmd", - json_encode(['cmd' => 'switch_primary']), + json_encode([ + 'cmd' => 'SWITCH_PRIMARY' + ]), 1 ); @@ -94,7 +148,17 @@ function send_switch_primary(MqttClient $mqtt, PDO $db): void { } } -function send_cert_update(MqttClient $mqtt, string $uid, string $certPath): void { +// ===================================================== +// SEND CERT +// ===================================================== + +function send_cert_chunks( + MqttClient $mqtt, + string $uid, + string $certPath, + int $linesPerChunk = 16 +): void { + if (!file_exists($certPath)) { logmsg("Cert não encontrado: $certPath", LOG_ERROR); return; @@ -102,47 +166,317 @@ function send_cert_update(MqttClient $mqtt, string $uid, string $certPath): void $cert = file_get_contents($certPath); + // validar com openssl antes de enviar + $parsed = openssl_x509_read($cert); + if ($parsed === false) { + logmsg("Cert inválido (openssl): $certPath", LOG_ERROR); + return; + } + + $lines = explode("\n", rtrim($cert)); + $chunks = array_chunk($lines, $linesPerChunk); + $total = count($chunks); + + logmsg("CERT_BEGIN $uid total=$total chunks"); + $mqtt->publish( "esp/$uid/cmd", - json_encode([ - 'cmd' => 'UPDATE_CERT', - 'pem' => $cert - ]), + json_encode(['cmd' => 'CERT_BEGIN', 'total' => $total]), 1 ); - logmsg("UPDATE_CERT enviado para $uid"); + foreach ($chunks as $seq => $chunk) { + $data = implode("\n", $chunk) . "\n"; + $mqtt->publish( + "esp/$uid/cmd", + json_encode(['cmd' => 'CERT_CHUNK', 'seq' => $seq, 'data' => $data]), + 1 + ); + logmsg("CERT_CHUNK $uid seq=$seq"); + usleep(100000); // 100ms entre chunks + } + + $mqtt->publish( + "esp/$uid/cmd", + json_encode(['cmd' => 'CERT_COMMIT']), + 1 + ); + + logmsg("CERT_COMMIT enviado para $uid"); } +// ===================================================== +// SUBSCRIPTIONS +// ===================================================== + $subscribe = function () use ( $mqtt, $db, $certPath, + $certUrl, $mainHost, - $mainPort + $mainPort, + $authSecret ) { - $mqtt->subscribe( - 'esp/+/status', - function (string $topic, string $msg) - use ($db, $mqtt, $mainHost, $mainPort) { + // ================================================= + // AUTH + // ================================================= - $uid = explode('/', $topic)[1] ?? null; - if (!$uid) return; + $mqtt->subscribe( + 'esp/+/auth', + function (string $topic, string $msg) + use ($db, $mqtt, $authSecret) + { + + $uid = explode('/', $topic)[1] ?? null; + + if (!$uid) { + return; + } + + logmsg("AUTH RX $uid => $msg"); + + $json = json_decode($msg, true); + + if (!$json) { + + logmsg( + "JSON AUTH inválido", + LOG_ERROR + ); + + return; + } + + // ============================================= + // HELLO + // ============================================= + + if (($json['cmd'] ?? '') === 'HELLO') { + + $nonce = + strtoupper( + bin2hex(random_bytes(16)) + ); $stmt = $db->prepare(" INSERT INTO contadores - (uid, entradas, saidas, entradas_total, saidas_total, last_seen, online) + (uid, auth_nonce, last_seen, online, blocked) VALUES - (:uid,0,0,0,0,NOW(),1) + (:uid, :nonce, NOW(), 1, 1) + ON DUPLICATE KEY UPDATE + auth_nonce=:nonce, + last_seen=NOW(), + online=1 + "); + /* novos ESPs entram com blocked=1 (pendente aprovação) + ESPs existentes mantêm o blocked actual */ + + $stmt->execute([ + 'uid' => $uid, + 'nonce' => $nonce + ]); + + $mqtt->publish( + "esp/$uid/cmd", + json_encode([ + 'cmd' => 'AUTH_CHALLENGE', + 'nonce' => $nonce + ]), + 1 + ); + + logmsg( + "AUTH_CHALLENGE enviado para $uid" + ); + + return; + } + + // ============================================= + // AUTH RESPONSE + // ============================================= + + if (($json['cmd'] ?? '') === 'AUTH_RESPONSE') { + + $hash = + strtoupper( + $json['hash'] ?? '' + ); + + if (!$hash) { + + logmsg( + "AUTH sem hash", + LOG_ERROR + ); + + return; + } + + $stmt = $db->prepare(" + SELECT auth_nonce, blocked + FROM contadores + WHERE uid=:uid + LIMIT 1 + "); + + $stmt->execute([ + 'uid' => $uid + ]); + + $row = + $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$row) { + + logmsg( + "UID desconhecido: $uid", + LOG_ERROR + ); + + return; + } + + $nonce = + $row['auth_nonce'] ?? ''; + + if (!$nonce) { + + logmsg( + "Nonce vazio para $uid", + LOG_ERROR + ); + + return; + } + + $expected = + strtoupper( + hash_hmac( + 'sha256', + $nonce, + $authSecret + ) + ); + + if (hash_equals($expected, $hash)) { + + // verificar se está aprovado + if ((int)($row['blocked'] ?? 1) === 1) { + + logmsg("AUTH PENDENTE/BLOQUEADO $uid — aguarda aprovação", LOG_ERROR); + + $mqtt->publish( + "esp/$uid/cmd", + json_encode(['cmd' => 'AUTH_FAIL']), + 1 + ); + + return; + } + + logmsg("AUTH OK $uid"); + + $stmt = $db->prepare(" + UPDATE contadores + SET + auth_nonce=NULL, + auth_ok=1, + online=1, + last_seen=NOW() + WHERE uid=:uid + "); + + $stmt->execute([ + 'uid' => $uid + ]); + + $mqtt->publish( + "esp/$uid/cmd", + json_encode([ + 'cmd' => 'AUTH_OK' + ]), + 1 + ); + + } else { + + logmsg( + "AUTH FAIL $uid", + LOG_ERROR + ); + + $mqtt->publish( + "esp/$uid/cmd", + json_encode([ + 'cmd' => 'AUTH_FAIL' + ]), + 1 + ); + } + + return; + } + + }, 1); + + // ================================================= + // STATUS + // ================================================= + + $mqtt->subscribe( + 'esp/+/status', + function ( + string $topic, + string $msg + ) use ( + $db, + $mqtt, + $mainHost, + $mainPort + ) { + + $uid = explode('/', $topic)[1] ?? null; + + if (!$uid) { + return; + } + + $stmt = $db->prepare(" + INSERT INTO contadores + ( + uid, + entradas, + saidas, + entradas_total, + saidas_total, + last_seen, + online + ) + VALUES + ( + :uid, + 0, + 0, + 0, + 0, + NOW(), + 1 + ) ON DUPLICATE KEY UPDATE last_seen=NOW(), online=1 "); - $stmt->execute(['uid' => $uid]); + $stmt->execute([ + 'uid' => $uid + ]); - logmsg("RECOVERY STATUS $uid => $msg"); + logmsg( + "RECOVERY STATUS $uid => $msg" + ); static $lastCheck = 0; @@ -152,15 +486,27 @@ $subscribe = function () use ( logmsg("CHECK broker"); - if (main_broker_online($mainHost, $mainPort)) { + if ( + main_broker_online( + $mainHost, + $mainPort + ) + ) { - logmsg("MAIN broker online"); + logmsg( + "MAIN broker online" + ); - send_switch_primary($mqtt, $db); + send_switch_primary( + $mqtt, + $db + ); } else { - logmsg("MAIN broker offline"); + logmsg( + "MAIN broker offline" + ); } } @@ -168,61 +514,140 @@ $subscribe = function () use ( 0 ); + // ================================================= + // EVENT + // ================================================= - $mqtt->subscribe('esp/+/event', function (string $topic, string $msg) use ($db) { - $uid = explode('/', $topic)[1] ?? null; - if (!$uid) return; + $mqtt->subscribe( + 'esp/+/event', + function ( + string $topic, + string $msg + ) { - logmsg("RECOVERY EVENT $uid => $msg"); - }, 0); + $uid = + explode('/', $topic)[1] ?? null; - $mqtt->subscribe('esp/+/cert_request', function (string $topic, string $msg) use ($mqtt, $certPath) { - $uid = explode('/', $topic)[1] ?? null; - if (!$uid) return; + if (!$uid) { + return; + } - logmsg("CERT_REQUEST de $uid"); - send_cert_update($mqtt, $uid, $certPath); - }, 0); + logmsg( + "RECOVERY EVENT $uid => $msg" + ); + + }, + 0 + ); + + // ================================================= + // CERT REQUEST + // ================================================= + + $mqtt->subscribe( + 'esp/+/cert_request', + function ( + string $topic, + string $msg + ) use ( + $mqtt, + $certPath, + $certUrl + ) { + + $uid = + explode('/', $topic)[1] ?? null; + + if (!$uid) { + return; + } + + logmsg("CERT_REQUEST de $uid → chunks"); + + send_cert_chunks($mqtt, $uid, $certPath); + + }, + 1 + ); }; +// ===================================================== +// MAIN LOOP +// ===================================================== + while (true) { + try { - logmsg("Ligar recovery $recoveryHost:$recoveryPort"); + + logmsg( + "Ligar recovery $recoveryHost:$recoveryPort" + ); $mqtt->connect($settings, false); + $subscribe(); - logmsg("RECOVERY ligado e subscrito"); + logmsg( + "RECOVERY ligado e subscrito" + ); $nextMainCheck = time() + 10; while ($mqtt->isConnected()) { + $mqtt->loop(false); - logmsg("tick"); + if (time() >= $nextMainCheck) { + $nextMainCheck = time() + 15; - - logmsg("LOOP recovery", LOG_DEBUG); - - if (main_broker_online($mainHost, $mainPort)) { - - logmsg("MAIN broker online"); - - logmsg("MAIN broker online. Mandar switch_primary."); - send_switch_primary($mqtt, $db); + + logmsg( + "LOOP recovery", + LOG_DEBUG + ); + + if ( + main_broker_online( + $mainHost, + $mainPort + ) + ) { + + logmsg( + "MAIN broker online" + ); + + logmsg( + "Mandar switch_primary" + ); + + send_switch_primary( + $mqtt, + $db + ); + } else { - logmsg("MAIN broker ainda offline", LOG_DEBUG); + + logmsg( + "MAIN broker ainda offline", + LOG_DEBUG + ); } } usleep(100000); } - - + $mqtt->disconnect(); } catch (Throwable $e) { - logmsg("ERRO recovery: " . $e->getMessage(), LOG_ERROR); + + logmsg( + "ERRO recovery: " . + $e->getMessage(), + LOG_ERROR + ); + sleep(5); } } \ No newline at end of file diff --git a/api/menu.php b/api/menu.php index 76dc30b..b5698f0 100644 --- a/api/menu.php +++ b/api/menu.php @@ -92,6 +92,9 @@ if (isset($_GET['vpn'])) {
+ diff --git a/api/mqtt_listener.php b/api/mqtt_listener.php index 71fb356..c3a5ec6 100644 --- a/api/mqtt_listener.php +++ b/api/mqtt_listener.php @@ -63,6 +63,8 @@ $mqttHost = 'mqtt.xupas.mywire.org'; $mqttUser = 'xupa'; $mqttPass = 'xupa'; +$deviceSecret = 'K82A91F0'; + $bootstrapSecret = "XUPA_TEMP_SECRET_2026"; $certPath = "/etc/mosquitto/certs/server.crt"; @@ -98,24 +100,70 @@ $mqttBootstrap = new MqttClient($mqttHost, 1883, 'listener-bootstrap'); /* ========================= * SUBSCRIPTIONS TLS * ========================= */ -$subscribeTls = function () use ($mqttTls, $db) { +$subscribeTls = function () use ($mqttTls, $db, $deviceSecret) { // ========================= // STATUS ESP // ========================= - $mqttTls->subscribe('esp/+/status', function (string $topic, string $msg) use ($db) { - + $mqttTls->subscribe('esp/+/status', function (string $topic, string $msg) + use ($db, $mqttTls, $deviceSecret) + { $uid = explode('/', $topic)[1] ?? null; if (!$uid) return; - $stmt = $db->prepare(" - INSERT INTO contadores (uid, entradas, saidas, entradas_total, saidas_total, last_seen, online) - VALUES (:uid,0,0,0,0,NOW(),1) - ON DUPLICATE KEY UPDATE last_seen=NOW(), online=1 - "); - $stmt->execute(['uid' => $uid]); + $json = json_decode($msg, true); + $uptime = isset($json['uptime']) ? (int)$json['uptime'] : null; + $heap = isset($json['heap']) ? (int)$json['heap'] : null; + $rssi = isset($json['rssi']) ? (int)$json['rssi'] : null; + $broker = isset($json['broker']) ? (string)$json['broker'] : null; + $hmac = isset($json['hmac']) ? strtoupper((string)$json['hmac']) : null; - logmsg("STATUS $uid"); + // Validar HMAC — apenas aceitar heartbeats assinados + if ($hmac === null || $uptime === null) { + logmsg("STATUS sem HMAC/uptime $uid — ignorado", LOG_ERROR); + return; + } + + $expected = strtoupper(hash_hmac('sha256', $uid . '|' . $uptime, $deviceSecret)); + + if (!hash_equals($expected, $hmac)) { + logmsg("STATUS HMAC INVÁLIDO $uid — ignorado", LOG_ERROR); + return; + } + + // HMAC OK → actualizar DB + $stmt = $db->prepare(" + INSERT INTO contadores + (uid, entradas, saidas, entradas_total, saidas_total, last_seen, online, + uptime, heap, rssi, current_broker) + VALUES + (:uid, 0, 0, 0, 0, NOW(), 1, + :uptime, :heap, :rssi, :broker) + ON DUPLICATE KEY UPDATE + last_seen=NOW(), + online=1, + uptime=COALESCE(:uptime2, uptime), + heap=COALESCE(:heap2, heap), + rssi=COALESCE(:rssi2, rssi), + current_broker=COALESCE(:broker2, current_broker) + "); + $stmt->execute([ + 'uid' => $uid, + 'uptime' => $uptime, + 'heap' => $heap, + 'rssi' => $rssi, + 'broker' => $broker, + 'uptime2' => $uptime, + 'heap2' => $heap, + 'rssi2' => $rssi, + 'broker2' => $broker, + ]); + + // Responder com HEARTBEAT_OK → ESP arranca serviços (sem retain) + $mqttTls->publish("esp/$uid/cmd", + json_encode(['cmd' => 'HEARTBEAT_OK']), 1, false); + + logmsg("STATUS OK $uid uptime=$uptime heap=$heap rssi=$rssi broker=$broker → HEARTBEAT_OK"); }, 0); @@ -217,11 +265,28 @@ while (true) { $subscribeBootstrap(); logmsg("Bootstrap ligado"); + $nextOfflineCheck = time() + 15; + while ($mqttTls->isConnected() && $mqttBootstrap->isConnected()) { $mqttTls->loop(true, false); $mqttBootstrap->loop(true, false); + if (time() >= $nextOfflineCheck) { + $nextOfflineCheck = time() + 15; + + $affected = $db->exec(" + UPDATE contadores + SET online=0 + WHERE online=1 + AND TIMESTAMPDIFF(SECOND, last_seen, NOW()) > 35 + "); + + if ($affected > 0) { + logmsg("OFFLINE sweep: $affected dispositivo(s) marcado(s) offline"); + } + } + usleep(100000); } diff --git a/api/protecao.php b/api/protecao.php index 7ae9a2c..8ae9e40 100644 --- a/api/protecao.php +++ b/api/protecao.php @@ -3,6 +3,6 @@ if (session_status() === PHP_SESSION_NONE) { session_start(); } if (!isset($_SESSION['user_id'])) { - header("Location: login.php"); + header("Location: /api/login.php"); exit; } \ No newline at end of file diff --git a/api/send_cmd.php b/api/send_cmd.php index 6793335..c6a6dcc 100644 --- a/api/send_cmd.php +++ b/api/send_cmd.php @@ -27,7 +27,7 @@ mqtt_publish($topic, json_encode($msg, JSON_UNESCAPED_SLASHES)); // ----------------------- BD ------------------------- if ($cmd === 'BLOCK') { - $stmt = $db->prepare("UPDATE contadores SET blocked = ? WHERE uid = ?"); + $stmt = $pdo->prepare("UPDATE contadores SET blocked = ? WHERE uid = ?"); $stmt->execute([intval($value), $uid]); } diff --git a/api/validar.php b/api/validar.php index 9141e95..854ddfa 100644 --- a/api/validar.php +++ b/api/validar.php @@ -33,7 +33,7 @@ try { $_SESSION['username'] = $user['username']; // Redireciona para o menu principal - header("Location: menu.php"); + header("Location: /api/menu.php"); exit; } catch (Exception $e) { diff --git a/comandos.php b/comandos.php index 15d4f3f..df45f6e 100644 --- a/comandos.php +++ b/comandos.php @@ -22,7 +22,7 @@ try { } // Buscar lista de devices - $devices = $pdo->query("SELECT uid FROM devices ORDER BY uid ASC")->fetchAll(); + $devices = $pdo->query("SELECT uid FROM contadores ORDER BY uid ASC")->fetchAll(); } catch (Exception $e) { die("Erro: " . $e->getMessage()); diff --git a/config.php b/config.php index fc02def..203fcc2 100644 --- a/config.php +++ b/config.php @@ -1,7 +1,7 @@ '192.168.10.200', // depois trocas para 'estica.bot.nu' - 'port' => 1883, // 8883 se fores usar TLS mais tarde + 'host' => 'mqtt.xupas.mywire.org', + 'port' => 8883, 'clientId' => 'php-web-' . bin2hex(random_bytes(3)), 'username' => 'xupa', 'password' => 'xupa', diff --git a/console.php b/console.php index 6841969..2fa07f4 100644 --- a/console.php +++ b/console.php @@ -1,199 +1,482 @@ query(" - UPDATE contadores - SET online = 0 - WHERE TIMESTAMPDIFF(SECOND, last_seen, NOW()) > 20 -"); +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]); -// ---------- 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) ---------- +// ===================================================== +// 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, entradas, saidas, online, blocked, - last_seen, - TIMESTAMPDIFF(SECOND, last_seen, NOW()) AS ago + 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 +// ===================================================== +// TOTAIS +// ===================================================== + $sumEntradas = 0; -$sumSaidas = 0; -$onlineCount = 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++; + + if ((int)$r['online'] === 1) { + $onlineCount++; + } else { + $offlineCount++; + } } ?> + + -| UID | -Entradas | -Saídas | -Estado | -Último Sinal | -Ações | + +UID | +NOTA | +ENTRADAS | +SAÍDAS | +ESTADO | +BROKER | +RSSI | +HEAP | +UPTIME | +ÚLTIMO | +AÇÕES | +
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| = htmlspecialchars($r['uid']) ?> | -= $r['entradas'] ?> | -= $r['saidas'] ?> | -= $state ?> | -= $lastSeen ?> | -- - = $blocked ? 'Desbloquear' : 'Bloquear' ?> - + | |||||||||||
| + = htmlspecialchars($r['uid']) ?> | + ++ + + + | + += (int)$r['entradas'] ?> | + += (int)$r['saidas'] ?> | + += $state ?> | + += $broker ?> | + ++ + = isset($r['rssi']) + ? $r['rssi'] . " dBm" + : "-" ?> + + | + ++ + = isset($r['heap']) + ? number_format((int)$r['heap']) + : "-" ?> + + | + ++ + + + | + += $lastSeen ?> | + ++ + + + | +