175 lines
8.3 KiB
PHP
175 lines
8.3 KiB
PHP
<?php
|
|
require_once __DIR__.'/inc/common.php';
|
|
|
|
$OTA_DIR = __DIR__.'/ota/';
|
|
$MANIFEST = $OTA_DIR.'manifest.json';
|
|
$MANIFEST_URL = (isset($_SERVER['HTTPS']) ? 'https' : 'http').'://'.$_SERVER['HTTP_HOST'].'/ota/manifest.json';
|
|
|
|
$msg = ''; $msgOk = true;
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['fw'])) {
|
|
$chip = preg_replace('/[^a-z0-9]/', '', strtolower($_POST['chip'] ?? ''));
|
|
if (!in_array($chip, ['s3','wroom'], true)) {
|
|
$msg = 'Chip invalido.'; $msgOk = false;
|
|
} else {
|
|
$dest = $OTA_DIR."fw_{$chip}.bin";
|
|
if (move_uploaded_file($_FILES['fw']['tmp_name'], $dest)) {
|
|
$manifest = file_exists($MANIFEST) ? json_decode(file_get_contents($MANIFEST), true) : [];
|
|
$manifest[$chip] = (isset($_SERVER['HTTPS']) ? 'https' : 'http').'://'.$_SERVER['HTTP_HOST']."/ota/fw_{$chip}.bin";
|
|
if (!empty($_POST['version'])) $manifest['version'] = trim($_POST['version']);
|
|
ksort($manifest);
|
|
file_put_contents($MANIFEST, json_encode($manifest, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES));
|
|
$msg = "fw_{$chip}.bin guardado. Manifest atualizado.";
|
|
} else {
|
|
$msg = 'Erro ao guardar ficheiro.'; $msgOk = false;
|
|
}
|
|
}
|
|
$qs = $msgOk ? '?ok='.urlencode($msg) : '?err='.urlencode($msg);
|
|
header("Location: /ota.php$qs"); exit;
|
|
}
|
|
|
|
if (isset($_GET['ok'])) { $msg = htmlspecialchars($_GET['ok']); $msgOk = true; }
|
|
if (isset($_GET['err'])) { $msg = htmlspecialchars($_GET['err']); $msgOk = false; }
|
|
|
|
$manifest = file_exists($MANIFEST) ? json_decode(file_get_contents($MANIFEST), true) : null;
|
|
$latest = $manifest['version'] ?? null;
|
|
|
|
$devices = db()->query("
|
|
SELECT uid, chip, fw_ver, online, blocked, last_seen,
|
|
TIMESTAMPDIFF(SECOND, last_seen, NOW()) AS ago_s
|
|
FROM contadores ORDER BY uid ASC
|
|
")->fetchAll();
|
|
|
|
$title = "OTA Firmware";
|
|
ob_start();
|
|
?>
|
|
|
|
<div class="page-header">
|
|
<div>
|
|
<div class="breadcrumb"><a href="/painel.php">Painel</a><span class="sep">›</span>OTA</div>
|
|
<h1>OTA Firmware</h1>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if ($msg): ?>
|
|
<div class="alert <?= $msgOk ? 'alert-success' : 'alert-error' ?>"><?= $msgOk ? '✓' : '⚠' ?> <?= $msg ?></div>
|
|
<?php endif; ?>
|
|
|
|
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin-bottom:24px">
|
|
|
|
<!-- Manifest -->
|
|
<div class="card" style="padding:20px">
|
|
<div style="font-weight:600;margin-bottom:14px;font-size:13px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted)">Manifest</div>
|
|
<?php if ($manifest): ?>
|
|
<div style="font-size:22px;font-weight:600;color:var(--success);margin-bottom:12px"><?= h($latest ?? '—') ?></div>
|
|
<?php foreach (['s3','wroom'] as $chip): $f = $OTA_DIR."fw_{$chip}.bin"; ?>
|
|
<div style="display:flex;justify-content:space-between;font-size:12px;margin-bottom:6px;padding:6px 10px;background:var(--surface2);border-radius:6px">
|
|
<span style="color:var(--muted)"><?= $chip ?></span>
|
|
<?php if (file_exists($f)): ?>
|
|
<span><?= round(filesize($f)/1024/1024,2) ?> MB <span style="color:var(--muted)"><?= date('d/m H:i', filemtime($f)) ?></span></span>
|
|
<?php else: ?><span style="color:var(--danger)">sem ficheiro</span><?php endif; ?>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
<div style="margin-top:10px;font-size:10px;color:var(--muted);font-family:monospace;word-break:break-all"><?= h($MANIFEST_URL) ?></div>
|
|
<?php else: ?>
|
|
<div style="color:var(--muted);font-size:13px">Nenhum manifest ainda.</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<!-- Upload S3 -->
|
|
<div class="card" style="padding:20px">
|
|
<div style="font-weight:600;margin-bottom:14px;font-size:13px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted)">Upload S3</div>
|
|
<form method="post" enctype="multipart/form-data">
|
|
<input type="hidden" name="chip" value="s3">
|
|
<div style="margin-bottom:10px">
|
|
<label style="font-size:11px;color:var(--muted);display:block;margin-bottom:4px">Versao</label>
|
|
<input name="version" type="text" placeholder="<?= h($latest ?? '1.0.10') ?>"
|
|
style="background:var(--surface2);border:1px solid var(--border);color:var(--text);padding:6px 10px;border-radius:6px;font-size:13px;width:100%">
|
|
</div>
|
|
<div style="margin-bottom:14px">
|
|
<label style="font-size:11px;color:var(--muted);display:block;margin-bottom:4px">fw_s3.bin</label>
|
|
<input name="fw" type="file" accept=".bin" style="font-size:12px;color:var(--text)">
|
|
</div>
|
|
<button type="submit" class="btn btn-primary btn-sm" style="width:100%">Upload S3</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Upload WROOM -->
|
|
<div class="card" style="padding:20px">
|
|
<div style="font-weight:600;margin-bottom:14px;font-size:13px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted)">Upload WROOM</div>
|
|
<form method="post" enctype="multipart/form-data">
|
|
<input type="hidden" name="chip" value="wroom">
|
|
<div style="margin-bottom:10px">
|
|
<label style="font-size:11px;color:var(--muted);display:block;margin-bottom:4px">Versao</label>
|
|
<input name="version" type="text" placeholder="<?= h($latest ?? '1.0.10') ?>"
|
|
style="background:var(--surface2);border:1px solid var(--border);color:var(--text);padding:6px 10px;border-radius:6px;font-size:13px;width:100%">
|
|
</div>
|
|
<div style="margin-bottom:14px">
|
|
<label style="font-size:11px;color:var(--muted);display:block;margin-bottom:4px">fw_wroom.bin</label>
|
|
<input name="fw" type="file" accept=".bin" style="font-size:12px;color:var(--text)">
|
|
</div>
|
|
<button type="submit" class="btn btn-primary btn-sm" style="width:100%">Upload WROOM</button>
|
|
</form>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Dispositivos -->
|
|
<div class="card">
|
|
<div class="card-header" style="display:flex;align-items:center;justify-content:space-between">
|
|
<h2>Dispositivos <span style="color:var(--muted);font-weight:400;font-size:13px">(<?= count($devices) ?>)</span></h2>
|
|
<?php if ($manifest): ?>
|
|
<button class="btn btn-primary btn-sm" onclick="otaAll()">⬆ Atualizar todos os desatualizados</button>
|
|
<?php endif; ?>
|
|
</div>
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead><tr><th>UID</th><th>Chip</th><th>Versao atual</th><th>Disponivel</th><th>Online</th><th></th></tr></thead>
|
|
<tbody>
|
|
<?php foreach ($devices as $d):
|
|
$online = (bool)$d['online'];
|
|
$fw = $d['fw_ver'] ?? null;
|
|
$chip = $d['chip'] ?? null;
|
|
$outdated = $latest && $fw && $fw !== $latest;
|
|
?>
|
|
<tr>
|
|
<td style="font-family:monospace;font-size:12px"><?= h($d['uid']) ?></td>
|
|
<td style="font-size:12px;color:var(--muted)"><?= h($chip ?? '—') ?></td>
|
|
<td style="font-size:12px">
|
|
<?php if ($fw): ?>
|
|
<span style="color:<?= $outdated ? '#d29922' : 'var(--success)' ?>"><?= h($fw) ?></span>
|
|
<?php else: ?><span style="color:var(--muted)">—</span><?php endif; ?>
|
|
</td>
|
|
<td style="font-size:12px;color:var(--muted)"><?= $latest ? h($latest) : '—' ?></td>
|
|
<td><?php if ($online): ?><span class="badge" style="background:#0d2818;color:var(--success)">online</span><?php else: ?><span class="badge" style="background:#1e2733;color:var(--muted)">offline</span><?php endif; ?></td>
|
|
<td>
|
|
<?php if ($manifest && $online): ?>
|
|
<button class="btn btn-secondary btn-sm" onclick="otaOne('<?= h($d['uid']) ?>')">OTA</button>
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const MANIFEST_URL = <?= json_encode($MANIFEST_URL) ?>;
|
|
function otaOne(uid) {
|
|
fetch('/api/ota_trigger.php', {
|
|
method:'POST', headers:{'Content-Type':'application/json'},
|
|
body: JSON.stringify({uid, url: MANIFEST_URL, cmd:'OTA_CHECK'})
|
|
}).then(r=>r.json()).then(r=>{ alert(r.ok ? 'OTA_CHECK enviado para '+uid : 'Erro: '+(r.error||'?')); });
|
|
}
|
|
function otaAll() {
|
|
if (!confirm('Enviar OTA_CHECK a todos os dispositivos online?')) return;
|
|
fetch('/api/ota_trigger.php', {
|
|
method:'POST', headers:{'Content-Type':'application/json'},
|
|
body: JSON.stringify({uid:'all', url: MANIFEST_URL, cmd:'OTA_CHECK'})
|
|
}).then(r=>r.json()).then(r=>{ alert(r.ok ? 'OTA_CHECK enviado a '+r.sent+' dispositivos.' : 'Erro: '+(r.error||'?')); });
|
|
}
|
|
</script>
|
|
|
|
<?php $content = ob_get_clean(); require __DIR__.'/inc/layout.php';
|