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:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||||
.topo .acao:disabled { opacity: .6; cursor: wait; }
|
.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; }
|
.wrap { max-width: 1320px; margin: 0 auto; padding: 22px 28px 48px; }
|
||||||
|
|
||||||
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
||||||
@@ -2226,7 +2258,7 @@ data:
|
|||||||
`Cache-Control: immutable` por um ano. Suba o numero a cada publicacao.
|
`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/fontes/fontes.css">
|
||||||
<link rel="stylesheet" href="/publico/painel.css?v=2.82">
|
<link rel="stylesheet" href="/publico/painel.css?v=2.83">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -2234,6 +2266,18 @@ data:
|
|||||||
<img class="logo" src="/publico/img/logo-athletic-map.png" alt="Athletic Map" width="150" height="37">
|
<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 class="titulo">Painel do dono</span>
|
||||||
<span id="apurado" class="apurado oculto"></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="atualizar" class="acao oculto">Atualizar</button>
|
||||||
<button type="button" id="sair" class="sair oculto">Sair</button>
|
<button type="button" id="sair" class="sair oculto">Sair</button>
|
||||||
</header>
|
</header>
|
||||||
@@ -2388,7 +2432,7 @@ data:
|
|||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script src="/publico/painel.js?v=2.82"></script>
|
<script src="/publico/painel.js?v=2.83"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
@@ -2422,16 +2466,22 @@ data:
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var API = "/api/plataforma/painel/visao";
|
var API = "/api/plataforma/painel/visao";
|
||||||
|
var API_ALERTAS = "/api/plataforma/painel/alertas-infra";
|
||||||
var CHAVE = "atm.painel.token";
|
var CHAVE = "atm.painel.token";
|
||||||
|
var INTERVALO_SINO_MS = 60000;
|
||||||
|
|
||||||
var $ = function (id) { return document.getElementById(id); };
|
var $ = function (id) { return document.getElementById(id); };
|
||||||
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
||||||
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
||||||
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
||||||
var corpo = $("linhas"), vazio = $("vazio");
|
var corpo = $("linhas"), vazio = $("vazio");
|
||||||
|
var sinoCaixa = $("sinoCaixa"), sino = $("sino"), sinoContagem = $("sinoContagem"),
|
||||||
|
sinoPainel = $("sinoPainel"), sinoLista = $("sinoLista"),
|
||||||
|
sinoVazio = $("sinoVazio"), sinoIndisponivel = $("sinoIndisponivel");
|
||||||
|
|
||||||
var visao = null;
|
var visao = null;
|
||||||
var tokenAtual = null;
|
var tokenAtual = null;
|
||||||
|
var sinoIntervalo = null;
|
||||||
|
|
||||||
/* ── token ──────────────────────────────────────────────────────────────── */
|
/* ── 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 ─────────────────────────────────────────────────────────── */
|
/* ── formatacao ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
function esc(s) {
|
function esc(s) {
|
||||||
@@ -2958,6 +3085,7 @@ data:
|
|||||||
tokenAtual = token;
|
tokenAtual = token;
|
||||||
pintar();
|
pintar();
|
||||||
mostrar(conteudo);
|
mostrar(conteudo);
|
||||||
|
ligarSino(token);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2992,6 +3120,17 @@ data:
|
|||||||
location.reload();
|
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 () {
|
botaoAtualizar.addEventListener("click", function () {
|
||||||
if (!tokenAtual) return;
|
if (!tokenAtual) return;
|
||||||
botaoAtualizar.disabled = true;
|
botaoAtualizar.disabled = true;
|
||||||
|
|||||||
@@ -1981,6 +1981,38 @@ data:
|
|||||||
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||||
.topo .acao:disabled { opacity: .6; cursor: wait; }
|
.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; }
|
.wrap { max-width: 1320px; margin: 0 auto; padding: 22px 28px 48px; }
|
||||||
|
|
||||||
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
||||||
@@ -2226,7 +2258,7 @@ data:
|
|||||||
`Cache-Control: immutable` por um ano. Suba o numero a cada publicacao.
|
`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/fontes/fontes.css">
|
||||||
<link rel="stylesheet" href="/publico/painel.css?v=2.82">
|
<link rel="stylesheet" href="/publico/painel.css?v=2.83">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -2234,6 +2266,18 @@ data:
|
|||||||
<img class="logo" src="/publico/img/logo-athletic-map.png" alt="Athletic Map" width="150" height="37">
|
<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 class="titulo">Painel do dono</span>
|
||||||
<span id="apurado" class="apurado oculto"></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="atualizar" class="acao oculto">Atualizar</button>
|
||||||
<button type="button" id="sair" class="sair oculto">Sair</button>
|
<button type="button" id="sair" class="sair oculto">Sair</button>
|
||||||
</header>
|
</header>
|
||||||
@@ -2388,7 +2432,7 @@ data:
|
|||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script src="/publico/painel.js?v=2.82"></script>
|
<script src="/publico/painel.js?v=2.83"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
@@ -2422,16 +2466,22 @@ data:
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var API = "/api/plataforma/painel/visao";
|
var API = "/api/plataforma/painel/visao";
|
||||||
|
var API_ALERTAS = "/api/plataforma/painel/alertas-infra";
|
||||||
var CHAVE = "atm.painel.token";
|
var CHAVE = "atm.painel.token";
|
||||||
|
var INTERVALO_SINO_MS = 60000;
|
||||||
|
|
||||||
var $ = function (id) { return document.getElementById(id); };
|
var $ = function (id) { return document.getElementById(id); };
|
||||||
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
||||||
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
||||||
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
||||||
var corpo = $("linhas"), vazio = $("vazio");
|
var corpo = $("linhas"), vazio = $("vazio");
|
||||||
|
var sinoCaixa = $("sinoCaixa"), sino = $("sino"), sinoContagem = $("sinoContagem"),
|
||||||
|
sinoPainel = $("sinoPainel"), sinoLista = $("sinoLista"),
|
||||||
|
sinoVazio = $("sinoVazio"), sinoIndisponivel = $("sinoIndisponivel");
|
||||||
|
|
||||||
var visao = null;
|
var visao = null;
|
||||||
var tokenAtual = null;
|
var tokenAtual = null;
|
||||||
|
var sinoIntervalo = null;
|
||||||
|
|
||||||
/* ── token ──────────────────────────────────────────────────────────────── */
|
/* ── 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 ─────────────────────────────────────────────────────────── */
|
/* ── formatacao ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
function esc(s) {
|
function esc(s) {
|
||||||
@@ -2958,6 +3085,7 @@ data:
|
|||||||
tokenAtual = token;
|
tokenAtual = token;
|
||||||
pintar();
|
pintar();
|
||||||
mostrar(conteudo);
|
mostrar(conteudo);
|
||||||
|
ligarSino(token);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2992,6 +3120,17 @@ data:
|
|||||||
location.reload();
|
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 () {
|
botaoAtualizar.addEventListener("click", function () {
|
||||||
if (!tokenAtual) return;
|
if (!tokenAtual) return;
|
||||||
botaoAtualizar.disabled = true;
|
botaoAtualizar.disabled = true;
|
||||||
|
|||||||
@@ -1981,6 +1981,38 @@ data:
|
|||||||
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||||
.topo .acao:disabled { opacity: .6; cursor: wait; }
|
.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; }
|
.wrap { max-width: 1320px; margin: 0 auto; padding: 22px 28px 48px; }
|
||||||
|
|
||||||
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
||||||
@@ -2226,7 +2258,7 @@ data:
|
|||||||
`Cache-Control: immutable` por um ano. Suba o numero a cada publicacao.
|
`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/fontes/fontes.css">
|
||||||
<link rel="stylesheet" href="/publico/painel.css?v=2.82">
|
<link rel="stylesheet" href="/publico/painel.css?v=2.83">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -2234,6 +2266,18 @@ data:
|
|||||||
<img class="logo" src="/publico/img/logo-athletic-map.png" alt="Athletic Map" width="150" height="37">
|
<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 class="titulo">Painel do dono</span>
|
||||||
<span id="apurado" class="apurado oculto"></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="atualizar" class="acao oculto">Atualizar</button>
|
||||||
<button type="button" id="sair" class="sair oculto">Sair</button>
|
<button type="button" id="sair" class="sair oculto">Sair</button>
|
||||||
</header>
|
</header>
|
||||||
@@ -2388,7 +2432,7 @@ data:
|
|||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script src="/publico/painel.js?v=2.82"></script>
|
<script src="/publico/painel.js?v=2.83"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
@@ -2422,16 +2466,22 @@ data:
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var API = "/api/plataforma/painel/visao";
|
var API = "/api/plataforma/painel/visao";
|
||||||
|
var API_ALERTAS = "/api/plataforma/painel/alertas-infra";
|
||||||
var CHAVE = "atm.painel.token";
|
var CHAVE = "atm.painel.token";
|
||||||
|
var INTERVALO_SINO_MS = 60000;
|
||||||
|
|
||||||
var $ = function (id) { return document.getElementById(id); };
|
var $ = function (id) { return document.getElementById(id); };
|
||||||
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
||||||
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
||||||
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
||||||
var corpo = $("linhas"), vazio = $("vazio");
|
var corpo = $("linhas"), vazio = $("vazio");
|
||||||
|
var sinoCaixa = $("sinoCaixa"), sino = $("sino"), sinoContagem = $("sinoContagem"),
|
||||||
|
sinoPainel = $("sinoPainel"), sinoLista = $("sinoLista"),
|
||||||
|
sinoVazio = $("sinoVazio"), sinoIndisponivel = $("sinoIndisponivel");
|
||||||
|
|
||||||
var visao = null;
|
var visao = null;
|
||||||
var tokenAtual = null;
|
var tokenAtual = null;
|
||||||
|
var sinoIntervalo = null;
|
||||||
|
|
||||||
/* ── token ──────────────────────────────────────────────────────────────── */
|
/* ── 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 ─────────────────────────────────────────────────────────── */
|
/* ── formatacao ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
function esc(s) {
|
function esc(s) {
|
||||||
@@ -2958,6 +3085,7 @@ data:
|
|||||||
tokenAtual = token;
|
tokenAtual = token;
|
||||||
pintar();
|
pintar();
|
||||||
mostrar(conteudo);
|
mostrar(conteudo);
|
||||||
|
ligarSino(token);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2992,6 +3120,17 @@ data:
|
|||||||
location.reload();
|
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 () {
|
botaoAtualizar.addEventListener("click", function () {
|
||||||
if (!tokenAtual) return;
|
if (!tokenAtual) return;
|
||||||
botaoAtualizar.disabled = true;
|
botaoAtualizar.disabled = true;
|
||||||
|
|||||||
@@ -1981,6 +1981,38 @@ data:
|
|||||||
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||||
.topo .acao:disabled { opacity: .6; cursor: wait; }
|
.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; }
|
.wrap { max-width: 1320px; margin: 0 auto; padding: 22px 28px 48px; }
|
||||||
|
|
||||||
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
||||||
@@ -2226,7 +2258,7 @@ data:
|
|||||||
`Cache-Control: immutable` por um ano. Suba o numero a cada publicacao.
|
`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/fontes/fontes.css">
|
||||||
<link rel="stylesheet" href="/publico/painel.css?v=2.82">
|
<link rel="stylesheet" href="/publico/painel.css?v=2.83">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -2234,6 +2266,18 @@ data:
|
|||||||
<img class="logo" src="/publico/img/logo-athletic-map.png" alt="Athletic Map" width="150" height="37">
|
<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 class="titulo">Painel do dono</span>
|
||||||
<span id="apurado" class="apurado oculto"></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="atualizar" class="acao oculto">Atualizar</button>
|
||||||
<button type="button" id="sair" class="sair oculto">Sair</button>
|
<button type="button" id="sair" class="sair oculto">Sair</button>
|
||||||
</header>
|
</header>
|
||||||
@@ -2388,7 +2432,7 @@ data:
|
|||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script src="/publico/painel.js?v=2.82"></script>
|
<script src="/publico/painel.js?v=2.83"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
@@ -2422,16 +2466,22 @@ data:
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var API = "/api/plataforma/painel/visao";
|
var API = "/api/plataforma/painel/visao";
|
||||||
|
var API_ALERTAS = "/api/plataforma/painel/alertas-infra";
|
||||||
var CHAVE = "atm.painel.token";
|
var CHAVE = "atm.painel.token";
|
||||||
|
var INTERVALO_SINO_MS = 60000;
|
||||||
|
|
||||||
var $ = function (id) { return document.getElementById(id); };
|
var $ = function (id) { return document.getElementById(id); };
|
||||||
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
||||||
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
||||||
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
||||||
var corpo = $("linhas"), vazio = $("vazio");
|
var corpo = $("linhas"), vazio = $("vazio");
|
||||||
|
var sinoCaixa = $("sinoCaixa"), sino = $("sino"), sinoContagem = $("sinoContagem"),
|
||||||
|
sinoPainel = $("sinoPainel"), sinoLista = $("sinoLista"),
|
||||||
|
sinoVazio = $("sinoVazio"), sinoIndisponivel = $("sinoIndisponivel");
|
||||||
|
|
||||||
var visao = null;
|
var visao = null;
|
||||||
var tokenAtual = null;
|
var tokenAtual = null;
|
||||||
|
var sinoIntervalo = null;
|
||||||
|
|
||||||
/* ── token ──────────────────────────────────────────────────────────────── */
|
/* ── 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 ─────────────────────────────────────────────────────────── */
|
/* ── formatacao ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
function esc(s) {
|
function esc(s) {
|
||||||
@@ -2958,6 +3085,7 @@ data:
|
|||||||
tokenAtual = token;
|
tokenAtual = token;
|
||||||
pintar();
|
pintar();
|
||||||
mostrar(conteudo);
|
mostrar(conteudo);
|
||||||
|
ligarSino(token);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2992,6 +3120,17 @@ data:
|
|||||||
location.reload();
|
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 () {
|
botaoAtualizar.addEventListener("click", function () {
|
||||||
if (!tokenAtual) return;
|
if (!tokenAtual) return;
|
||||||
botaoAtualizar.disabled = true;
|
botaoAtualizar.disabled = true;
|
||||||
|
|||||||
@@ -1981,6 +1981,38 @@ data:
|
|||||||
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||||
.topo .acao:disabled { opacity: .6; cursor: wait; }
|
.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; }
|
.wrap { max-width: 1320px; margin: 0 auto; padding: 22px 28px 48px; }
|
||||||
|
|
||||||
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
||||||
@@ -2226,7 +2258,7 @@ data:
|
|||||||
`Cache-Control: immutable` por um ano. Suba o numero a cada publicacao.
|
`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/fontes/fontes.css">
|
||||||
<link rel="stylesheet" href="/publico/painel.css?v=2.82">
|
<link rel="stylesheet" href="/publico/painel.css?v=2.83">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -2234,6 +2266,18 @@ data:
|
|||||||
<img class="logo" src="/publico/img/logo-athletic-map.png" alt="Athletic Map" width="150" height="37">
|
<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 class="titulo">Painel do dono</span>
|
||||||
<span id="apurado" class="apurado oculto"></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="atualizar" class="acao oculto">Atualizar</button>
|
||||||
<button type="button" id="sair" class="sair oculto">Sair</button>
|
<button type="button" id="sair" class="sair oculto">Sair</button>
|
||||||
</header>
|
</header>
|
||||||
@@ -2388,7 +2432,7 @@ data:
|
|||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script src="/publico/painel.js?v=2.82"></script>
|
<script src="/publico/painel.js?v=2.83"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
@@ -2422,16 +2466,22 @@ data:
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var API = "/api/plataforma/painel/visao";
|
var API = "/api/plataforma/painel/visao";
|
||||||
|
var API_ALERTAS = "/api/plataforma/painel/alertas-infra";
|
||||||
var CHAVE = "atm.painel.token";
|
var CHAVE = "atm.painel.token";
|
||||||
|
var INTERVALO_SINO_MS = 60000;
|
||||||
|
|
||||||
var $ = function (id) { return document.getElementById(id); };
|
var $ = function (id) { return document.getElementById(id); };
|
||||||
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
||||||
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
||||||
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
||||||
var corpo = $("linhas"), vazio = $("vazio");
|
var corpo = $("linhas"), vazio = $("vazio");
|
||||||
|
var sinoCaixa = $("sinoCaixa"), sino = $("sino"), sinoContagem = $("sinoContagem"),
|
||||||
|
sinoPainel = $("sinoPainel"), sinoLista = $("sinoLista"),
|
||||||
|
sinoVazio = $("sinoVazio"), sinoIndisponivel = $("sinoIndisponivel");
|
||||||
|
|
||||||
var visao = null;
|
var visao = null;
|
||||||
var tokenAtual = null;
|
var tokenAtual = null;
|
||||||
|
var sinoIntervalo = null;
|
||||||
|
|
||||||
/* ── token ──────────────────────────────────────────────────────────────── */
|
/* ── 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 ─────────────────────────────────────────────────────────── */
|
/* ── formatacao ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
function esc(s) {
|
function esc(s) {
|
||||||
@@ -2958,6 +3085,7 @@ data:
|
|||||||
tokenAtual = token;
|
tokenAtual = token;
|
||||||
pintar();
|
pintar();
|
||||||
mostrar(conteudo);
|
mostrar(conteudo);
|
||||||
|
ligarSino(token);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2992,6 +3120,17 @@ data:
|
|||||||
location.reload();
|
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 () {
|
botaoAtualizar.addEventListener("click", function () {
|
||||||
if (!tokenAtual) return;
|
if (!tokenAtual) return;
|
||||||
botaoAtualizar.disabled = true;
|
botaoAtualizar.disabled = true;
|
||||||
|
|||||||
@@ -1981,6 +1981,38 @@ data:
|
|||||||
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||||
.topo .acao:disabled { opacity: .6; cursor: wait; }
|
.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; }
|
.wrap { max-width: 1320px; margin: 0 auto; padding: 22px 28px 48px; }
|
||||||
|
|
||||||
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
||||||
@@ -2226,7 +2258,7 @@ data:
|
|||||||
`Cache-Control: immutable` por um ano. Suba o numero a cada publicacao.
|
`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/fontes/fontes.css">
|
||||||
<link rel="stylesheet" href="/publico/painel.css?v=2.82">
|
<link rel="stylesheet" href="/publico/painel.css?v=2.83">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -2234,6 +2266,18 @@ data:
|
|||||||
<img class="logo" src="/publico/img/logo-athletic-map.png" alt="Athletic Map" width="150" height="37">
|
<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 class="titulo">Painel do dono</span>
|
||||||
<span id="apurado" class="apurado oculto"></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="atualizar" class="acao oculto">Atualizar</button>
|
||||||
<button type="button" id="sair" class="sair oculto">Sair</button>
|
<button type="button" id="sair" class="sair oculto">Sair</button>
|
||||||
</header>
|
</header>
|
||||||
@@ -2388,7 +2432,7 @@ data:
|
|||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script src="/publico/painel.js?v=2.82"></script>
|
<script src="/publico/painel.js?v=2.83"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
@@ -2422,16 +2466,22 @@ data:
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var API = "/api/plataforma/painel/visao";
|
var API = "/api/plataforma/painel/visao";
|
||||||
|
var API_ALERTAS = "/api/plataforma/painel/alertas-infra";
|
||||||
var CHAVE = "atm.painel.token";
|
var CHAVE = "atm.painel.token";
|
||||||
|
var INTERVALO_SINO_MS = 60000;
|
||||||
|
|
||||||
var $ = function (id) { return document.getElementById(id); };
|
var $ = function (id) { return document.getElementById(id); };
|
||||||
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
||||||
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
||||||
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
||||||
var corpo = $("linhas"), vazio = $("vazio");
|
var corpo = $("linhas"), vazio = $("vazio");
|
||||||
|
var sinoCaixa = $("sinoCaixa"), sino = $("sino"), sinoContagem = $("sinoContagem"),
|
||||||
|
sinoPainel = $("sinoPainel"), sinoLista = $("sinoLista"),
|
||||||
|
sinoVazio = $("sinoVazio"), sinoIndisponivel = $("sinoIndisponivel");
|
||||||
|
|
||||||
var visao = null;
|
var visao = null;
|
||||||
var tokenAtual = null;
|
var tokenAtual = null;
|
||||||
|
var sinoIntervalo = null;
|
||||||
|
|
||||||
/* ── token ──────────────────────────────────────────────────────────────── */
|
/* ── 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 ─────────────────────────────────────────────────────────── */
|
/* ── formatacao ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
function esc(s) {
|
function esc(s) {
|
||||||
@@ -2958,6 +3085,7 @@ data:
|
|||||||
tokenAtual = token;
|
tokenAtual = token;
|
||||||
pintar();
|
pintar();
|
||||||
mostrar(conteudo);
|
mostrar(conteudo);
|
||||||
|
ligarSino(token);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2992,6 +3120,17 @@ data:
|
|||||||
location.reload();
|
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 () {
|
botaoAtualizar.addEventListener("click", function () {
|
||||||
if (!tokenAtual) return;
|
if (!tokenAtual) return;
|
||||||
botaoAtualizar.disabled = true;
|
botaoAtualizar.disabled = true;
|
||||||
|
|||||||
@@ -1981,6 +1981,38 @@ data:
|
|||||||
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||||
.topo .acao:disabled { opacity: .6; cursor: wait; }
|
.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; }
|
.wrap { max-width: 1320px; margin: 0 auto; padding: 22px 28px 48px; }
|
||||||
|
|
||||||
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
||||||
@@ -2226,7 +2258,7 @@ data:
|
|||||||
`Cache-Control: immutable` por um ano. Suba o numero a cada publicacao.
|
`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/fontes/fontes.css">
|
||||||
<link rel="stylesheet" href="/publico/painel.css?v=2.82">
|
<link rel="stylesheet" href="/publico/painel.css?v=2.83">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -2234,6 +2266,18 @@ data:
|
|||||||
<img class="logo" src="/publico/img/logo-athletic-map.png" alt="Athletic Map" width="150" height="37">
|
<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 class="titulo">Painel do dono</span>
|
||||||
<span id="apurado" class="apurado oculto"></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="atualizar" class="acao oculto">Atualizar</button>
|
||||||
<button type="button" id="sair" class="sair oculto">Sair</button>
|
<button type="button" id="sair" class="sair oculto">Sair</button>
|
||||||
</header>
|
</header>
|
||||||
@@ -2388,7 +2432,7 @@ data:
|
|||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script src="/publico/painel.js?v=2.82"></script>
|
<script src="/publico/painel.js?v=2.83"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
@@ -2422,16 +2466,22 @@ data:
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var API = "/api/plataforma/painel/visao";
|
var API = "/api/plataforma/painel/visao";
|
||||||
|
var API_ALERTAS = "/api/plataforma/painel/alertas-infra";
|
||||||
var CHAVE = "atm.painel.token";
|
var CHAVE = "atm.painel.token";
|
||||||
|
var INTERVALO_SINO_MS = 60000;
|
||||||
|
|
||||||
var $ = function (id) { return document.getElementById(id); };
|
var $ = function (id) { return document.getElementById(id); };
|
||||||
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
||||||
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
||||||
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
||||||
var corpo = $("linhas"), vazio = $("vazio");
|
var corpo = $("linhas"), vazio = $("vazio");
|
||||||
|
var sinoCaixa = $("sinoCaixa"), sino = $("sino"), sinoContagem = $("sinoContagem"),
|
||||||
|
sinoPainel = $("sinoPainel"), sinoLista = $("sinoLista"),
|
||||||
|
sinoVazio = $("sinoVazio"), sinoIndisponivel = $("sinoIndisponivel");
|
||||||
|
|
||||||
var visao = null;
|
var visao = null;
|
||||||
var tokenAtual = null;
|
var tokenAtual = null;
|
||||||
|
var sinoIntervalo = null;
|
||||||
|
|
||||||
/* ── token ──────────────────────────────────────────────────────────────── */
|
/* ── 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 ─────────────────────────────────────────────────────────── */
|
/* ── formatacao ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
function esc(s) {
|
function esc(s) {
|
||||||
@@ -2958,6 +3085,7 @@ data:
|
|||||||
tokenAtual = token;
|
tokenAtual = token;
|
||||||
pintar();
|
pintar();
|
||||||
mostrar(conteudo);
|
mostrar(conteudo);
|
||||||
|
ligarSino(token);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2992,6 +3120,17 @@ data:
|
|||||||
location.reload();
|
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 () {
|
botaoAtualizar.addEventListener("click", function () {
|
||||||
if (!tokenAtual) return;
|
if (!tokenAtual) return;
|
||||||
botaoAtualizar.disabled = true;
|
botaoAtualizar.disabled = true;
|
||||||
|
|||||||
@@ -1981,6 +1981,38 @@ data:
|
|||||||
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||||
.topo .acao:disabled { opacity: .6; cursor: wait; }
|
.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; }
|
.wrap { max-width: 1320px; margin: 0 auto; padding: 22px 28px 48px; }
|
||||||
|
|
||||||
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
||||||
@@ -2226,7 +2258,7 @@ data:
|
|||||||
`Cache-Control: immutable` por um ano. Suba o numero a cada publicacao.
|
`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/fontes/fontes.css">
|
||||||
<link rel="stylesheet" href="/publico/painel.css?v=2.82">
|
<link rel="stylesheet" href="/publico/painel.css?v=2.83">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -2234,6 +2266,18 @@ data:
|
|||||||
<img class="logo" src="/publico/img/logo-athletic-map.png" alt="Athletic Map" width="150" height="37">
|
<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 class="titulo">Painel do dono</span>
|
||||||
<span id="apurado" class="apurado oculto"></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="atualizar" class="acao oculto">Atualizar</button>
|
||||||
<button type="button" id="sair" class="sair oculto">Sair</button>
|
<button type="button" id="sair" class="sair oculto">Sair</button>
|
||||||
</header>
|
</header>
|
||||||
@@ -2388,7 +2432,7 @@ data:
|
|||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script src="/publico/painel.js?v=2.82"></script>
|
<script src="/publico/painel.js?v=2.83"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
@@ -2422,16 +2466,22 @@ data:
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var API = "/api/plataforma/painel/visao";
|
var API = "/api/plataforma/painel/visao";
|
||||||
|
var API_ALERTAS = "/api/plataforma/painel/alertas-infra";
|
||||||
var CHAVE = "atm.painel.token";
|
var CHAVE = "atm.painel.token";
|
||||||
|
var INTERVALO_SINO_MS = 60000;
|
||||||
|
|
||||||
var $ = function (id) { return document.getElementById(id); };
|
var $ = function (id) { return document.getElementById(id); };
|
||||||
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
||||||
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
||||||
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
||||||
var corpo = $("linhas"), vazio = $("vazio");
|
var corpo = $("linhas"), vazio = $("vazio");
|
||||||
|
var sinoCaixa = $("sinoCaixa"), sino = $("sino"), sinoContagem = $("sinoContagem"),
|
||||||
|
sinoPainel = $("sinoPainel"), sinoLista = $("sinoLista"),
|
||||||
|
sinoVazio = $("sinoVazio"), sinoIndisponivel = $("sinoIndisponivel");
|
||||||
|
|
||||||
var visao = null;
|
var visao = null;
|
||||||
var tokenAtual = null;
|
var tokenAtual = null;
|
||||||
|
var sinoIntervalo = null;
|
||||||
|
|
||||||
/* ── token ──────────────────────────────────────────────────────────────── */
|
/* ── 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 ─────────────────────────────────────────────────────────── */
|
/* ── formatacao ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
function esc(s) {
|
function esc(s) {
|
||||||
@@ -2958,6 +3085,7 @@ data:
|
|||||||
tokenAtual = token;
|
tokenAtual = token;
|
||||||
pintar();
|
pintar();
|
||||||
mostrar(conteudo);
|
mostrar(conteudo);
|
||||||
|
ligarSino(token);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2992,6 +3120,17 @@ data:
|
|||||||
location.reload();
|
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 () {
|
botaoAtualizar.addEventListener("click", function () {
|
||||||
if (!tokenAtual) return;
|
if (!tokenAtual) return;
|
||||||
botaoAtualizar.disabled = true;
|
botaoAtualizar.disabled = true;
|
||||||
|
|||||||
@@ -1981,6 +1981,38 @@ data:
|
|||||||
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||||
.topo .acao:disabled { opacity: .6; cursor: wait; }
|
.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; }
|
.wrap { max-width: 1320px; margin: 0 auto; padding: 22px 28px 48px; }
|
||||||
|
|
||||||
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
||||||
@@ -2226,7 +2258,7 @@ data:
|
|||||||
`Cache-Control: immutable` por um ano. Suba o numero a cada publicacao.
|
`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/fontes/fontes.css">
|
||||||
<link rel="stylesheet" href="/publico/painel.css?v=2.82">
|
<link rel="stylesheet" href="/publico/painel.css?v=2.83">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -2234,6 +2266,18 @@ data:
|
|||||||
<img class="logo" src="/publico/img/logo-athletic-map.png" alt="Athletic Map" width="150" height="37">
|
<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 class="titulo">Painel do dono</span>
|
||||||
<span id="apurado" class="apurado oculto"></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="atualizar" class="acao oculto">Atualizar</button>
|
||||||
<button type="button" id="sair" class="sair oculto">Sair</button>
|
<button type="button" id="sair" class="sair oculto">Sair</button>
|
||||||
</header>
|
</header>
|
||||||
@@ -2388,7 +2432,7 @@ data:
|
|||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script src="/publico/painel.js?v=2.82"></script>
|
<script src="/publico/painel.js?v=2.83"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
@@ -2422,16 +2466,22 @@ data:
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var API = "/api/plataforma/painel/visao";
|
var API = "/api/plataforma/painel/visao";
|
||||||
|
var API_ALERTAS = "/api/plataforma/painel/alertas-infra";
|
||||||
var CHAVE = "atm.painel.token";
|
var CHAVE = "atm.painel.token";
|
||||||
|
var INTERVALO_SINO_MS = 60000;
|
||||||
|
|
||||||
var $ = function (id) { return document.getElementById(id); };
|
var $ = function (id) { return document.getElementById(id); };
|
||||||
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
||||||
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
||||||
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
||||||
var corpo = $("linhas"), vazio = $("vazio");
|
var corpo = $("linhas"), vazio = $("vazio");
|
||||||
|
var sinoCaixa = $("sinoCaixa"), sino = $("sino"), sinoContagem = $("sinoContagem"),
|
||||||
|
sinoPainel = $("sinoPainel"), sinoLista = $("sinoLista"),
|
||||||
|
sinoVazio = $("sinoVazio"), sinoIndisponivel = $("sinoIndisponivel");
|
||||||
|
|
||||||
var visao = null;
|
var visao = null;
|
||||||
var tokenAtual = null;
|
var tokenAtual = null;
|
||||||
|
var sinoIntervalo = null;
|
||||||
|
|
||||||
/* ── token ──────────────────────────────────────────────────────────────── */
|
/* ── 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 ─────────────────────────────────────────────────────────── */
|
/* ── formatacao ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
function esc(s) {
|
function esc(s) {
|
||||||
@@ -2958,6 +3085,7 @@ data:
|
|||||||
tokenAtual = token;
|
tokenAtual = token;
|
||||||
pintar();
|
pintar();
|
||||||
mostrar(conteudo);
|
mostrar(conteudo);
|
||||||
|
ligarSino(token);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2992,6 +3120,17 @@ data:
|
|||||||
location.reload();
|
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 () {
|
botaoAtualizar.addEventListener("click", function () {
|
||||||
if (!tokenAtual) return;
|
if (!tokenAtual) return;
|
||||||
botaoAtualizar.disabled = true;
|
botaoAtualizar.disabled = true;
|
||||||
|
|||||||
@@ -1981,6 +1981,38 @@ data:
|
|||||||
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
.topo .acao:hover, .topo .sair:hover { border-color: var(--orange); color: var(--orange-600); }
|
||||||
.topo .acao:disabled { opacity: .6; cursor: wait; }
|
.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; }
|
.wrap { max-width: 1320px; margin: 0 auto; padding: 22px 28px 48px; }
|
||||||
|
|
||||||
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
/* ── cartoes ──────────────────────────────────────────────────────────────── */
|
||||||
@@ -2226,7 +2258,7 @@ data:
|
|||||||
`Cache-Control: immutable` por um ano. Suba o numero a cada publicacao.
|
`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/fontes/fontes.css">
|
||||||
<link rel="stylesheet" href="/publico/painel.css?v=2.82">
|
<link rel="stylesheet" href="/publico/painel.css?v=2.83">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -2234,6 +2266,18 @@ data:
|
|||||||
<img class="logo" src="/publico/img/logo-athletic-map.png" alt="Athletic Map" width="150" height="37">
|
<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 class="titulo">Painel do dono</span>
|
||||||
<span id="apurado" class="apurado oculto"></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="atualizar" class="acao oculto">Atualizar</button>
|
||||||
<button type="button" id="sair" class="sair oculto">Sair</button>
|
<button type="button" id="sair" class="sair oculto">Sair</button>
|
||||||
</header>
|
</header>
|
||||||
@@ -2388,7 +2432,7 @@ data:
|
|||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script src="/publico/painel.js?v=2.82"></script>
|
<script src="/publico/painel.js?v=2.83"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
@@ -2422,16 +2466,22 @@ data:
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var API = "/api/plataforma/painel/visao";
|
var API = "/api/plataforma/painel/visao";
|
||||||
|
var API_ALERTAS = "/api/plataforma/painel/alertas-infra";
|
||||||
var CHAVE = "atm.painel.token";
|
var CHAVE = "atm.painel.token";
|
||||||
|
var INTERVALO_SINO_MS = 60000;
|
||||||
|
|
||||||
var $ = function (id) { return document.getElementById(id); };
|
var $ = function (id) { return document.getElementById(id); };
|
||||||
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
var porta = $("porta"), conteudo = $("conteudo"), carregando = $("carregando");
|
||||||
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
var formEntrar = $("formEntrar"), campoToken = $("token"), erroEntrar = $("erroEntrar");
|
||||||
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
var botaoSair = $("sair"), botaoAtualizar = $("atualizar"), apurado = $("apurado");
|
||||||
var corpo = $("linhas"), vazio = $("vazio");
|
var corpo = $("linhas"), vazio = $("vazio");
|
||||||
|
var sinoCaixa = $("sinoCaixa"), sino = $("sino"), sinoContagem = $("sinoContagem"),
|
||||||
|
sinoPainel = $("sinoPainel"), sinoLista = $("sinoLista"),
|
||||||
|
sinoVazio = $("sinoVazio"), sinoIndisponivel = $("sinoIndisponivel");
|
||||||
|
|
||||||
var visao = null;
|
var visao = null;
|
||||||
var tokenAtual = null;
|
var tokenAtual = null;
|
||||||
|
var sinoIntervalo = null;
|
||||||
|
|
||||||
/* ── token ──────────────────────────────────────────────────────────────── */
|
/* ── 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 ─────────────────────────────────────────────────────────── */
|
/* ── formatacao ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
function esc(s) {
|
function esc(s) {
|
||||||
@@ -2958,6 +3085,7 @@ data:
|
|||||||
tokenAtual = token;
|
tokenAtual = token;
|
||||||
pintar();
|
pintar();
|
||||||
mostrar(conteudo);
|
mostrar(conteudo);
|
||||||
|
ligarSino(token);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2992,6 +3120,17 @@ data:
|
|||||||
location.reload();
|
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 () {
|
botaoAtualizar.addEventListener("click", function () {
|
||||||
if (!tokenAtual) return;
|
if (!tokenAtual) return;
|
||||||
botaoAtualizar.disabled = true;
|
botaoAtualizar.disabled = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user