Files

169 lines
10 KiB
HTML

<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<title>Notifier Master - Voltec</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body { background: #0b0b0b; color: #e0e0e0; font-family: 'Segoe UI', sans-serif; }
.header-panel { background: #151515; padding: 20px; border-bottom: 2px solid #222; margin-bottom: 30px; }
.node-group { background: #111; border: 1px solid #222; border-radius: 15px; padding: 20px; margin-bottom: 30px; }
.node-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #333; margin-bottom: 20px; padding-bottom: 10px; }
.node-title { border-left: 4px solid #d9534f; padding-left: 15px; color: #fff; font-weight: bold; margin: 0; }
.node-status-badge { font-size: 0.7em; padding: 4px 10px; border-radius: 20px; text-transform: uppercase; font-weight: bold; }
.card-sensor { background: #1a1a1a; border-radius: 12px; margin-bottom: 15px; padding: 15px; border-left: 5px solid #333; position: relative; }
.badge-status { font-size: 0.75em; padding: 4px 10px; border-radius: 10px; font-weight: bold; }
.edit-btn { position: absolute; top: 8px; right: 8px; cursor: pointer; opacity: 0.3; }
.edit-btn:hover { opacity: 1; }
.node-row { background: #1a1a1a; padding: 15px; border-radius: 10px; margin-bottom: 10px; border: 1px solid #333; }
.loading-overlay { position: fixed; top:0; left:0; width:100%; height:100%; background: rgba(0,0,0,0.9); display:none; z-index:9999; justify-content:center; align-items:center; flex-direction:column; }
</style>
</head>
<body>
<div id="loader" class="loading-overlay">
<div class="spinner-border text-danger"></div>
<h5 class="mt-3 text-white">Varrendo central (1-600)...</h5>
</div>
<div class="header-panel shadow-sm">
<div class="container d-flex justify-content-between align-items-center">
<h2 class="mb-0 text-danger fw-bold">🔥 Notifier Voltec</h2>
<div class="btn-group">
<button class="btn btn-dark btn-sm" onclick="abrirModalNodes()">⚙️ Config</button>
<a href="/logout" class="btn btn-outline-danger btn-sm">Sair</a>
</div>
</div>
</div>
<div class="container">
{% if not licenciado %}
<div class="row justify-content-center mt-5">
<div class="col-md-6 bg-dark p-5 rounded border border-danger text-center shadow-lg">
<h2 class="text-danger">🔒 Sistema Bloqueado</h2>
<p class="text-muted">ID: <code class="bg-black p-2 d-block my-2">{{ hwid }}</code></p>
<textarea id="serial-in" class="form-control bg-black text-white mb-3" rows="3" placeholder="Serial"></textarea>
<button class="btn btn-danger w-100" onclick="ativar()">ATIVAR</button>
</div>
</div>
{% else %}
<div class="d-flex justify-content-between align-items-center mb-4 p-2 bg-dark rounded border border-secondary" style="gap: 15px; background-color: #151515 !important;">
<div class="flex-shrink-0">
<span class="badge {% if dias_restantes < 15 %}bg-danger text-white{% else %}bg-secondary text-white{% endif %} p-2 shadow-sm">
⏳ {{ dias_restantes }} dias restantes
</span>
</div>
<div class="flex-grow-1 text-end text-truncate" style="max-width: 400px;">
<span class="text-white fw-bold small" title="{{ lic_msg }}" style="letter-spacing: 0.5px; opacity: 0.9;">
{{ lic_msg }}
</span>
</div>
</div>
<div id="main-container"></div>
{% endif %}
</div>
<div class="modal fade" id="modalNodes" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content bg-dark border-secondary">
<div class="modal-header border-secondary"><h5 class="modal-title">Config</h5><button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button></div>
<div class="modal-body">
<div class="row mb-3">
<div class="col-md-6"><label>Zabbix Server</label><input type="text" id="zab-server" class="form-control bg-black text-white border-secondary" value="{{config.zabbix_server}}"></div>
<div class="col-md-6"><label>Hostname Zabbix</label><input type="text" id="zab-host" class="form-control bg-black text-white border-secondary" value="{{config.hostname_zabbix}}"></div>
</div>
<div class="d-flex justify-content-between mb-2"><h6>Nodes</h6><button class="btn btn-success btn-sm" onclick="addNodeRow()">+ Central</button></div>
<div id="nodes-list"></div>
</div>
<div class="modal-footer border-secondary"><button class="btn btn-danger w-100" onclick="salvarTudo()">Salvar e Reiniciar</button></div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
const modalNodes = new bootstrap.Modal(document.getElementById('modalNodes'));
let nodesAtuais = {{ config.nodes | tojson }};
async function ativar() {
const serial = document.getElementById('serial-in').value.trim();
const res = await fetch('/api/license', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({serial}) });
if(res.ok) { alert("Sucesso!"); location.reload(); } else { alert("Erro!"); }
}
function abrirModalNodes() {
const list = document.getElementById('nodes-list');
list.innerHTML = '';
nodesAtuais.forEach((node, idx) => {
const div = document.createElement('div');
div.className = 'node-row';
div.innerHTML = `
<div class="row g-2 align-items-end">
<div class="col-md-3"><input type="text" class="form-control form-control-sm n-nome" value="${node.nome}"></div>
<div class="col-md-4"><input type="text" class="form-control form-control-sm n-ip" value="${node.ip}"></div>
<div class="col-md-2"><input type="number" class="form-control form-control-sm n-unit" value="${node.unit}"></div>
<div class="col-md-3 d-flex gap-1"><button class="btn btn-warning btn-sm flex-grow-1" onclick="scanIndividual(${idx})">Scan</button><button class="btn btn-danger btn-sm" onclick="this.closest('.node-row').remove()">❌</button></div>
</div>`;
list.appendChild(div);
});
modalNodes.show();
}
function addNodeRow() {
const div = document.createElement('div');
div.className = 'node-row';
div.innerHTML = `<div class="row g-2 align-items-end"><div class="col-md-3"><input type="text" class="form-control form-control-sm n-nome" placeholder="Nome"></div><div class="col-md-4"><input type="text" class="form-control form-control-sm n-ip" placeholder="IP"></div><div class="col-md-2"><input type="number" class="form-control form-control-sm n-unit" value="3"></div><div class="col-md-3"><button class="btn btn-danger btn-sm w-100" onclick="this.closest('.node-row').remove()">❌</button></div></div>`;
document.getElementById('nodes-list').appendChild(div);
}
async function salvarTudo() {
const nodes = [];
document.querySelectorAll('.node-row').forEach(r => {
const n = r.querySelector('.n-nome').value;
const i = r.querySelector('.n-ip').value;
const u = r.querySelector('.n-unit').value;
if(n && i) nodes.push({ nome: n, ip: i, unit: parseInt(u) });
});
const res = await fetch('/api/nodes', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ zabbix_server: document.getElementById('zab-server').value, hostname_zabbix: document.getElementById('zab-host').value, nodes: nodes }) });
if(res.ok) location.reload();
}
async function scanIndividual(idx) {
const rows = document.querySelectorAll('.node-row');
const node = { nome: rows[idx].querySelector('.n-nome').value, ip: rows[idx].querySelector('.n-ip').value, unit: parseInt(rows[idx].querySelector('.n-unit').value) };
modalNodes.hide();
document.getElementById('loader').style.display = 'flex';
await fetch('/api/rescan_node', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(node) });
location.reload();
}
async function renomear(key, atual) {
const novo = prompt("Novo nome:", atual);
if(novo && novo !== atual) { await fetch('/api/rename', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({addr: key, label: novo}) }); atualizar(); }
}
async function atualizar() {
try {
const res = await fetch('/api/data');
const data = await res.json();
const main = document.getElementById('main-container');
if(!main) return;
const nodesMap = {};
data.sensores.forEach(s => { if(!nodesMap[s.node]) nodesMap[s.node] = []; nodesMap[s.node].push(s); });
main.innerHTML = '';
for(const n in nodesMap) {
const st = data.nodes_status[n] || "Offline";
let h = `<div class="node-group"><div class="node-header"><h4 class="node-title">📍 ${n}</h4><span class="node-status-badge ${st==='Online'?'bg-success':'bg-danger'}">${st}</span></div><div class="row">`;
nodesMap[n].forEach(s => {
h += `<div class="col-md-3"><div class="card-sensor" style="border-left-color: ${s.color}"><span class="edit-btn" onclick="renomear('${s.key}', '${s.label}')">✏️</span><div class="fw-bold text-white small">${s.label}</div><div class="mt-2"><span class="badge-status" style="background: ${s.color}22; color: ${s.color}; border: 1px solid ${s.color}">${s.status_text}</span></div><div class="time-upd text-end">⏱ ${s.last}</div></div></div>`;
});
main.innerHTML += h + `</div></div>`;
}
} catch(e){}
}
setInterval(atualizar, 3000); atualizar();
</script>
</body>
</html>