chore(tenants): conteudo de publico/ atualizado
This commit is contained in:
@@ -1981,6 +1981,38 @@ data:
|
||||
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||
.topo .acao:disabled { opacity: .6; cursor: wait; }
|
||||
|
||||
/* ── sino de alertas de infraestrutura ───────────────────────────────────── */
|
||||
.sino-caixa { position: relative; }
|
||||
.sino {
|
||||
background: none; border: 1px solid var(--line); color: var(--navy);
|
||||
border-radius: 10px; padding: 6px 10px; cursor: pointer; font-size: 16px;
|
||||
line-height: 1; position: relative;
|
||||
}
|
||||
.sino:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||
.sino[aria-expanded="true"] { border-color: var(--orange); }
|
||||
.sino-contagem {
|
||||
position: absolute; top: -7px; right: -7px;
|
||||
background: var(--perigo); color: var(--branco); border-radius: 999px;
|
||||
font-size: 11px; font-weight: 700; padding: 1px 6px; line-height: 1.4; min-width: 14px; text-align: center;
|
||||
}
|
||||
.sino-contagem.aviso { background: var(--alerta); }
|
||||
.sino-contagem.sem-dado { background: var(--muted); }
|
||||
.sino-painel {
|
||||
position: absolute; right: 0; top: calc(100% + 10px);
|
||||
width: min(360px, calc(100vw - 32px)); max-height: 60vh; overflow: auto;
|
||||
background: var(--branco); border: 1px solid var(--line); border-radius: 14px;
|
||||
box-shadow: 0 14px 34px rgba(11,44,61,.16); padding: 14px; z-index: 20; text-align: left;
|
||||
}
|
||||
.sino-topo { font-weight: 700; margin-bottom: 10px; }
|
||||
.sino-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
|
||||
.sino-item { border-radius: 10px; padding: 10px 12px; font-size: 13px; }
|
||||
.sino-item.critical { background: #fdecea; }
|
||||
.sino-item.warning { background: #fdf3e6; }
|
||||
.sino-item.info { background: var(--paper); }
|
||||
.sino-item .nome { font-weight: 700; display: block; }
|
||||
.sino-item .resumo { display: block; margin-top: 2px; }
|
||||
.sino-item .quem { color: var(--muted); font-size: 12px; display: block; margin-top: 4px; }
|
||||
|
||||
.wrap { max-width: 1320px; margin: 0 auto; padding: 22px 28px 48px; }
|
||||
|
||||
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
||||
@@ -2226,7 +2258,7 @@ data:
|
||||
`Cache-Control: immutable` por um ano. Suba o numero a cada publicacao.
|
||||
-->
|
||||
<link rel="stylesheet" href="/publico/fontes/fontes.css">
|
||||
<link rel="stylesheet" href="/publico/painel.css?v=2.82">
|
||||
<link rel="stylesheet" href="/publico/painel.css?v=2.83">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -2234,6 +2266,18 @@ data:
|
||||
<img class="logo" src="/publico/img/logo-athletic-map.png" alt="Athletic Map" width="150" height="37">
|
||||
<span class="titulo">Painel do dono</span>
|
||||
<span id="apurado" class="apurado oculto"></span>
|
||||
<div class="sino-caixa oculto" id="sinoCaixa">
|
||||
<button type="button" id="sino" class="sino" aria-haspopup="true" aria-expanded="false" title="Alertas de infraestrutura">
|
||||
<span aria-hidden="true">🔔</span>
|
||||
<span id="sinoContagem" class="sino-contagem oculto"></span>
|
||||
</button>
|
||||
<div id="sinoPainel" class="sino-painel oculto" role="menu">
|
||||
<div class="sino-topo">Alertas de infraestrutura</div>
|
||||
<ul id="sinoLista" class="sino-lista"></ul>
|
||||
<p id="sinoVazio" class="ajuda oculto">Nenhum alerta agora.</p>
|
||||
<p id="sinoIndisponivel" class="ajuda oculto">Não consegui falar com o monitoramento agora.</p>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" id="atualizar" class="acao oculto">Atualizar</button>
|
||||
<button type="button" id="sair" class="sair oculto">Sair</button>
|
||||
</header>
|
||||
@@ -2388,7 +2432,7 @@ data:
|
||||
|
||||
</main>
|
||||
|
||||
<script src="/publico/painel.js?v=2.82"></script>
|
||||
<script src="/publico/painel.js?v=2.83"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2422,16 +2466,22 @@ data:
|
||||
"use strict";
|
||||
|
||||
var API = "/api/plataforma/painel/visao";
|
||||
var API_ALERTAS = "/api/plataforma/painel/alertas-infra";
|
||||
var CHAVE = "atm.painel.token";
|
||||
var INTERVALO_SINO_MS = 60000;
|
||||
|
||||
var $ = function (id) { return document.getElementById(id); };
|
||||
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
||||
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
||||
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
||||
var corpo = $("linhas"), vazio = $("vazio");
|
||||
var sinoCaixa = $("sinoCaixa"), sino = $("sino"), sinoContagem = $("sinoContagem"),
|
||||
sinoPainel = $("sinoPainel"), sinoLista = $("sinoLista"),
|
||||
sinoVazio = $("sinoVazio"), sinoIndisponivel = $("sinoIndisponivel");
|
||||
|
||||
var visao = null;
|
||||
var tokenAtual = null;
|
||||
var sinoIntervalo = null;
|
||||
|
||||
/* ── token ──────────────────────────────────────────────────────────────── */
|
||||
|
||||
@@ -2448,6 +2498,83 @@ data:
|
||||
});
|
||||
}
|
||||
|
||||
/* ── sino de alertas de infraestrutura ─────────────────────────────────────
|
||||
*
|
||||
* Le o mesmo Alertmanager que ja manda e-mail — o sino nao inventa alerta
|
||||
* nenhum, so mostra o que o e-mail ja avisou. Falha calada de proposito:
|
||||
* se o servidor nao responder, o sino so fica "sem dado" — nao pode
|
||||
* derrubar o painel inteiro por causa de um widget auxiliar.
|
||||
*/
|
||||
function buscarAlertasInfra(token) {
|
||||
return fetch(API_ALERTAS, { headers: { "x-atm-admin-token": token } })
|
||||
.then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.json(); })
|
||||
.catch(function () { return null; }); // null = "sem dado", diferente de "zero alertas"
|
||||
}
|
||||
|
||||
function pesoSeveridade(s) { return s === "critical" ? 3 : s === "warning" ? 2 : 1; }
|
||||
|
||||
function pintarSino(dados) {
|
||||
sinoLista.innerHTML = "";
|
||||
sinoVazio.classList.add("oculto");
|
||||
sinoIndisponivel.classList.add("oculto");
|
||||
|
||||
if (!dados || dados.indisponivel) {
|
||||
sinoContagem.textContent = "!";
|
||||
sinoContagem.className = "sino-contagem sem-dado";
|
||||
sinoIndisponivel.classList.remove("oculto");
|
||||
return;
|
||||
}
|
||||
|
||||
var alertas = dados.alertas || [];
|
||||
if (alertas.length === 0) {
|
||||
sinoContagem.classList.add("oculto");
|
||||
sinoVazio.classList.remove("oculto");
|
||||
return;
|
||||
}
|
||||
|
||||
sinoContagem.textContent = String(alertas.length);
|
||||
sinoContagem.className = "sino-contagem" +
|
||||
(alertas.some(function (a) { return a.severidade === "critical"; }) ? "" : " aviso");
|
||||
sinoContagem.classList.remove("oculto");
|
||||
|
||||
alertas.forEach(function (a) {
|
||||
var li = document.createElement("li");
|
||||
var sev = pesoSeveridade(a.severidade) === 3 ? "critical" : pesoSeveridade(a.severidade) === 2 ? "warning" : "info";
|
||||
li.className = "sino-item " + sev;
|
||||
li.innerHTML = "<span class=\"nome\">" + esc(a.nome) + "</span>"
|
||||
+ (a.resumo ? "<span class=\"resumo\">" + esc(a.resumo) + "</span>" : "")
|
||||
+ (a.tenant ? "<span class=\"quem\">" + esc(a.tenant) + (a.desde ? " — " + ha(a.desde) : "") + "</span>"
|
||||
: (a.desde ? "<span class=\"quem\">" + ha(a.desde) + "</span>" : ""));
|
||||
sinoLista.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
function atualizarSino(token) {
|
||||
buscarAlertasInfra(token).then(pintarSino);
|
||||
}
|
||||
|
||||
function ligarSino(token) {
|
||||
sinoCaixa.classList.remove("oculto");
|
||||
atualizarSino(token);
|
||||
if (sinoIntervalo) clearInterval(sinoIntervalo);
|
||||
sinoIntervalo = setInterval(function () { atualizarSino(token); }, INTERVALO_SINO_MS);
|
||||
}
|
||||
|
||||
function desligarSino() {
|
||||
sinoCaixa.classList.add("oculto");
|
||||
fecharSino();
|
||||
if (sinoIntervalo) { clearInterval(sinoIntervalo); sinoIntervalo = null; }
|
||||
}
|
||||
|
||||
function abrirSino() {
|
||||
sinoPainel.classList.remove("oculto");
|
||||
sino.setAttribute("aria-expanded", "true");
|
||||
}
|
||||
function fecharSino() {
|
||||
sinoPainel.classList.add("oculto");
|
||||
sino.setAttribute("aria-expanded", "false");
|
||||
}
|
||||
|
||||
/* ── formatacao ─────────────────────────────────────────────────────────── */
|
||||
|
||||
function esc(s) {
|
||||
@@ -2958,6 +3085,7 @@ data:
|
||||
tokenAtual = token;
|
||||
pintar();
|
||||
mostrar(conteudo);
|
||||
ligarSino(token);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2992,6 +3120,17 @@ data:
|
||||
location.reload();
|
||||
});
|
||||
|
||||
sino.addEventListener("click", function () {
|
||||
var abrindo = sinoPainel.classList.contains("oculto");
|
||||
if (abrindo) { abrirSino(); if (tokenAtual) atualizarSino(tokenAtual); } else { fecharSino(); }
|
||||
});
|
||||
document.addEventListener("click", function (ev) {
|
||||
if (!sinoPainel.classList.contains("oculto") && !sinoCaixa.contains(ev.target)) fecharSino();
|
||||
});
|
||||
document.addEventListener("keydown", function (ev) {
|
||||
if (ev.key === "Escape") fecharSino();
|
||||
});
|
||||
|
||||
botaoAtualizar.addEventListener("click", function () {
|
||||
if (!tokenAtual) return;
|
||||
botaoAtualizar.disabled = true;
|
||||
|
||||
@@ -1981,6 +1981,38 @@ data:
|
||||
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||
.topo .acao:disabled { opacity: .6; cursor: wait; }
|
||||
|
||||
/* ── sino de alertas de infraestrutura ───────────────────────────────────── */
|
||||
.sino-caixa { position: relative; }
|
||||
.sino {
|
||||
background: none; border: 1px solid var(--line); color: var(--navy);
|
||||
border-radius: 10px; padding: 6px 10px; cursor: pointer; font-size: 16px;
|
||||
line-height: 1; position: relative;
|
||||
}
|
||||
.sino:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||
.sino[aria-expanded="true"] { border-color: var(--orange); }
|
||||
.sino-contagem {
|
||||
position: absolute; top: -7px; right: -7px;
|
||||
background: var(--perigo); color: var(--branco); border-radius: 999px;
|
||||
font-size: 11px; font-weight: 700; padding: 1px 6px; line-height: 1.4; min-width: 14px; text-align: center;
|
||||
}
|
||||
.sino-contagem.aviso { background: var(--alerta); }
|
||||
.sino-contagem.sem-dado { background: var(--muted); }
|
||||
.sino-painel {
|
||||
position: absolute; right: 0; top: calc(100% + 10px);
|
||||
width: min(360px, calc(100vw - 32px)); max-height: 60vh; overflow: auto;
|
||||
background: var(--branco); border: 1px solid var(--line); border-radius: 14px;
|
||||
box-shadow: 0 14px 34px rgba(11,44,61,.16); padding: 14px; z-index: 20; text-align: left;
|
||||
}
|
||||
.sino-topo { font-weight: 700; margin-bottom: 10px; }
|
||||
.sino-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
|
||||
.sino-item { border-radius: 10px; padding: 10px 12px; font-size: 13px; }
|
||||
.sino-item.critical { background: #fdecea; }
|
||||
.sino-item.warning { background: #fdf3e6; }
|
||||
.sino-item.info { background: var(--paper); }
|
||||
.sino-item .nome { font-weight: 700; display: block; }
|
||||
.sino-item .resumo { display: block; margin-top: 2px; }
|
||||
.sino-item .quem { color: var(--muted); font-size: 12px; display: block; margin-top: 4px; }
|
||||
|
||||
.wrap { max-width: 1320px; margin: 0 auto; padding: 22px 28px 48px; }
|
||||
|
||||
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
||||
@@ -2226,7 +2258,7 @@ data:
|
||||
`Cache-Control: immutable` por um ano. Suba o numero a cada publicacao.
|
||||
-->
|
||||
<link rel="stylesheet" href="/publico/fontes/fontes.css">
|
||||
<link rel="stylesheet" href="/publico/painel.css?v=2.82">
|
||||
<link rel="stylesheet" href="/publico/painel.css?v=2.83">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -2234,6 +2266,18 @@ data:
|
||||
<img class="logo" src="/publico/img/logo-athletic-map.png" alt="Athletic Map" width="150" height="37">
|
||||
<span class="titulo">Painel do dono</span>
|
||||
<span id="apurado" class="apurado oculto"></span>
|
||||
<div class="sino-caixa oculto" id="sinoCaixa">
|
||||
<button type="button" id="sino" class="sino" aria-haspopup="true" aria-expanded="false" title="Alertas de infraestrutura">
|
||||
<span aria-hidden="true">🔔</span>
|
||||
<span id="sinoContagem" class="sino-contagem oculto"></span>
|
||||
</button>
|
||||
<div id="sinoPainel" class="sino-painel oculto" role="menu">
|
||||
<div class="sino-topo">Alertas de infraestrutura</div>
|
||||
<ul id="sinoLista" class="sino-lista"></ul>
|
||||
<p id="sinoVazio" class="ajuda oculto">Nenhum alerta agora.</p>
|
||||
<p id="sinoIndisponivel" class="ajuda oculto">Não consegui falar com o monitoramento agora.</p>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" id="atualizar" class="acao oculto">Atualizar</button>
|
||||
<button type="button" id="sair" class="sair oculto">Sair</button>
|
||||
</header>
|
||||
@@ -2388,7 +2432,7 @@ data:
|
||||
|
||||
</main>
|
||||
|
||||
<script src="/publico/painel.js?v=2.82"></script>
|
||||
<script src="/publico/painel.js?v=2.83"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2422,16 +2466,22 @@ data:
|
||||
"use strict";
|
||||
|
||||
var API = "/api/plataforma/painel/visao";
|
||||
var API_ALERTAS = "/api/plataforma/painel/alertas-infra";
|
||||
var CHAVE = "atm.painel.token";
|
||||
var INTERVALO_SINO_MS = 60000;
|
||||
|
||||
var $ = function (id) { return document.getElementById(id); };
|
||||
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
||||
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
||||
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
||||
var corpo = $("linhas"), vazio = $("vazio");
|
||||
var sinoCaixa = $("sinoCaixa"), sino = $("sino"), sinoContagem = $("sinoContagem"),
|
||||
sinoPainel = $("sinoPainel"), sinoLista = $("sinoLista"),
|
||||
sinoVazio = $("sinoVazio"), sinoIndisponivel = $("sinoIndisponivel");
|
||||
|
||||
var visao = null;
|
||||
var tokenAtual = null;
|
||||
var sinoIntervalo = null;
|
||||
|
||||
/* ── token ──────────────────────────────────────────────────────────────── */
|
||||
|
||||
@@ -2448,6 +2498,83 @@ data:
|
||||
});
|
||||
}
|
||||
|
||||
/* ── sino de alertas de infraestrutura ─────────────────────────────────────
|
||||
*
|
||||
* Le o mesmo Alertmanager que ja manda e-mail — o sino nao inventa alerta
|
||||
* nenhum, so mostra o que o e-mail ja avisou. Falha calada de proposito:
|
||||
* se o servidor nao responder, o sino so fica "sem dado" — nao pode
|
||||
* derrubar o painel inteiro por causa de um widget auxiliar.
|
||||
*/
|
||||
function buscarAlertasInfra(token) {
|
||||
return fetch(API_ALERTAS, { headers: { "x-atm-admin-token": token } })
|
||||
.then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.json(); })
|
||||
.catch(function () { return null; }); // null = "sem dado", diferente de "zero alertas"
|
||||
}
|
||||
|
||||
function pesoSeveridade(s) { return s === "critical" ? 3 : s === "warning" ? 2 : 1; }
|
||||
|
||||
function pintarSino(dados) {
|
||||
sinoLista.innerHTML = "";
|
||||
sinoVazio.classList.add("oculto");
|
||||
sinoIndisponivel.classList.add("oculto");
|
||||
|
||||
if (!dados || dados.indisponivel) {
|
||||
sinoContagem.textContent = "!";
|
||||
sinoContagem.className = "sino-contagem sem-dado";
|
||||
sinoIndisponivel.classList.remove("oculto");
|
||||
return;
|
||||
}
|
||||
|
||||
var alertas = dados.alertas || [];
|
||||
if (alertas.length === 0) {
|
||||
sinoContagem.classList.add("oculto");
|
||||
sinoVazio.classList.remove("oculto");
|
||||
return;
|
||||
}
|
||||
|
||||
sinoContagem.textContent = String(alertas.length);
|
||||
sinoContagem.className = "sino-contagem" +
|
||||
(alertas.some(function (a) { return a.severidade === "critical"; }) ? "" : " aviso");
|
||||
sinoContagem.classList.remove("oculto");
|
||||
|
||||
alertas.forEach(function (a) {
|
||||
var li = document.createElement("li");
|
||||
var sev = pesoSeveridade(a.severidade) === 3 ? "critical" : pesoSeveridade(a.severidade) === 2 ? "warning" : "info";
|
||||
li.className = "sino-item " + sev;
|
||||
li.innerHTML = "<span class=\"nome\">" + esc(a.nome) + "</span>"
|
||||
+ (a.resumo ? "<span class=\"resumo\">" + esc(a.resumo) + "</span>" : "")
|
||||
+ (a.tenant ? "<span class=\"quem\">" + esc(a.tenant) + (a.desde ? " — " + ha(a.desde) : "") + "</span>"
|
||||
: (a.desde ? "<span class=\"quem\">" + ha(a.desde) + "</span>" : ""));
|
||||
sinoLista.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
function atualizarSino(token) {
|
||||
buscarAlertasInfra(token).then(pintarSino);
|
||||
}
|
||||
|
||||
function ligarSino(token) {
|
||||
sinoCaixa.classList.remove("oculto");
|
||||
atualizarSino(token);
|
||||
if (sinoIntervalo) clearInterval(sinoIntervalo);
|
||||
sinoIntervalo = setInterval(function () { atualizarSino(token); }, INTERVALO_SINO_MS);
|
||||
}
|
||||
|
||||
function desligarSino() {
|
||||
sinoCaixa.classList.add("oculto");
|
||||
fecharSino();
|
||||
if (sinoIntervalo) { clearInterval(sinoIntervalo); sinoIntervalo = null; }
|
||||
}
|
||||
|
||||
function abrirSino() {
|
||||
sinoPainel.classList.remove("oculto");
|
||||
sino.setAttribute("aria-expanded", "true");
|
||||
}
|
||||
function fecharSino() {
|
||||
sinoPainel.classList.add("oculto");
|
||||
sino.setAttribute("aria-expanded", "false");
|
||||
}
|
||||
|
||||
/* ── formatacao ─────────────────────────────────────────────────────────── */
|
||||
|
||||
function esc(s) {
|
||||
@@ -2958,6 +3085,7 @@ data:
|
||||
tokenAtual = token;
|
||||
pintar();
|
||||
mostrar(conteudo);
|
||||
ligarSino(token);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2992,6 +3120,17 @@ data:
|
||||
location.reload();
|
||||
});
|
||||
|
||||
sino.addEventListener("click", function () {
|
||||
var abrindo = sinoPainel.classList.contains("oculto");
|
||||
if (abrindo) { abrirSino(); if (tokenAtual) atualizarSino(tokenAtual); } else { fecharSino(); }
|
||||
});
|
||||
document.addEventListener("click", function (ev) {
|
||||
if (!sinoPainel.classList.contains("oculto") && !sinoCaixa.contains(ev.target)) fecharSino();
|
||||
});
|
||||
document.addEventListener("keydown", function (ev) {
|
||||
if (ev.key === "Escape") fecharSino();
|
||||
});
|
||||
|
||||
botaoAtualizar.addEventListener("click", function () {
|
||||
if (!tokenAtual) return;
|
||||
botaoAtualizar.disabled = true;
|
||||
|
||||
@@ -1981,6 +1981,38 @@ data:
|
||||
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||
.topo .acao:disabled { opacity: .6; cursor: wait; }
|
||||
|
||||
/* ── sino de alertas de infraestrutura ───────────────────────────────────── */
|
||||
.sino-caixa { position: relative; }
|
||||
.sino {
|
||||
background: none; border: 1px solid var(--line); color: var(--navy);
|
||||
border-radius: 10px; padding: 6px 10px; cursor: pointer; font-size: 16px;
|
||||
line-height: 1; position: relative;
|
||||
}
|
||||
.sino:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||
.sino[aria-expanded="true"] { border-color: var(--orange); }
|
||||
.sino-contagem {
|
||||
position: absolute; top: -7px; right: -7px;
|
||||
background: var(--perigo); color: var(--branco); border-radius: 999px;
|
||||
font-size: 11px; font-weight: 700; padding: 1px 6px; line-height: 1.4; min-width: 14px; text-align: center;
|
||||
}
|
||||
.sino-contagem.aviso { background: var(--alerta); }
|
||||
.sino-contagem.sem-dado { background: var(--muted); }
|
||||
.sino-painel {
|
||||
position: absolute; right: 0; top: calc(100% + 10px);
|
||||
width: min(360px, calc(100vw - 32px)); max-height: 60vh; overflow: auto;
|
||||
background: var(--branco); border: 1px solid var(--line); border-radius: 14px;
|
||||
box-shadow: 0 14px 34px rgba(11,44,61,.16); padding: 14px; z-index: 20; text-align: left;
|
||||
}
|
||||
.sino-topo { font-weight: 700; margin-bottom: 10px; }
|
||||
.sino-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
|
||||
.sino-item { border-radius: 10px; padding: 10px 12px; font-size: 13px; }
|
||||
.sino-item.critical { background: #fdecea; }
|
||||
.sino-item.warning { background: #fdf3e6; }
|
||||
.sino-item.info { background: var(--paper); }
|
||||
.sino-item .nome { font-weight: 700; display: block; }
|
||||
.sino-item .resumo { display: block; margin-top: 2px; }
|
||||
.sino-item .quem { color: var(--muted); font-size: 12px; display: block; margin-top: 4px; }
|
||||
|
||||
.wrap { max-width: 1320px; margin: 0 auto; padding: 22px 28px 48px; }
|
||||
|
||||
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
||||
@@ -2226,7 +2258,7 @@ data:
|
||||
`Cache-Control: immutable` por um ano. Suba o numero a cada publicacao.
|
||||
-->
|
||||
<link rel="stylesheet" href="/publico/fontes/fontes.css">
|
||||
<link rel="stylesheet" href="/publico/painel.css?v=2.82">
|
||||
<link rel="stylesheet" href="/publico/painel.css?v=2.83">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -2234,6 +2266,18 @@ data:
|
||||
<img class="logo" src="/publico/img/logo-athletic-map.png" alt="Athletic Map" width="150" height="37">
|
||||
<span class="titulo">Painel do dono</span>
|
||||
<span id="apurado" class="apurado oculto"></span>
|
||||
<div class="sino-caixa oculto" id="sinoCaixa">
|
||||
<button type="button" id="sino" class="sino" aria-haspopup="true" aria-expanded="false" title="Alertas de infraestrutura">
|
||||
<span aria-hidden="true">🔔</span>
|
||||
<span id="sinoContagem" class="sino-contagem oculto"></span>
|
||||
</button>
|
||||
<div id="sinoPainel" class="sino-painel oculto" role="menu">
|
||||
<div class="sino-topo">Alertas de infraestrutura</div>
|
||||
<ul id="sinoLista" class="sino-lista"></ul>
|
||||
<p id="sinoVazio" class="ajuda oculto">Nenhum alerta agora.</p>
|
||||
<p id="sinoIndisponivel" class="ajuda oculto">Não consegui falar com o monitoramento agora.</p>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" id="atualizar" class="acao oculto">Atualizar</button>
|
||||
<button type="button" id="sair" class="sair oculto">Sair</button>
|
||||
</header>
|
||||
@@ -2388,7 +2432,7 @@ data:
|
||||
|
||||
</main>
|
||||
|
||||
<script src="/publico/painel.js?v=2.82"></script>
|
||||
<script src="/publico/painel.js?v=2.83"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2422,16 +2466,22 @@ data:
|
||||
"use strict";
|
||||
|
||||
var API = "/api/plataforma/painel/visao";
|
||||
var API_ALERTAS = "/api/plataforma/painel/alertas-infra";
|
||||
var CHAVE = "atm.painel.token";
|
||||
var INTERVALO_SINO_MS = 60000;
|
||||
|
||||
var $ = function (id) { return document.getElementById(id); };
|
||||
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
||||
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
||||
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
||||
var corpo = $("linhas"), vazio = $("vazio");
|
||||
var sinoCaixa = $("sinoCaixa"), sino = $("sino"), sinoContagem = $("sinoContagem"),
|
||||
sinoPainel = $("sinoPainel"), sinoLista = $("sinoLista"),
|
||||
sinoVazio = $("sinoVazio"), sinoIndisponivel = $("sinoIndisponivel");
|
||||
|
||||
var visao = null;
|
||||
var tokenAtual = null;
|
||||
var sinoIntervalo = null;
|
||||
|
||||
/* ── token ──────────────────────────────────────────────────────────────── */
|
||||
|
||||
@@ -2448,6 +2498,83 @@ data:
|
||||
});
|
||||
}
|
||||
|
||||
/* ── sino de alertas de infraestrutura ─────────────────────────────────────
|
||||
*
|
||||
* Le o mesmo Alertmanager que ja manda e-mail — o sino nao inventa alerta
|
||||
* nenhum, so mostra o que o e-mail ja avisou. Falha calada de proposito:
|
||||
* se o servidor nao responder, o sino so fica "sem dado" — nao pode
|
||||
* derrubar o painel inteiro por causa de um widget auxiliar.
|
||||
*/
|
||||
function buscarAlertasInfra(token) {
|
||||
return fetch(API_ALERTAS, { headers: { "x-atm-admin-token": token } })
|
||||
.then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.json(); })
|
||||
.catch(function () { return null; }); // null = "sem dado", diferente de "zero alertas"
|
||||
}
|
||||
|
||||
function pesoSeveridade(s) { return s === "critical" ? 3 : s === "warning" ? 2 : 1; }
|
||||
|
||||
function pintarSino(dados) {
|
||||
sinoLista.innerHTML = "";
|
||||
sinoVazio.classList.add("oculto");
|
||||
sinoIndisponivel.classList.add("oculto");
|
||||
|
||||
if (!dados || dados.indisponivel) {
|
||||
sinoContagem.textContent = "!";
|
||||
sinoContagem.className = "sino-contagem sem-dado";
|
||||
sinoIndisponivel.classList.remove("oculto");
|
||||
return;
|
||||
}
|
||||
|
||||
var alertas = dados.alertas || [];
|
||||
if (alertas.length === 0) {
|
||||
sinoContagem.classList.add("oculto");
|
||||
sinoVazio.classList.remove("oculto");
|
||||
return;
|
||||
}
|
||||
|
||||
sinoContagem.textContent = String(alertas.length);
|
||||
sinoContagem.className = "sino-contagem" +
|
||||
(alertas.some(function (a) { return a.severidade === "critical"; }) ? "" : " aviso");
|
||||
sinoContagem.classList.remove("oculto");
|
||||
|
||||
alertas.forEach(function (a) {
|
||||
var li = document.createElement("li");
|
||||
var sev = pesoSeveridade(a.severidade) === 3 ? "critical" : pesoSeveridade(a.severidade) === 2 ? "warning" : "info";
|
||||
li.className = "sino-item " + sev;
|
||||
li.innerHTML = "<span class=\"nome\">" + esc(a.nome) + "</span>"
|
||||
+ (a.resumo ? "<span class=\"resumo\">" + esc(a.resumo) + "</span>" : "")
|
||||
+ (a.tenant ? "<span class=\"quem\">" + esc(a.tenant) + (a.desde ? " — " + ha(a.desde) : "") + "</span>"
|
||||
: (a.desde ? "<span class=\"quem\">" + ha(a.desde) + "</span>" : ""));
|
||||
sinoLista.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
function atualizarSino(token) {
|
||||
buscarAlertasInfra(token).then(pintarSino);
|
||||
}
|
||||
|
||||
function ligarSino(token) {
|
||||
sinoCaixa.classList.remove("oculto");
|
||||
atualizarSino(token);
|
||||
if (sinoIntervalo) clearInterval(sinoIntervalo);
|
||||
sinoIntervalo = setInterval(function () { atualizarSino(token); }, INTERVALO_SINO_MS);
|
||||
}
|
||||
|
||||
function desligarSino() {
|
||||
sinoCaixa.classList.add("oculto");
|
||||
fecharSino();
|
||||
if (sinoIntervalo) { clearInterval(sinoIntervalo); sinoIntervalo = null; }
|
||||
}
|
||||
|
||||
function abrirSino() {
|
||||
sinoPainel.classList.remove("oculto");
|
||||
sino.setAttribute("aria-expanded", "true");
|
||||
}
|
||||
function fecharSino() {
|
||||
sinoPainel.classList.add("oculto");
|
||||
sino.setAttribute("aria-expanded", "false");
|
||||
}
|
||||
|
||||
/* ── formatacao ─────────────────────────────────────────────────────────── */
|
||||
|
||||
function esc(s) {
|
||||
@@ -2958,6 +3085,7 @@ data:
|
||||
tokenAtual = token;
|
||||
pintar();
|
||||
mostrar(conteudo);
|
||||
ligarSino(token);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2992,6 +3120,17 @@ data:
|
||||
location.reload();
|
||||
});
|
||||
|
||||
sino.addEventListener("click", function () {
|
||||
var abrindo = sinoPainel.classList.contains("oculto");
|
||||
if (abrindo) { abrirSino(); if (tokenAtual) atualizarSino(tokenAtual); } else { fecharSino(); }
|
||||
});
|
||||
document.addEventListener("click", function (ev) {
|
||||
if (!sinoPainel.classList.contains("oculto") && !sinoCaixa.contains(ev.target)) fecharSino();
|
||||
});
|
||||
document.addEventListener("keydown", function (ev) {
|
||||
if (ev.key === "Escape") fecharSino();
|
||||
});
|
||||
|
||||
botaoAtualizar.addEventListener("click", function () {
|
||||
if (!tokenAtual) return;
|
||||
botaoAtualizar.disabled = true;
|
||||
|
||||
@@ -1981,6 +1981,38 @@ data:
|
||||
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||
.topo .acao:disabled { opacity: .6; cursor: wait; }
|
||||
|
||||
/* ── sino de alertas de infraestrutura ───────────────────────────────────── */
|
||||
.sino-caixa { position: relative; }
|
||||
.sino {
|
||||
background: none; border: 1px solid var(--line); color: var(--navy);
|
||||
border-radius: 10px; padding: 6px 10px; cursor: pointer; font-size: 16px;
|
||||
line-height: 1; position: relative;
|
||||
}
|
||||
.sino:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||
.sino[aria-expanded="true"] { border-color: var(--orange); }
|
||||
.sino-contagem {
|
||||
position: absolute; top: -7px; right: -7px;
|
||||
background: var(--perigo); color: var(--branco); border-radius: 999px;
|
||||
font-size: 11px; font-weight: 700; padding: 1px 6px; line-height: 1.4; min-width: 14px; text-align: center;
|
||||
}
|
||||
.sino-contagem.aviso { background: var(--alerta); }
|
||||
.sino-contagem.sem-dado { background: var(--muted); }
|
||||
.sino-painel {
|
||||
position: absolute; right: 0; top: calc(100% + 10px);
|
||||
width: min(360px, calc(100vw - 32px)); max-height: 60vh; overflow: auto;
|
||||
background: var(--branco); border: 1px solid var(--line); border-radius: 14px;
|
||||
box-shadow: 0 14px 34px rgba(11,44,61,.16); padding: 14px; z-index: 20; text-align: left;
|
||||
}
|
||||
.sino-topo { font-weight: 700; margin-bottom: 10px; }
|
||||
.sino-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
|
||||
.sino-item { border-radius: 10px; padding: 10px 12px; font-size: 13px; }
|
||||
.sino-item.critical { background: #fdecea; }
|
||||
.sino-item.warning { background: #fdf3e6; }
|
||||
.sino-item.info { background: var(--paper); }
|
||||
.sino-item .nome { font-weight: 700; display: block; }
|
||||
.sino-item .resumo { display: block; margin-top: 2px; }
|
||||
.sino-item .quem { color: var(--muted); font-size: 12px; display: block; margin-top: 4px; }
|
||||
|
||||
.wrap { max-width: 1320px; margin: 0 auto; padding: 22px 28px 48px; }
|
||||
|
||||
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
||||
@@ -2226,7 +2258,7 @@ data:
|
||||
`Cache-Control: immutable` por um ano. Suba o numero a cada publicacao.
|
||||
-->
|
||||
<link rel="stylesheet" href="/publico/fontes/fontes.css">
|
||||
<link rel="stylesheet" href="/publico/painel.css?v=2.82">
|
||||
<link rel="stylesheet" href="/publico/painel.css?v=2.83">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -2234,6 +2266,18 @@ data:
|
||||
<img class="logo" src="/publico/img/logo-athletic-map.png" alt="Athletic Map" width="150" height="37">
|
||||
<span class="titulo">Painel do dono</span>
|
||||
<span id="apurado" class="apurado oculto"></span>
|
||||
<div class="sino-caixa oculto" id="sinoCaixa">
|
||||
<button type="button" id="sino" class="sino" aria-haspopup="true" aria-expanded="false" title="Alertas de infraestrutura">
|
||||
<span aria-hidden="true">🔔</span>
|
||||
<span id="sinoContagem" class="sino-contagem oculto"></span>
|
||||
</button>
|
||||
<div id="sinoPainel" class="sino-painel oculto" role="menu">
|
||||
<div class="sino-topo">Alertas de infraestrutura</div>
|
||||
<ul id="sinoLista" class="sino-lista"></ul>
|
||||
<p id="sinoVazio" class="ajuda oculto">Nenhum alerta agora.</p>
|
||||
<p id="sinoIndisponivel" class="ajuda oculto">Não consegui falar com o monitoramento agora.</p>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" id="atualizar" class="acao oculto">Atualizar</button>
|
||||
<button type="button" id="sair" class="sair oculto">Sair</button>
|
||||
</header>
|
||||
@@ -2388,7 +2432,7 @@ data:
|
||||
|
||||
</main>
|
||||
|
||||
<script src="/publico/painel.js?v=2.82"></script>
|
||||
<script src="/publico/painel.js?v=2.83"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2422,16 +2466,22 @@ data:
|
||||
"use strict";
|
||||
|
||||
var API = "/api/plataforma/painel/visao";
|
||||
var API_ALERTAS = "/api/plataforma/painel/alertas-infra";
|
||||
var CHAVE = "atm.painel.token";
|
||||
var INTERVALO_SINO_MS = 60000;
|
||||
|
||||
var $ = function (id) { return document.getElementById(id); };
|
||||
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
||||
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
||||
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
||||
var corpo = $("linhas"), vazio = $("vazio");
|
||||
var sinoCaixa = $("sinoCaixa"), sino = $("sino"), sinoContagem = $("sinoContagem"),
|
||||
sinoPainel = $("sinoPainel"), sinoLista = $("sinoLista"),
|
||||
sinoVazio = $("sinoVazio"), sinoIndisponivel = $("sinoIndisponivel");
|
||||
|
||||
var visao = null;
|
||||
var tokenAtual = null;
|
||||
var sinoIntervalo = null;
|
||||
|
||||
/* ── token ──────────────────────────────────────────────────────────────── */
|
||||
|
||||
@@ -2448,6 +2498,83 @@ data:
|
||||
});
|
||||
}
|
||||
|
||||
/* ── sino de alertas de infraestrutura ─────────────────────────────────────
|
||||
*
|
||||
* Le o mesmo Alertmanager que ja manda e-mail — o sino nao inventa alerta
|
||||
* nenhum, so mostra o que o e-mail ja avisou. Falha calada de proposito:
|
||||
* se o servidor nao responder, o sino so fica "sem dado" — nao pode
|
||||
* derrubar o painel inteiro por causa de um widget auxiliar.
|
||||
*/
|
||||
function buscarAlertasInfra(token) {
|
||||
return fetch(API_ALERTAS, { headers: { "x-atm-admin-token": token } })
|
||||
.then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.json(); })
|
||||
.catch(function () { return null; }); // null = "sem dado", diferente de "zero alertas"
|
||||
}
|
||||
|
||||
function pesoSeveridade(s) { return s === "critical" ? 3 : s === "warning" ? 2 : 1; }
|
||||
|
||||
function pintarSino(dados) {
|
||||
sinoLista.innerHTML = "";
|
||||
sinoVazio.classList.add("oculto");
|
||||
sinoIndisponivel.classList.add("oculto");
|
||||
|
||||
if (!dados || dados.indisponivel) {
|
||||
sinoContagem.textContent = "!";
|
||||
sinoContagem.className = "sino-contagem sem-dado";
|
||||
sinoIndisponivel.classList.remove("oculto");
|
||||
return;
|
||||
}
|
||||
|
||||
var alertas = dados.alertas || [];
|
||||
if (alertas.length === 0) {
|
||||
sinoContagem.classList.add("oculto");
|
||||
sinoVazio.classList.remove("oculto");
|
||||
return;
|
||||
}
|
||||
|
||||
sinoContagem.textContent = String(alertas.length);
|
||||
sinoContagem.className = "sino-contagem" +
|
||||
(alertas.some(function (a) { return a.severidade === "critical"; }) ? "" : " aviso");
|
||||
sinoContagem.classList.remove("oculto");
|
||||
|
||||
alertas.forEach(function (a) {
|
||||
var li = document.createElement("li");
|
||||
var sev = pesoSeveridade(a.severidade) === 3 ? "critical" : pesoSeveridade(a.severidade) === 2 ? "warning" : "info";
|
||||
li.className = "sino-item " + sev;
|
||||
li.innerHTML = "<span class=\"nome\">" + esc(a.nome) + "</span>"
|
||||
+ (a.resumo ? "<span class=\"resumo\">" + esc(a.resumo) + "</span>" : "")
|
||||
+ (a.tenant ? "<span class=\"quem\">" + esc(a.tenant) + (a.desde ? " — " + ha(a.desde) : "") + "</span>"
|
||||
: (a.desde ? "<span class=\"quem\">" + ha(a.desde) + "</span>" : ""));
|
||||
sinoLista.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
function atualizarSino(token) {
|
||||
buscarAlertasInfra(token).then(pintarSino);
|
||||
}
|
||||
|
||||
function ligarSino(token) {
|
||||
sinoCaixa.classList.remove("oculto");
|
||||
atualizarSino(token);
|
||||
if (sinoIntervalo) clearInterval(sinoIntervalo);
|
||||
sinoIntervalo = setInterval(function () { atualizarSino(token); }, INTERVALO_SINO_MS);
|
||||
}
|
||||
|
||||
function desligarSino() {
|
||||
sinoCaixa.classList.add("oculto");
|
||||
fecharSino();
|
||||
if (sinoIntervalo) { clearInterval(sinoIntervalo); sinoIntervalo = null; }
|
||||
}
|
||||
|
||||
function abrirSino() {
|
||||
sinoPainel.classList.remove("oculto");
|
||||
sino.setAttribute("aria-expanded", "true");
|
||||
}
|
||||
function fecharSino() {
|
||||
sinoPainel.classList.add("oculto");
|
||||
sino.setAttribute("aria-expanded", "false");
|
||||
}
|
||||
|
||||
/* ── formatacao ─────────────────────────────────────────────────────────── */
|
||||
|
||||
function esc(s) {
|
||||
@@ -2958,6 +3085,7 @@ data:
|
||||
tokenAtual = token;
|
||||
pintar();
|
||||
mostrar(conteudo);
|
||||
ligarSino(token);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2992,6 +3120,17 @@ data:
|
||||
location.reload();
|
||||
});
|
||||
|
||||
sino.addEventListener("click", function () {
|
||||
var abrindo = sinoPainel.classList.contains("oculto");
|
||||
if (abrindo) { abrirSino(); if (tokenAtual) atualizarSino(tokenAtual); } else { fecharSino(); }
|
||||
});
|
||||
document.addEventListener("click", function (ev) {
|
||||
if (!sinoPainel.classList.contains("oculto") && !sinoCaixa.contains(ev.target)) fecharSino();
|
||||
});
|
||||
document.addEventListener("keydown", function (ev) {
|
||||
if (ev.key === "Escape") fecharSino();
|
||||
});
|
||||
|
||||
botaoAtualizar.addEventListener("click", function () {
|
||||
if (!tokenAtual) return;
|
||||
botaoAtualizar.disabled = true;
|
||||
|
||||
@@ -1981,6 +1981,38 @@ data:
|
||||
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||
.topo .acao:disabled { opacity: .6; cursor: wait; }
|
||||
|
||||
/* ── sino de alertas de infraestrutura ───────────────────────────────────── */
|
||||
.sino-caixa { position: relative; }
|
||||
.sino {
|
||||
background: none; border: 1px solid var(--line); color: var(--navy);
|
||||
border-radius: 10px; padding: 6px 10px; cursor: pointer; font-size: 16px;
|
||||
line-height: 1; position: relative;
|
||||
}
|
||||
.sino:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||
.sino[aria-expanded="true"] { border-color: var(--orange); }
|
||||
.sino-contagem {
|
||||
position: absolute; top: -7px; right: -7px;
|
||||
background: var(--perigo); color: var(--branco); border-radius: 999px;
|
||||
font-size: 11px; font-weight: 700; padding: 1px 6px; line-height: 1.4; min-width: 14px; text-align: center;
|
||||
}
|
||||
.sino-contagem.aviso { background: var(--alerta); }
|
||||
.sino-contagem.sem-dado { background: var(--muted); }
|
||||
.sino-painel {
|
||||
position: absolute; right: 0; top: calc(100% + 10px);
|
||||
width: min(360px, calc(100vw - 32px)); max-height: 60vh; overflow: auto;
|
||||
background: var(--branco); border: 1px solid var(--line); border-radius: 14px;
|
||||
box-shadow: 0 14px 34px rgba(11,44,61,.16); padding: 14px; z-index: 20; text-align: left;
|
||||
}
|
||||
.sino-topo { font-weight: 700; margin-bottom: 10px; }
|
||||
.sino-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
|
||||
.sino-item { border-radius: 10px; padding: 10px 12px; font-size: 13px; }
|
||||
.sino-item.critical { background: #fdecea; }
|
||||
.sino-item.warning { background: #fdf3e6; }
|
||||
.sino-item.info { background: var(--paper); }
|
||||
.sino-item .nome { font-weight: 700; display: block; }
|
||||
.sino-item .resumo { display: block; margin-top: 2px; }
|
||||
.sino-item .quem { color: var(--muted); font-size: 12px; display: block; margin-top: 4px; }
|
||||
|
||||
.wrap { max-width: 1320px; margin: 0 auto; padding: 22px 28px 48px; }
|
||||
|
||||
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
||||
@@ -2226,7 +2258,7 @@ data:
|
||||
`Cache-Control: immutable` por um ano. Suba o numero a cada publicacao.
|
||||
-->
|
||||
<link rel="stylesheet" href="/publico/fontes/fontes.css">
|
||||
<link rel="stylesheet" href="/publico/painel.css?v=2.82">
|
||||
<link rel="stylesheet" href="/publico/painel.css?v=2.83">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -2234,6 +2266,18 @@ data:
|
||||
<img class="logo" src="/publico/img/logo-athletic-map.png" alt="Athletic Map" width="150" height="37">
|
||||
<span class="titulo">Painel do dono</span>
|
||||
<span id="apurado" class="apurado oculto"></span>
|
||||
<div class="sino-caixa oculto" id="sinoCaixa">
|
||||
<button type="button" id="sino" class="sino" aria-haspopup="true" aria-expanded="false" title="Alertas de infraestrutura">
|
||||
<span aria-hidden="true">🔔</span>
|
||||
<span id="sinoContagem" class="sino-contagem oculto"></span>
|
||||
</button>
|
||||
<div id="sinoPainel" class="sino-painel oculto" role="menu">
|
||||
<div class="sino-topo">Alertas de infraestrutura</div>
|
||||
<ul id="sinoLista" class="sino-lista"></ul>
|
||||
<p id="sinoVazio" class="ajuda oculto">Nenhum alerta agora.</p>
|
||||
<p id="sinoIndisponivel" class="ajuda oculto">Não consegui falar com o monitoramento agora.</p>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" id="atualizar" class="acao oculto">Atualizar</button>
|
||||
<button type="button" id="sair" class="sair oculto">Sair</button>
|
||||
</header>
|
||||
@@ -2388,7 +2432,7 @@ data:
|
||||
|
||||
</main>
|
||||
|
||||
<script src="/publico/painel.js?v=2.82"></script>
|
||||
<script src="/publico/painel.js?v=2.83"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2422,16 +2466,22 @@ data:
|
||||
"use strict";
|
||||
|
||||
var API = "/api/plataforma/painel/visao";
|
||||
var API_ALERTAS = "/api/plataforma/painel/alertas-infra";
|
||||
var CHAVE = "atm.painel.token";
|
||||
var INTERVALO_SINO_MS = 60000;
|
||||
|
||||
var $ = function (id) { return document.getElementById(id); };
|
||||
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
||||
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
||||
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
||||
var corpo = $("linhas"), vazio = $("vazio");
|
||||
var sinoCaixa = $("sinoCaixa"), sino = $("sino"), sinoContagem = $("sinoContagem"),
|
||||
sinoPainel = $("sinoPainel"), sinoLista = $("sinoLista"),
|
||||
sinoVazio = $("sinoVazio"), sinoIndisponivel = $("sinoIndisponivel");
|
||||
|
||||
var visao = null;
|
||||
var tokenAtual = null;
|
||||
var sinoIntervalo = null;
|
||||
|
||||
/* ── token ──────────────────────────────────────────────────────────────── */
|
||||
|
||||
@@ -2448,6 +2498,83 @@ data:
|
||||
});
|
||||
}
|
||||
|
||||
/* ── sino de alertas de infraestrutura ─────────────────────────────────────
|
||||
*
|
||||
* Le o mesmo Alertmanager que ja manda e-mail — o sino nao inventa alerta
|
||||
* nenhum, so mostra o que o e-mail ja avisou. Falha calada de proposito:
|
||||
* se o servidor nao responder, o sino so fica "sem dado" — nao pode
|
||||
* derrubar o painel inteiro por causa de um widget auxiliar.
|
||||
*/
|
||||
function buscarAlertasInfra(token) {
|
||||
return fetch(API_ALERTAS, { headers: { "x-atm-admin-token": token } })
|
||||
.then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.json(); })
|
||||
.catch(function () { return null; }); // null = "sem dado", diferente de "zero alertas"
|
||||
}
|
||||
|
||||
function pesoSeveridade(s) { return s === "critical" ? 3 : s === "warning" ? 2 : 1; }
|
||||
|
||||
function pintarSino(dados) {
|
||||
sinoLista.innerHTML = "";
|
||||
sinoVazio.classList.add("oculto");
|
||||
sinoIndisponivel.classList.add("oculto");
|
||||
|
||||
if (!dados || dados.indisponivel) {
|
||||
sinoContagem.textContent = "!";
|
||||
sinoContagem.className = "sino-contagem sem-dado";
|
||||
sinoIndisponivel.classList.remove("oculto");
|
||||
return;
|
||||
}
|
||||
|
||||
var alertas = dados.alertas || [];
|
||||
if (alertas.length === 0) {
|
||||
sinoContagem.classList.add("oculto");
|
||||
sinoVazio.classList.remove("oculto");
|
||||
return;
|
||||
}
|
||||
|
||||
sinoContagem.textContent = String(alertas.length);
|
||||
sinoContagem.className = "sino-contagem" +
|
||||
(alertas.some(function (a) { return a.severidade === "critical"; }) ? "" : " aviso");
|
||||
sinoContagem.classList.remove("oculto");
|
||||
|
||||
alertas.forEach(function (a) {
|
||||
var li = document.createElement("li");
|
||||
var sev = pesoSeveridade(a.severidade) === 3 ? "critical" : pesoSeveridade(a.severidade) === 2 ? "warning" : "info";
|
||||
li.className = "sino-item " + sev;
|
||||
li.innerHTML = "<span class=\"nome\">" + esc(a.nome) + "</span>"
|
||||
+ (a.resumo ? "<span class=\"resumo\">" + esc(a.resumo) + "</span>" : "")
|
||||
+ (a.tenant ? "<span class=\"quem\">" + esc(a.tenant) + (a.desde ? " — " + ha(a.desde) : "") + "</span>"
|
||||
: (a.desde ? "<span class=\"quem\">" + ha(a.desde) + "</span>" : ""));
|
||||
sinoLista.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
function atualizarSino(token) {
|
||||
buscarAlertasInfra(token).then(pintarSino);
|
||||
}
|
||||
|
||||
function ligarSino(token) {
|
||||
sinoCaixa.classList.remove("oculto");
|
||||
atualizarSino(token);
|
||||
if (sinoIntervalo) clearInterval(sinoIntervalo);
|
||||
sinoIntervalo = setInterval(function () { atualizarSino(token); }, INTERVALO_SINO_MS);
|
||||
}
|
||||
|
||||
function desligarSino() {
|
||||
sinoCaixa.classList.add("oculto");
|
||||
fecharSino();
|
||||
if (sinoIntervalo) { clearInterval(sinoIntervalo); sinoIntervalo = null; }
|
||||
}
|
||||
|
||||
function abrirSino() {
|
||||
sinoPainel.classList.remove("oculto");
|
||||
sino.setAttribute("aria-expanded", "true");
|
||||
}
|
||||
function fecharSino() {
|
||||
sinoPainel.classList.add("oculto");
|
||||
sino.setAttribute("aria-expanded", "false");
|
||||
}
|
||||
|
||||
/* ── formatacao ─────────────────────────────────────────────────────────── */
|
||||
|
||||
function esc(s) {
|
||||
@@ -2958,6 +3085,7 @@ data:
|
||||
tokenAtual = token;
|
||||
pintar();
|
||||
mostrar(conteudo);
|
||||
ligarSino(token);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2992,6 +3120,17 @@ data:
|
||||
location.reload();
|
||||
});
|
||||
|
||||
sino.addEventListener("click", function () {
|
||||
var abrindo = sinoPainel.classList.contains("oculto");
|
||||
if (abrindo) { abrirSino(); if (tokenAtual) atualizarSino(tokenAtual); } else { fecharSino(); }
|
||||
});
|
||||
document.addEventListener("click", function (ev) {
|
||||
if (!sinoPainel.classList.contains("oculto") && !sinoCaixa.contains(ev.target)) fecharSino();
|
||||
});
|
||||
document.addEventListener("keydown", function (ev) {
|
||||
if (ev.key === "Escape") fecharSino();
|
||||
});
|
||||
|
||||
botaoAtualizar.addEventListener("click", function () {
|
||||
if (!tokenAtual) return;
|
||||
botaoAtualizar.disabled = true;
|
||||
|
||||
@@ -1981,6 +1981,38 @@ data:
|
||||
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||
.topo .acao:disabled { opacity: .6; cursor: wait; }
|
||||
|
||||
/* ── sino de alertas de infraestrutura ───────────────────────────────────── */
|
||||
.sino-caixa { position: relative; }
|
||||
.sino {
|
||||
background: none; border: 1px solid var(--line); color: var(--navy);
|
||||
border-radius: 10px; padding: 6px 10px; cursor: pointer; font-size: 16px;
|
||||
line-height: 1; position: relative;
|
||||
}
|
||||
.sino:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||
.sino[aria-expanded="true"] { border-color: var(--orange); }
|
||||
.sino-contagem {
|
||||
position: absolute; top: -7px; right: -7px;
|
||||
background: var(--perigo); color: var(--branco); border-radius: 999px;
|
||||
font-size: 11px; font-weight: 700; padding: 1px 6px; line-height: 1.4; min-width: 14px; text-align: center;
|
||||
}
|
||||
.sino-contagem.aviso { background: var(--alerta); }
|
||||
.sino-contagem.sem-dado { background: var(--muted); }
|
||||
.sino-painel {
|
||||
position: absolute; right: 0; top: calc(100% + 10px);
|
||||
width: min(360px, calc(100vw - 32px)); max-height: 60vh; overflow: auto;
|
||||
background: var(--branco); border: 1px solid var(--line); border-radius: 14px;
|
||||
box-shadow: 0 14px 34px rgba(11,44,61,.16); padding: 14px; z-index: 20; text-align: left;
|
||||
}
|
||||
.sino-topo { font-weight: 700; margin-bottom: 10px; }
|
||||
.sino-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
|
||||
.sino-item { border-radius: 10px; padding: 10px 12px; font-size: 13px; }
|
||||
.sino-item.critical { background: #fdecea; }
|
||||
.sino-item.warning { background: #fdf3e6; }
|
||||
.sino-item.info { background: var(--paper); }
|
||||
.sino-item .nome { font-weight: 700; display: block; }
|
||||
.sino-item .resumo { display: block; margin-top: 2px; }
|
||||
.sino-item .quem { color: var(--muted); font-size: 12px; display: block; margin-top: 4px; }
|
||||
|
||||
.wrap { max-width: 1320px; margin: 0 auto; padding: 22px 28px 48px; }
|
||||
|
||||
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
||||
@@ -2226,7 +2258,7 @@ data:
|
||||
`Cache-Control: immutable` por um ano. Suba o numero a cada publicacao.
|
||||
-->
|
||||
<link rel="stylesheet" href="/publico/fontes/fontes.css">
|
||||
<link rel="stylesheet" href="/publico/painel.css?v=2.82">
|
||||
<link rel="stylesheet" href="/publico/painel.css?v=2.83">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -2234,6 +2266,18 @@ data:
|
||||
<img class="logo" src="/publico/img/logo-athletic-map.png" alt="Athletic Map" width="150" height="37">
|
||||
<span class="titulo">Painel do dono</span>
|
||||
<span id="apurado" class="apurado oculto"></span>
|
||||
<div class="sino-caixa oculto" id="sinoCaixa">
|
||||
<button type="button" id="sino" class="sino" aria-haspopup="true" aria-expanded="false" title="Alertas de infraestrutura">
|
||||
<span aria-hidden="true">🔔</span>
|
||||
<span id="sinoContagem" class="sino-contagem oculto"></span>
|
||||
</button>
|
||||
<div id="sinoPainel" class="sino-painel oculto" role="menu">
|
||||
<div class="sino-topo">Alertas de infraestrutura</div>
|
||||
<ul id="sinoLista" class="sino-lista"></ul>
|
||||
<p id="sinoVazio" class="ajuda oculto">Nenhum alerta agora.</p>
|
||||
<p id="sinoIndisponivel" class="ajuda oculto">Não consegui falar com o monitoramento agora.</p>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" id="atualizar" class="acao oculto">Atualizar</button>
|
||||
<button type="button" id="sair" class="sair oculto">Sair</button>
|
||||
</header>
|
||||
@@ -2388,7 +2432,7 @@ data:
|
||||
|
||||
</main>
|
||||
|
||||
<script src="/publico/painel.js?v=2.82"></script>
|
||||
<script src="/publico/painel.js?v=2.83"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2422,16 +2466,22 @@ data:
|
||||
"use strict";
|
||||
|
||||
var API = "/api/plataforma/painel/visao";
|
||||
var API_ALERTAS = "/api/plataforma/painel/alertas-infra";
|
||||
var CHAVE = "atm.painel.token";
|
||||
var INTERVALO_SINO_MS = 60000;
|
||||
|
||||
var $ = function (id) { return document.getElementById(id); };
|
||||
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
||||
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
||||
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
||||
var corpo = $("linhas"), vazio = $("vazio");
|
||||
var sinoCaixa = $("sinoCaixa"), sino = $("sino"), sinoContagem = $("sinoContagem"),
|
||||
sinoPainel = $("sinoPainel"), sinoLista = $("sinoLista"),
|
||||
sinoVazio = $("sinoVazio"), sinoIndisponivel = $("sinoIndisponivel");
|
||||
|
||||
var visao = null;
|
||||
var tokenAtual = null;
|
||||
var sinoIntervalo = null;
|
||||
|
||||
/* ── token ──────────────────────────────────────────────────────────────── */
|
||||
|
||||
@@ -2448,6 +2498,83 @@ data:
|
||||
});
|
||||
}
|
||||
|
||||
/* ── sino de alertas de infraestrutura ─────────────────────────────────────
|
||||
*
|
||||
* Le o mesmo Alertmanager que ja manda e-mail — o sino nao inventa alerta
|
||||
* nenhum, so mostra o que o e-mail ja avisou. Falha calada de proposito:
|
||||
* se o servidor nao responder, o sino so fica "sem dado" — nao pode
|
||||
* derrubar o painel inteiro por causa de um widget auxiliar.
|
||||
*/
|
||||
function buscarAlertasInfra(token) {
|
||||
return fetch(API_ALERTAS, { headers: { "x-atm-admin-token": token } })
|
||||
.then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.json(); })
|
||||
.catch(function () { return null; }); // null = "sem dado", diferente de "zero alertas"
|
||||
}
|
||||
|
||||
function pesoSeveridade(s) { return s === "critical" ? 3 : s === "warning" ? 2 : 1; }
|
||||
|
||||
function pintarSino(dados) {
|
||||
sinoLista.innerHTML = "";
|
||||
sinoVazio.classList.add("oculto");
|
||||
sinoIndisponivel.classList.add("oculto");
|
||||
|
||||
if (!dados || dados.indisponivel) {
|
||||
sinoContagem.textContent = "!";
|
||||
sinoContagem.className = "sino-contagem sem-dado";
|
||||
sinoIndisponivel.classList.remove("oculto");
|
||||
return;
|
||||
}
|
||||
|
||||
var alertas = dados.alertas || [];
|
||||
if (alertas.length === 0) {
|
||||
sinoContagem.classList.add("oculto");
|
||||
sinoVazio.classList.remove("oculto");
|
||||
return;
|
||||
}
|
||||
|
||||
sinoContagem.textContent = String(alertas.length);
|
||||
sinoContagem.className = "sino-contagem" +
|
||||
(alertas.some(function (a) { return a.severidade === "critical"; }) ? "" : " aviso");
|
||||
sinoContagem.classList.remove("oculto");
|
||||
|
||||
alertas.forEach(function (a) {
|
||||
var li = document.createElement("li");
|
||||
var sev = pesoSeveridade(a.severidade) === 3 ? "critical" : pesoSeveridade(a.severidade) === 2 ? "warning" : "info";
|
||||
li.className = "sino-item " + sev;
|
||||
li.innerHTML = "<span class=\"nome\">" + esc(a.nome) + "</span>"
|
||||
+ (a.resumo ? "<span class=\"resumo\">" + esc(a.resumo) + "</span>" : "")
|
||||
+ (a.tenant ? "<span class=\"quem\">" + esc(a.tenant) + (a.desde ? " — " + ha(a.desde) : "") + "</span>"
|
||||
: (a.desde ? "<span class=\"quem\">" + ha(a.desde) + "</span>" : ""));
|
||||
sinoLista.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
function atualizarSino(token) {
|
||||
buscarAlertasInfra(token).then(pintarSino);
|
||||
}
|
||||
|
||||
function ligarSino(token) {
|
||||
sinoCaixa.classList.remove("oculto");
|
||||
atualizarSino(token);
|
||||
if (sinoIntervalo) clearInterval(sinoIntervalo);
|
||||
sinoIntervalo = setInterval(function () { atualizarSino(token); }, INTERVALO_SINO_MS);
|
||||
}
|
||||
|
||||
function desligarSino() {
|
||||
sinoCaixa.classList.add("oculto");
|
||||
fecharSino();
|
||||
if (sinoIntervalo) { clearInterval(sinoIntervalo); sinoIntervalo = null; }
|
||||
}
|
||||
|
||||
function abrirSino() {
|
||||
sinoPainel.classList.remove("oculto");
|
||||
sino.setAttribute("aria-expanded", "true");
|
||||
}
|
||||
function fecharSino() {
|
||||
sinoPainel.classList.add("oculto");
|
||||
sino.setAttribute("aria-expanded", "false");
|
||||
}
|
||||
|
||||
/* ── formatacao ─────────────────────────────────────────────────────────── */
|
||||
|
||||
function esc(s) {
|
||||
@@ -2958,6 +3085,7 @@ data:
|
||||
tokenAtual = token;
|
||||
pintar();
|
||||
mostrar(conteudo);
|
||||
ligarSino(token);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2992,6 +3120,17 @@ data:
|
||||
location.reload();
|
||||
});
|
||||
|
||||
sino.addEventListener("click", function () {
|
||||
var abrindo = sinoPainel.classList.contains("oculto");
|
||||
if (abrindo) { abrirSino(); if (tokenAtual) atualizarSino(tokenAtual); } else { fecharSino(); }
|
||||
});
|
||||
document.addEventListener("click", function (ev) {
|
||||
if (!sinoPainel.classList.contains("oculto") && !sinoCaixa.contains(ev.target)) fecharSino();
|
||||
});
|
||||
document.addEventListener("keydown", function (ev) {
|
||||
if (ev.key === "Escape") fecharSino();
|
||||
});
|
||||
|
||||
botaoAtualizar.addEventListener("click", function () {
|
||||
if (!tokenAtual) return;
|
||||
botaoAtualizar.disabled = true;
|
||||
|
||||
@@ -1981,6 +1981,38 @@ data:
|
||||
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||
.topo .acao:disabled { opacity: .6; cursor: wait; }
|
||||
|
||||
/* ── sino de alertas de infraestrutura ───────────────────────────────────── */
|
||||
.sino-caixa { position: relative; }
|
||||
.sino {
|
||||
background: none; border: 1px solid var(--line); color: var(--navy);
|
||||
border-radius: 10px; padding: 6px 10px; cursor: pointer; font-size: 16px;
|
||||
line-height: 1; position: relative;
|
||||
}
|
||||
.sino:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||
.sino[aria-expanded="true"] { border-color: var(--orange); }
|
||||
.sino-contagem {
|
||||
position: absolute; top: -7px; right: -7px;
|
||||
background: var(--perigo); color: var(--branco); border-radius: 999px;
|
||||
font-size: 11px; font-weight: 700; padding: 1px 6px; line-height: 1.4; min-width: 14px; text-align: center;
|
||||
}
|
||||
.sino-contagem.aviso { background: var(--alerta); }
|
||||
.sino-contagem.sem-dado { background: var(--muted); }
|
||||
.sino-painel {
|
||||
position: absolute; right: 0; top: calc(100% + 10px);
|
||||
width: min(360px, calc(100vw - 32px)); max-height: 60vh; overflow: auto;
|
||||
background: var(--branco); border: 1px solid var(--line); border-radius: 14px;
|
||||
box-shadow: 0 14px 34px rgba(11,44,61,.16); padding: 14px; z-index: 20; text-align: left;
|
||||
}
|
||||
.sino-topo { font-weight: 700; margin-bottom: 10px; }
|
||||
.sino-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
|
||||
.sino-item { border-radius: 10px; padding: 10px 12px; font-size: 13px; }
|
||||
.sino-item.critical { background: #fdecea; }
|
||||
.sino-item.warning { background: #fdf3e6; }
|
||||
.sino-item.info { background: var(--paper); }
|
||||
.sino-item .nome { font-weight: 700; display: block; }
|
||||
.sino-item .resumo { display: block; margin-top: 2px; }
|
||||
.sino-item .quem { color: var(--muted); font-size: 12px; display: block; margin-top: 4px; }
|
||||
|
||||
.wrap { max-width: 1320px; margin: 0 auto; padding: 22px 28px 48px; }
|
||||
|
||||
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
||||
@@ -2226,7 +2258,7 @@ data:
|
||||
`Cache-Control: immutable` por um ano. Suba o numero a cada publicacao.
|
||||
-->
|
||||
<link rel="stylesheet" href="/publico/fontes/fontes.css">
|
||||
<link rel="stylesheet" href="/publico/painel.css?v=2.82">
|
||||
<link rel="stylesheet" href="/publico/painel.css?v=2.83">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -2234,6 +2266,18 @@ data:
|
||||
<img class="logo" src="/publico/img/logo-athletic-map.png" alt="Athletic Map" width="150" height="37">
|
||||
<span class="titulo">Painel do dono</span>
|
||||
<span id="apurado" class="apurado oculto"></span>
|
||||
<div class="sino-caixa oculto" id="sinoCaixa">
|
||||
<button type="button" id="sino" class="sino" aria-haspopup="true" aria-expanded="false" title="Alertas de infraestrutura">
|
||||
<span aria-hidden="true">🔔</span>
|
||||
<span id="sinoContagem" class="sino-contagem oculto"></span>
|
||||
</button>
|
||||
<div id="sinoPainel" class="sino-painel oculto" role="menu">
|
||||
<div class="sino-topo">Alertas de infraestrutura</div>
|
||||
<ul id="sinoLista" class="sino-lista"></ul>
|
||||
<p id="sinoVazio" class="ajuda oculto">Nenhum alerta agora.</p>
|
||||
<p id="sinoIndisponivel" class="ajuda oculto">Não consegui falar com o monitoramento agora.</p>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" id="atualizar" class="acao oculto">Atualizar</button>
|
||||
<button type="button" id="sair" class="sair oculto">Sair</button>
|
||||
</header>
|
||||
@@ -2388,7 +2432,7 @@ data:
|
||||
|
||||
</main>
|
||||
|
||||
<script src="/publico/painel.js?v=2.82"></script>
|
||||
<script src="/publico/painel.js?v=2.83"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2422,16 +2466,22 @@ data:
|
||||
"use strict";
|
||||
|
||||
var API = "/api/plataforma/painel/visao";
|
||||
var API_ALERTAS = "/api/plataforma/painel/alertas-infra";
|
||||
var CHAVE = "atm.painel.token";
|
||||
var INTERVALO_SINO_MS = 60000;
|
||||
|
||||
var $ = function (id) { return document.getElementById(id); };
|
||||
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
||||
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
||||
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
||||
var corpo = $("linhas"), vazio = $("vazio");
|
||||
var sinoCaixa = $("sinoCaixa"), sino = $("sino"), sinoContagem = $("sinoContagem"),
|
||||
sinoPainel = $("sinoPainel"), sinoLista = $("sinoLista"),
|
||||
sinoVazio = $("sinoVazio"), sinoIndisponivel = $("sinoIndisponivel");
|
||||
|
||||
var visao = null;
|
||||
var tokenAtual = null;
|
||||
var sinoIntervalo = null;
|
||||
|
||||
/* ── token ──────────────────────────────────────────────────────────────── */
|
||||
|
||||
@@ -2448,6 +2498,83 @@ data:
|
||||
});
|
||||
}
|
||||
|
||||
/* ── sino de alertas de infraestrutura ─────────────────────────────────────
|
||||
*
|
||||
* Le o mesmo Alertmanager que ja manda e-mail — o sino nao inventa alerta
|
||||
* nenhum, so mostra o que o e-mail ja avisou. Falha calada de proposito:
|
||||
* se o servidor nao responder, o sino so fica "sem dado" — nao pode
|
||||
* derrubar o painel inteiro por causa de um widget auxiliar.
|
||||
*/
|
||||
function buscarAlertasInfra(token) {
|
||||
return fetch(API_ALERTAS, { headers: { "x-atm-admin-token": token } })
|
||||
.then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.json(); })
|
||||
.catch(function () { return null; }); // null = "sem dado", diferente de "zero alertas"
|
||||
}
|
||||
|
||||
function pesoSeveridade(s) { return s === "critical" ? 3 : s === "warning" ? 2 : 1; }
|
||||
|
||||
function pintarSino(dados) {
|
||||
sinoLista.innerHTML = "";
|
||||
sinoVazio.classList.add("oculto");
|
||||
sinoIndisponivel.classList.add("oculto");
|
||||
|
||||
if (!dados || dados.indisponivel) {
|
||||
sinoContagem.textContent = "!";
|
||||
sinoContagem.className = "sino-contagem sem-dado";
|
||||
sinoIndisponivel.classList.remove("oculto");
|
||||
return;
|
||||
}
|
||||
|
||||
var alertas = dados.alertas || [];
|
||||
if (alertas.length === 0) {
|
||||
sinoContagem.classList.add("oculto");
|
||||
sinoVazio.classList.remove("oculto");
|
||||
return;
|
||||
}
|
||||
|
||||
sinoContagem.textContent = String(alertas.length);
|
||||
sinoContagem.className = "sino-contagem" +
|
||||
(alertas.some(function (a) { return a.severidade === "critical"; }) ? "" : " aviso");
|
||||
sinoContagem.classList.remove("oculto");
|
||||
|
||||
alertas.forEach(function (a) {
|
||||
var li = document.createElement("li");
|
||||
var sev = pesoSeveridade(a.severidade) === 3 ? "critical" : pesoSeveridade(a.severidade) === 2 ? "warning" : "info";
|
||||
li.className = "sino-item " + sev;
|
||||
li.innerHTML = "<span class=\"nome\">" + esc(a.nome) + "</span>"
|
||||
+ (a.resumo ? "<span class=\"resumo\">" + esc(a.resumo) + "</span>" : "")
|
||||
+ (a.tenant ? "<span class=\"quem\">" + esc(a.tenant) + (a.desde ? " — " + ha(a.desde) : "") + "</span>"
|
||||
: (a.desde ? "<span class=\"quem\">" + ha(a.desde) + "</span>" : ""));
|
||||
sinoLista.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
function atualizarSino(token) {
|
||||
buscarAlertasInfra(token).then(pintarSino);
|
||||
}
|
||||
|
||||
function ligarSino(token) {
|
||||
sinoCaixa.classList.remove("oculto");
|
||||
atualizarSino(token);
|
||||
if (sinoIntervalo) clearInterval(sinoIntervalo);
|
||||
sinoIntervalo = setInterval(function () { atualizarSino(token); }, INTERVALO_SINO_MS);
|
||||
}
|
||||
|
||||
function desligarSino() {
|
||||
sinoCaixa.classList.add("oculto");
|
||||
fecharSino();
|
||||
if (sinoIntervalo) { clearInterval(sinoIntervalo); sinoIntervalo = null; }
|
||||
}
|
||||
|
||||
function abrirSino() {
|
||||
sinoPainel.classList.remove("oculto");
|
||||
sino.setAttribute("aria-expanded", "true");
|
||||
}
|
||||
function fecharSino() {
|
||||
sinoPainel.classList.add("oculto");
|
||||
sino.setAttribute("aria-expanded", "false");
|
||||
}
|
||||
|
||||
/* ── formatacao ─────────────────────────────────────────────────────────── */
|
||||
|
||||
function esc(s) {
|
||||
@@ -2958,6 +3085,7 @@ data:
|
||||
tokenAtual = token;
|
||||
pintar();
|
||||
mostrar(conteudo);
|
||||
ligarSino(token);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2992,6 +3120,17 @@ data:
|
||||
location.reload();
|
||||
});
|
||||
|
||||
sino.addEventListener("click", function () {
|
||||
var abrindo = sinoPainel.classList.contains("oculto");
|
||||
if (abrindo) { abrirSino(); if (tokenAtual) atualizarSino(tokenAtual); } else { fecharSino(); }
|
||||
});
|
||||
document.addEventListener("click", function (ev) {
|
||||
if (!sinoPainel.classList.contains("oculto") && !sinoCaixa.contains(ev.target)) fecharSino();
|
||||
});
|
||||
document.addEventListener("keydown", function (ev) {
|
||||
if (ev.key === "Escape") fecharSino();
|
||||
});
|
||||
|
||||
botaoAtualizar.addEventListener("click", function () {
|
||||
if (!tokenAtual) return;
|
||||
botaoAtualizar.disabled = true;
|
||||
|
||||
@@ -1981,6 +1981,38 @@ data:
|
||||
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||
.topo .acao:disabled { opacity: .6; cursor: wait; }
|
||||
|
||||
/* ── sino de alertas de infraestrutura ───────────────────────────────────── */
|
||||
.sino-caixa { position: relative; }
|
||||
.sino {
|
||||
background: none; border: 1px solid var(--line); color: var(--navy);
|
||||
border-radius: 10px; padding: 6px 10px; cursor: pointer; font-size: 16px;
|
||||
line-height: 1; position: relative;
|
||||
}
|
||||
.sino:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||
.sino[aria-expanded="true"] { border-color: var(--orange); }
|
||||
.sino-contagem {
|
||||
position: absolute; top: -7px; right: -7px;
|
||||
background: var(--perigo); color: var(--branco); border-radius: 999px;
|
||||
font-size: 11px; font-weight: 700; padding: 1px 6px; line-height: 1.4; min-width: 14px; text-align: center;
|
||||
}
|
||||
.sino-contagem.aviso { background: var(--alerta); }
|
||||
.sino-contagem.sem-dado { background: var(--muted); }
|
||||
.sino-painel {
|
||||
position: absolute; right: 0; top: calc(100% + 10px);
|
||||
width: min(360px, calc(100vw - 32px)); max-height: 60vh; overflow: auto;
|
||||
background: var(--branco); border: 1px solid var(--line); border-radius: 14px;
|
||||
box-shadow: 0 14px 34px rgba(11,44,61,.16); padding: 14px; z-index: 20; text-align: left;
|
||||
}
|
||||
.sino-topo { font-weight: 700; margin-bottom: 10px; }
|
||||
.sino-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
|
||||
.sino-item { border-radius: 10px; padding: 10px 12px; font-size: 13px; }
|
||||
.sino-item.critical { background: #fdecea; }
|
||||
.sino-item.warning { background: #fdf3e6; }
|
||||
.sino-item.info { background: var(--paper); }
|
||||
.sino-item .nome { font-weight: 700; display: block; }
|
||||
.sino-item .resumo { display: block; margin-top: 2px; }
|
||||
.sino-item .quem { color: var(--muted); font-size: 12px; display: block; margin-top: 4px; }
|
||||
|
||||
.wrap { max-width: 1320px; margin: 0 auto; padding: 22px 28px 48px; }
|
||||
|
||||
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
||||
@@ -2226,7 +2258,7 @@ data:
|
||||
`Cache-Control: immutable` por um ano. Suba o numero a cada publicacao.
|
||||
-->
|
||||
<link rel="stylesheet" href="/publico/fontes/fontes.css">
|
||||
<link rel="stylesheet" href="/publico/painel.css?v=2.82">
|
||||
<link rel="stylesheet" href="/publico/painel.css?v=2.83">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -2234,6 +2266,18 @@ data:
|
||||
<img class="logo" src="/publico/img/logo-athletic-map.png" alt="Athletic Map" width="150" height="37">
|
||||
<span class="titulo">Painel do dono</span>
|
||||
<span id="apurado" class="apurado oculto"></span>
|
||||
<div class="sino-caixa oculto" id="sinoCaixa">
|
||||
<button type="button" id="sino" class="sino" aria-haspopup="true" aria-expanded="false" title="Alertas de infraestrutura">
|
||||
<span aria-hidden="true">🔔</span>
|
||||
<span id="sinoContagem" class="sino-contagem oculto"></span>
|
||||
</button>
|
||||
<div id="sinoPainel" class="sino-painel oculto" role="menu">
|
||||
<div class="sino-topo">Alertas de infraestrutura</div>
|
||||
<ul id="sinoLista" class="sino-lista"></ul>
|
||||
<p id="sinoVazio" class="ajuda oculto">Nenhum alerta agora.</p>
|
||||
<p id="sinoIndisponivel" class="ajuda oculto">Não consegui falar com o monitoramento agora.</p>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" id="atualizar" class="acao oculto">Atualizar</button>
|
||||
<button type="button" id="sair" class="sair oculto">Sair</button>
|
||||
</header>
|
||||
@@ -2388,7 +2432,7 @@ data:
|
||||
|
||||
</main>
|
||||
|
||||
<script src="/publico/painel.js?v=2.82"></script>
|
||||
<script src="/publico/painel.js?v=2.83"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2422,16 +2466,22 @@ data:
|
||||
"use strict";
|
||||
|
||||
var API = "/api/plataforma/painel/visao";
|
||||
var API_ALERTAS = "/api/plataforma/painel/alertas-infra";
|
||||
var CHAVE = "atm.painel.token";
|
||||
var INTERVALO_SINO_MS = 60000;
|
||||
|
||||
var $ = function (id) { return document.getElementById(id); };
|
||||
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
||||
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
||||
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
||||
var corpo = $("linhas"), vazio = $("vazio");
|
||||
var sinoCaixa = $("sinoCaixa"), sino = $("sino"), sinoContagem = $("sinoContagem"),
|
||||
sinoPainel = $("sinoPainel"), sinoLista = $("sinoLista"),
|
||||
sinoVazio = $("sinoVazio"), sinoIndisponivel = $("sinoIndisponivel");
|
||||
|
||||
var visao = null;
|
||||
var tokenAtual = null;
|
||||
var sinoIntervalo = null;
|
||||
|
||||
/* ── token ──────────────────────────────────────────────────────────────── */
|
||||
|
||||
@@ -2448,6 +2498,83 @@ data:
|
||||
});
|
||||
}
|
||||
|
||||
/* ── sino de alertas de infraestrutura ─────────────────────────────────────
|
||||
*
|
||||
* Le o mesmo Alertmanager que ja manda e-mail — o sino nao inventa alerta
|
||||
* nenhum, so mostra o que o e-mail ja avisou. Falha calada de proposito:
|
||||
* se o servidor nao responder, o sino so fica "sem dado" — nao pode
|
||||
* derrubar o painel inteiro por causa de um widget auxiliar.
|
||||
*/
|
||||
function buscarAlertasInfra(token) {
|
||||
return fetch(API_ALERTAS, { headers: { "x-atm-admin-token": token } })
|
||||
.then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.json(); })
|
||||
.catch(function () { return null; }); // null = "sem dado", diferente de "zero alertas"
|
||||
}
|
||||
|
||||
function pesoSeveridade(s) { return s === "critical" ? 3 : s === "warning" ? 2 : 1; }
|
||||
|
||||
function pintarSino(dados) {
|
||||
sinoLista.innerHTML = "";
|
||||
sinoVazio.classList.add("oculto");
|
||||
sinoIndisponivel.classList.add("oculto");
|
||||
|
||||
if (!dados || dados.indisponivel) {
|
||||
sinoContagem.textContent = "!";
|
||||
sinoContagem.className = "sino-contagem sem-dado";
|
||||
sinoIndisponivel.classList.remove("oculto");
|
||||
return;
|
||||
}
|
||||
|
||||
var alertas = dados.alertas || [];
|
||||
if (alertas.length === 0) {
|
||||
sinoContagem.classList.add("oculto");
|
||||
sinoVazio.classList.remove("oculto");
|
||||
return;
|
||||
}
|
||||
|
||||
sinoContagem.textContent = String(alertas.length);
|
||||
sinoContagem.className = "sino-contagem" +
|
||||
(alertas.some(function (a) { return a.severidade === "critical"; }) ? "" : " aviso");
|
||||
sinoContagem.classList.remove("oculto");
|
||||
|
||||
alertas.forEach(function (a) {
|
||||
var li = document.createElement("li");
|
||||
var sev = pesoSeveridade(a.severidade) === 3 ? "critical" : pesoSeveridade(a.severidade) === 2 ? "warning" : "info";
|
||||
li.className = "sino-item " + sev;
|
||||
li.innerHTML = "<span class=\"nome\">" + esc(a.nome) + "</span>"
|
||||
+ (a.resumo ? "<span class=\"resumo\">" + esc(a.resumo) + "</span>" : "")
|
||||
+ (a.tenant ? "<span class=\"quem\">" + esc(a.tenant) + (a.desde ? " — " + ha(a.desde) : "") + "</span>"
|
||||
: (a.desde ? "<span class=\"quem\">" + ha(a.desde) + "</span>" : ""));
|
||||
sinoLista.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
function atualizarSino(token) {
|
||||
buscarAlertasInfra(token).then(pintarSino);
|
||||
}
|
||||
|
||||
function ligarSino(token) {
|
||||
sinoCaixa.classList.remove("oculto");
|
||||
atualizarSino(token);
|
||||
if (sinoIntervalo) clearInterval(sinoIntervalo);
|
||||
sinoIntervalo = setInterval(function () { atualizarSino(token); }, INTERVALO_SINO_MS);
|
||||
}
|
||||
|
||||
function desligarSino() {
|
||||
sinoCaixa.classList.add("oculto");
|
||||
fecharSino();
|
||||
if (sinoIntervalo) { clearInterval(sinoIntervalo); sinoIntervalo = null; }
|
||||
}
|
||||
|
||||
function abrirSino() {
|
||||
sinoPainel.classList.remove("oculto");
|
||||
sino.setAttribute("aria-expanded", "true");
|
||||
}
|
||||
function fecharSino() {
|
||||
sinoPainel.classList.add("oculto");
|
||||
sino.setAttribute("aria-expanded", "false");
|
||||
}
|
||||
|
||||
/* ── formatacao ─────────────────────────────────────────────────────────── */
|
||||
|
||||
function esc(s) {
|
||||
@@ -2958,6 +3085,7 @@ data:
|
||||
tokenAtual = token;
|
||||
pintar();
|
||||
mostrar(conteudo);
|
||||
ligarSino(token);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2992,6 +3120,17 @@ data:
|
||||
location.reload();
|
||||
});
|
||||
|
||||
sino.addEventListener("click", function () {
|
||||
var abrindo = sinoPainel.classList.contains("oculto");
|
||||
if (abrindo) { abrirSino(); if (tokenAtual) atualizarSino(tokenAtual); } else { fecharSino(); }
|
||||
});
|
||||
document.addEventListener("click", function (ev) {
|
||||
if (!sinoPainel.classList.contains("oculto") && !sinoCaixa.contains(ev.target)) fecharSino();
|
||||
});
|
||||
document.addEventListener("keydown", function (ev) {
|
||||
if (ev.key === "Escape") fecharSino();
|
||||
});
|
||||
|
||||
botaoAtualizar.addEventListener("click", function () {
|
||||
if (!tokenAtual) return;
|
||||
botaoAtualizar.disabled = true;
|
||||
|
||||
@@ -1981,6 +1981,38 @@ data:
|
||||
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||
.topo .acao:disabled { opacity: .6; cursor: wait; }
|
||||
|
||||
/* ── sino de alertas de infraestrutura ───────────────────────────────────── */
|
||||
.sino-caixa { position: relative; }
|
||||
.sino {
|
||||
background: none; border: 1px solid var(--line); color: var(--navy);
|
||||
border-radius: 10px; padding: 6px 10px; cursor: pointer; font-size: 16px;
|
||||
line-height: 1; position: relative;
|
||||
}
|
||||
.sino:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||
.sino[aria-expanded="true"] { border-color: var(--orange); }
|
||||
.sino-contagem {
|
||||
position: absolute; top: -7px; right: -7px;
|
||||
background: var(--perigo); color: var(--branco); border-radius: 999px;
|
||||
font-size: 11px; font-weight: 700; padding: 1px 6px; line-height: 1.4; min-width: 14px; text-align: center;
|
||||
}
|
||||
.sino-contagem.aviso { background: var(--alerta); }
|
||||
.sino-contagem.sem-dado { background: var(--muted); }
|
||||
.sino-painel {
|
||||
position: absolute; right: 0; top: calc(100% + 10px);
|
||||
width: min(360px, calc(100vw - 32px)); max-height: 60vh; overflow: auto;
|
||||
background: var(--branco); border: 1px solid var(--line); border-radius: 14px;
|
||||
box-shadow: 0 14px 34px rgba(11,44,61,.16); padding: 14px; z-index: 20; text-align: left;
|
||||
}
|
||||
.sino-topo { font-weight: 700; margin-bottom: 10px; }
|
||||
.sino-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
|
||||
.sino-item { border-radius: 10px; padding: 10px 12px; font-size: 13px; }
|
||||
.sino-item.critical { background: #fdecea; }
|
||||
.sino-item.warning { background: #fdf3e6; }
|
||||
.sino-item.info { background: var(--paper); }
|
||||
.sino-item .nome { font-weight: 700; display: block; }
|
||||
.sino-item .resumo { display: block; margin-top: 2px; }
|
||||
.sino-item .quem { color: var(--muted); font-size: 12px; display: block; margin-top: 4px; }
|
||||
|
||||
.wrap { max-width: 1320px; margin: 0 auto; padding: 22px 28px 48px; }
|
||||
|
||||
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
||||
@@ -2226,7 +2258,7 @@ data:
|
||||
`Cache-Control: immutable` por um ano. Suba o numero a cada publicacao.
|
||||
-->
|
||||
<link rel="stylesheet" href="/publico/fontes/fontes.css">
|
||||
<link rel="stylesheet" href="/publico/painel.css?v=2.82">
|
||||
<link rel="stylesheet" href="/publico/painel.css?v=2.83">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -2234,6 +2266,18 @@ data:
|
||||
<img class="logo" src="/publico/img/logo-athletic-map.png" alt="Athletic Map" width="150" height="37">
|
||||
<span class="titulo">Painel do dono</span>
|
||||
<span id="apurado" class="apurado oculto"></span>
|
||||
<div class="sino-caixa oculto" id="sinoCaixa">
|
||||
<button type="button" id="sino" class="sino" aria-haspopup="true" aria-expanded="false" title="Alertas de infraestrutura">
|
||||
<span aria-hidden="true">🔔</span>
|
||||
<span id="sinoContagem" class="sino-contagem oculto"></span>
|
||||
</button>
|
||||
<div id="sinoPainel" class="sino-painel oculto" role="menu">
|
||||
<div class="sino-topo">Alertas de infraestrutura</div>
|
||||
<ul id="sinoLista" class="sino-lista"></ul>
|
||||
<p id="sinoVazio" class="ajuda oculto">Nenhum alerta agora.</p>
|
||||
<p id="sinoIndisponivel" class="ajuda oculto">Não consegui falar com o monitoramento agora.</p>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" id="atualizar" class="acao oculto">Atualizar</button>
|
||||
<button type="button" id="sair" class="sair oculto">Sair</button>
|
||||
</header>
|
||||
@@ -2388,7 +2432,7 @@ data:
|
||||
|
||||
</main>
|
||||
|
||||
<script src="/publico/painel.js?v=2.82"></script>
|
||||
<script src="/publico/painel.js?v=2.83"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2422,16 +2466,22 @@ data:
|
||||
"use strict";
|
||||
|
||||
var API = "/api/plataforma/painel/visao";
|
||||
var API_ALERTAS = "/api/plataforma/painel/alertas-infra";
|
||||
var CHAVE = "atm.painel.token";
|
||||
var INTERVALO_SINO_MS = 60000;
|
||||
|
||||
var $ = function (id) { return document.getElementById(id); };
|
||||
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
||||
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
||||
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
||||
var corpo = $("linhas"), vazio = $("vazio");
|
||||
var sinoCaixa = $("sinoCaixa"), sino = $("sino"), sinoContagem = $("sinoContagem"),
|
||||
sinoPainel = $("sinoPainel"), sinoLista = $("sinoLista"),
|
||||
sinoVazio = $("sinoVazio"), sinoIndisponivel = $("sinoIndisponivel");
|
||||
|
||||
var visao = null;
|
||||
var tokenAtual = null;
|
||||
var sinoIntervalo = null;
|
||||
|
||||
/* ── token ──────────────────────────────────────────────────────────────── */
|
||||
|
||||
@@ -2448,6 +2498,83 @@ data:
|
||||
});
|
||||
}
|
||||
|
||||
/* ── sino de alertas de infraestrutura ─────────────────────────────────────
|
||||
*
|
||||
* Le o mesmo Alertmanager que ja manda e-mail — o sino nao inventa alerta
|
||||
* nenhum, so mostra o que o e-mail ja avisou. Falha calada de proposito:
|
||||
* se o servidor nao responder, o sino so fica "sem dado" — nao pode
|
||||
* derrubar o painel inteiro por causa de um widget auxiliar.
|
||||
*/
|
||||
function buscarAlertasInfra(token) {
|
||||
return fetch(API_ALERTAS, { headers: { "x-atm-admin-token": token } })
|
||||
.then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.json(); })
|
||||
.catch(function () { return null; }); // null = "sem dado", diferente de "zero alertas"
|
||||
}
|
||||
|
||||
function pesoSeveridade(s) { return s === "critical" ? 3 : s === "warning" ? 2 : 1; }
|
||||
|
||||
function pintarSino(dados) {
|
||||
sinoLista.innerHTML = "";
|
||||
sinoVazio.classList.add("oculto");
|
||||
sinoIndisponivel.classList.add("oculto");
|
||||
|
||||
if (!dados || dados.indisponivel) {
|
||||
sinoContagem.textContent = "!";
|
||||
sinoContagem.className = "sino-contagem sem-dado";
|
||||
sinoIndisponivel.classList.remove("oculto");
|
||||
return;
|
||||
}
|
||||
|
||||
var alertas = dados.alertas || [];
|
||||
if (alertas.length === 0) {
|
||||
sinoContagem.classList.add("oculto");
|
||||
sinoVazio.classList.remove("oculto");
|
||||
return;
|
||||
}
|
||||
|
||||
sinoContagem.textContent = String(alertas.length);
|
||||
sinoContagem.className = "sino-contagem" +
|
||||
(alertas.some(function (a) { return a.severidade === "critical"; }) ? "" : " aviso");
|
||||
sinoContagem.classList.remove("oculto");
|
||||
|
||||
alertas.forEach(function (a) {
|
||||
var li = document.createElement("li");
|
||||
var sev = pesoSeveridade(a.severidade) === 3 ? "critical" : pesoSeveridade(a.severidade) === 2 ? "warning" : "info";
|
||||
li.className = "sino-item " + sev;
|
||||
li.innerHTML = "<span class=\"nome\">" + esc(a.nome) + "</span>"
|
||||
+ (a.resumo ? "<span class=\"resumo\">" + esc(a.resumo) + "</span>" : "")
|
||||
+ (a.tenant ? "<span class=\"quem\">" + esc(a.tenant) + (a.desde ? " — " + ha(a.desde) : "") + "</span>"
|
||||
: (a.desde ? "<span class=\"quem\">" + ha(a.desde) + "</span>" : ""));
|
||||
sinoLista.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
function atualizarSino(token) {
|
||||
buscarAlertasInfra(token).then(pintarSino);
|
||||
}
|
||||
|
||||
function ligarSino(token) {
|
||||
sinoCaixa.classList.remove("oculto");
|
||||
atualizarSino(token);
|
||||
if (sinoIntervalo) clearInterval(sinoIntervalo);
|
||||
sinoIntervalo = setInterval(function () { atualizarSino(token); }, INTERVALO_SINO_MS);
|
||||
}
|
||||
|
||||
function desligarSino() {
|
||||
sinoCaixa.classList.add("oculto");
|
||||
fecharSino();
|
||||
if (sinoIntervalo) { clearInterval(sinoIntervalo); sinoIntervalo = null; }
|
||||
}
|
||||
|
||||
function abrirSino() {
|
||||
sinoPainel.classList.remove("oculto");
|
||||
sino.setAttribute("aria-expanded", "true");
|
||||
}
|
||||
function fecharSino() {
|
||||
sinoPainel.classList.add("oculto");
|
||||
sino.setAttribute("aria-expanded", "false");
|
||||
}
|
||||
|
||||
/* ── formatacao ─────────────────────────────────────────────────────────── */
|
||||
|
||||
function esc(s) {
|
||||
@@ -2958,6 +3085,7 @@ data:
|
||||
tokenAtual = token;
|
||||
pintar();
|
||||
mostrar(conteudo);
|
||||
ligarSino(token);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2992,6 +3120,17 @@ data:
|
||||
location.reload();
|
||||
});
|
||||
|
||||
sino.addEventListener("click", function () {
|
||||
var abrindo = sinoPainel.classList.contains("oculto");
|
||||
if (abrindo) { abrirSino(); if (tokenAtual) atualizarSino(tokenAtual); } else { fecharSino(); }
|
||||
});
|
||||
document.addEventListener("click", function (ev) {
|
||||
if (!sinoPainel.classList.contains("oculto") && !sinoCaixa.contains(ev.target)) fecharSino();
|
||||
});
|
||||
document.addEventListener("keydown", function (ev) {
|
||||
if (ev.key === "Escape") fecharSino();
|
||||
});
|
||||
|
||||
botaoAtualizar.addEventListener("click", function () {
|
||||
if (!tokenAtual) return;
|
||||
botaoAtualizar.disabled = true;
|
||||
|
||||
@@ -1981,6 +1981,38 @@ data:
|
||||
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||
.topo .acao:disabled { opacity: .6; cursor: wait; }
|
||||
|
||||
/* ── sino de alertas de infraestrutura ───────────────────────────────────── */
|
||||
.sino-caixa { position: relative; }
|
||||
.sino {
|
||||
background: none; border: 1px solid var(--line); color: var(--navy);
|
||||
border-radius: 10px; padding: 6px 10px; cursor: pointer; font-size: 16px;
|
||||
line-height: 1; position: relative;
|
||||
}
|
||||
.sino:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||
.sino[aria-expanded="true"] { border-color: var(--orange); }
|
||||
.sino-contagem {
|
||||
position: absolute; top: -7px; right: -7px;
|
||||
background: var(--perigo); color: var(--branco); border-radius: 999px;
|
||||
font-size: 11px; font-weight: 700; padding: 1px 6px; line-height: 1.4; min-width: 14px; text-align: center;
|
||||
}
|
||||
.sino-contagem.aviso { background: var(--alerta); }
|
||||
.sino-contagem.sem-dado { background: var(--muted); }
|
||||
.sino-painel {
|
||||
position: absolute; right: 0; top: calc(100% + 10px);
|
||||
width: min(360px, calc(100vw - 32px)); max-height: 60vh; overflow: auto;
|
||||
background: var(--branco); border: 1px solid var(--line); border-radius: 14px;
|
||||
box-shadow: 0 14px 34px rgba(11,44,61,.16); padding: 14px; z-index: 20; text-align: left;
|
||||
}
|
||||
.sino-topo { font-weight: 700; margin-bottom: 10px; }
|
||||
.sino-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
|
||||
.sino-item { border-radius: 10px; padding: 10px 12px; font-size: 13px; }
|
||||
.sino-item.critical { background: #fdecea; }
|
||||
.sino-item.warning { background: #fdf3e6; }
|
||||
.sino-item.info { background: var(--paper); }
|
||||
.sino-item .nome { font-weight: 700; display: block; }
|
||||
.sino-item .resumo { display: block; margin-top: 2px; }
|
||||
.sino-item .quem { color: var(--muted); font-size: 12px; display: block; margin-top: 4px; }
|
||||
|
||||
.wrap { max-width: 1320px; margin: 0 auto; padding: 22px 28px 48px; }
|
||||
|
||||
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
||||
@@ -2226,7 +2258,7 @@ data:
|
||||
`Cache-Control: immutable` por um ano. Suba o numero a cada publicacao.
|
||||
-->
|
||||
<link rel="stylesheet" href="/publico/fontes/fontes.css">
|
||||
<link rel="stylesheet" href="/publico/painel.css?v=2.82">
|
||||
<link rel="stylesheet" href="/publico/painel.css?v=2.83">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -2234,6 +2266,18 @@ data:
|
||||
<img class="logo" src="/publico/img/logo-athletic-map.png" alt="Athletic Map" width="150" height="37">
|
||||
<span class="titulo">Painel do dono</span>
|
||||
<span id="apurado" class="apurado oculto"></span>
|
||||
<div class="sino-caixa oculto" id="sinoCaixa">
|
||||
<button type="button" id="sino" class="sino" aria-haspopup="true" aria-expanded="false" title="Alertas de infraestrutura">
|
||||
<span aria-hidden="true">🔔</span>
|
||||
<span id="sinoContagem" class="sino-contagem oculto"></span>
|
||||
</button>
|
||||
<div id="sinoPainel" class="sino-painel oculto" role="menu">
|
||||
<div class="sino-topo">Alertas de infraestrutura</div>
|
||||
<ul id="sinoLista" class="sino-lista"></ul>
|
||||
<p id="sinoVazio" class="ajuda oculto">Nenhum alerta agora.</p>
|
||||
<p id="sinoIndisponivel" class="ajuda oculto">Não consegui falar com o monitoramento agora.</p>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" id="atualizar" class="acao oculto">Atualizar</button>
|
||||
<button type="button" id="sair" class="sair oculto">Sair</button>
|
||||
</header>
|
||||
@@ -2388,7 +2432,7 @@ data:
|
||||
|
||||
</main>
|
||||
|
||||
<script src="/publico/painel.js?v=2.82"></script>
|
||||
<script src="/publico/painel.js?v=2.83"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2422,16 +2466,22 @@ data:
|
||||
"use strict";
|
||||
|
||||
var API = "/api/plataforma/painel/visao";
|
||||
var API_ALERTAS = "/api/plataforma/painel/alertas-infra";
|
||||
var CHAVE = "atm.painel.token";
|
||||
var INTERVALO_SINO_MS = 60000;
|
||||
|
||||
var $ = function (id) { return document.getElementById(id); };
|
||||
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
||||
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
||||
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
||||
var corpo = $("linhas"), vazio = $("vazio");
|
||||
var sinoCaixa = $("sinoCaixa"), sino = $("sino"), sinoContagem = $("sinoContagem"),
|
||||
sinoPainel = $("sinoPainel"), sinoLista = $("sinoLista"),
|
||||
sinoVazio = $("sinoVazio"), sinoIndisponivel = $("sinoIndisponivel");
|
||||
|
||||
var visao = null;
|
||||
var tokenAtual = null;
|
||||
var sinoIntervalo = null;
|
||||
|
||||
/* ── token ──────────────────────────────────────────────────────────────── */
|
||||
|
||||
@@ -2448,6 +2498,83 @@ data:
|
||||
});
|
||||
}
|
||||
|
||||
/* ── sino de alertas de infraestrutura ─────────────────────────────────────
|
||||
*
|
||||
* Le o mesmo Alertmanager que ja manda e-mail — o sino nao inventa alerta
|
||||
* nenhum, so mostra o que o e-mail ja avisou. Falha calada de proposito:
|
||||
* se o servidor nao responder, o sino so fica "sem dado" — nao pode
|
||||
* derrubar o painel inteiro por causa de um widget auxiliar.
|
||||
*/
|
||||
function buscarAlertasInfra(token) {
|
||||
return fetch(API_ALERTAS, { headers: { "x-atm-admin-token": token } })
|
||||
.then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.json(); })
|
||||
.catch(function () { return null; }); // null = "sem dado", diferente de "zero alertas"
|
||||
}
|
||||
|
||||
function pesoSeveridade(s) { return s === "critical" ? 3 : s === "warning" ? 2 : 1; }
|
||||
|
||||
function pintarSino(dados) {
|
||||
sinoLista.innerHTML = "";
|
||||
sinoVazio.classList.add("oculto");
|
||||
sinoIndisponivel.classList.add("oculto");
|
||||
|
||||
if (!dados || dados.indisponivel) {
|
||||
sinoContagem.textContent = "!";
|
||||
sinoContagem.className = "sino-contagem sem-dado";
|
||||
sinoIndisponivel.classList.remove("oculto");
|
||||
return;
|
||||
}
|
||||
|
||||
var alertas = dados.alertas || [];
|
||||
if (alertas.length === 0) {
|
||||
sinoContagem.classList.add("oculto");
|
||||
sinoVazio.classList.remove("oculto");
|
||||
return;
|
||||
}
|
||||
|
||||
sinoContagem.textContent = String(alertas.length);
|
||||
sinoContagem.className = "sino-contagem" +
|
||||
(alertas.some(function (a) { return a.severidade === "critical"; }) ? "" : " aviso");
|
||||
sinoContagem.classList.remove("oculto");
|
||||
|
||||
alertas.forEach(function (a) {
|
||||
var li = document.createElement("li");
|
||||
var sev = pesoSeveridade(a.severidade) === 3 ? "critical" : pesoSeveridade(a.severidade) === 2 ? "warning" : "info";
|
||||
li.className = "sino-item " + sev;
|
||||
li.innerHTML = "<span class=\"nome\">" + esc(a.nome) + "</span>"
|
||||
+ (a.resumo ? "<span class=\"resumo\">" + esc(a.resumo) + "</span>" : "")
|
||||
+ (a.tenant ? "<span class=\"quem\">" + esc(a.tenant) + (a.desde ? " — " + ha(a.desde) : "") + "</span>"
|
||||
: (a.desde ? "<span class=\"quem\">" + ha(a.desde) + "</span>" : ""));
|
||||
sinoLista.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
function atualizarSino(token) {
|
||||
buscarAlertasInfra(token).then(pintarSino);
|
||||
}
|
||||
|
||||
function ligarSino(token) {
|
||||
sinoCaixa.classList.remove("oculto");
|
||||
atualizarSino(token);
|
||||
if (sinoIntervalo) clearInterval(sinoIntervalo);
|
||||
sinoIntervalo = setInterval(function () { atualizarSino(token); }, INTERVALO_SINO_MS);
|
||||
}
|
||||
|
||||
function desligarSino() {
|
||||
sinoCaixa.classList.add("oculto");
|
||||
fecharSino();
|
||||
if (sinoIntervalo) { clearInterval(sinoIntervalo); sinoIntervalo = null; }
|
||||
}
|
||||
|
||||
function abrirSino() {
|
||||
sinoPainel.classList.remove("oculto");
|
||||
sino.setAttribute("aria-expanded", "true");
|
||||
}
|
||||
function fecharSino() {
|
||||
sinoPainel.classList.add("oculto");
|
||||
sino.setAttribute("aria-expanded", "false");
|
||||
}
|
||||
|
||||
/* ── formatacao ─────────────────────────────────────────────────────────── */
|
||||
|
||||
function esc(s) {
|
||||
@@ -2958,6 +3085,7 @@ data:
|
||||
tokenAtual = token;
|
||||
pintar();
|
||||
mostrar(conteudo);
|
||||
ligarSino(token);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2992,6 +3120,17 @@ data:
|
||||
location.reload();
|
||||
});
|
||||
|
||||
sino.addEventListener("click", function () {
|
||||
var abrindo = sinoPainel.classList.contains("oculto");
|
||||
if (abrindo) { abrirSino(); if (tokenAtual) atualizarSino(tokenAtual); } else { fecharSino(); }
|
||||
});
|
||||
document.addEventListener("click", function (ev) {
|
||||
if (!sinoPainel.classList.contains("oculto") && !sinoCaixa.contains(ev.target)) fecharSino();
|
||||
});
|
||||
document.addEventListener("keydown", function (ev) {
|
||||
if (ev.key === "Escape") fecharSino();
|
||||
});
|
||||
|
||||
botaoAtualizar.addEventListener("click", function () {
|
||||
if (!tokenAtual) return;
|
||||
botaoAtualizar.disabled = true;
|
||||
|
||||
Reference in New Issue
Block a user