Conforme prometido, segue o script de tabela de alarmes. Importante avisar que eu fiz esse script para um trabalho que tive com o Scada-LTS, então a aparência da tabela foi feita para atender às necessidades de um cliente específico, mas você pode editar o CSS e deixar o estilo da forma que preferir.
Código CSS (insira como componente HTML)
<style>
@keyframes piscalinha {
0% { background-color: #FFF785; }
49% { background-color: #FFF785; }
50% { background-color: #FFD454; }
100% { background-color: #FFD454; }
}
.alarms-table tr {
color: #333333;
}
.alarms-table tr.evento-ativo {
color: #D00101;
}
.alarms-table tr.evento-pendente {
background-color: #FFD97F;
}
.alarms-table tr.evento-pendente.evento-ativo {
animation-duration: 2s;
animation-name: piscalinha;
animation-iteration-count: infinite;
animation-direction: alternate;
}
</style>
<style>
/* Tabela de alarmes */
.alarms-table {
overflow-y: auto;
/* Altura máxima da tabela antes de ativar o scroll */
max-height: 400px;
}
.alarms-table thead th {
position: sticky;
white-space: nowrap;
top: 0px;
}
.alarms-table table {
border-collapse: collapse !important;
font-size: 12px;
font-weight: normal;
background: #FFFFFF;
padding: 0px !important;
border: none;
/* Largura da tabela */
width: 930px;
}
.alarms-table table td,
.alarms-table table th {
text-align: center;
padding: 8px;
}
.alarms-table table thead th {
color: #FFFFFF;
background: #1F68A3;
}
.alarms-table table thead th:nth-child(odd):not(.evento-pendente) {
background: #324960;
}
.alarms-table table tr:nth-child(even):not(.evento-pendente) {
background: #F8F8F8;
}
.alarms-table table td {
vertical-align: top;
}
/* Alinhar as "bandeiras" e "descrição" dos alarmes à esquerda */
.alarms-table table td:nth-child(3),
.alarms-table table td:nth-child(1) {
text-align: left;
}
/* Ícones de comandos */
.cmd-btn {
cursor: pointer;
display: inline !important;
margin: 2px !important;
}
/* Ícones das bandeiras para cada nível de alarme */
.alarms-table td.alarm-flag img{
margin-right: 3px !important;
width: 20px;
height: 20px;
display: inline !important;
}
</style>
<style>
/* Comentários de data point */
/* Background da janela */
.point-comments-div {
background: #FFFFFF;
padding: 3px;
overflow: auto;
/* Altere de "auto" caso queira fixar a altura */
height: auto;
}
/* Título */
.point-comments-div span.title {
position: sticky;
top: 0px;
background: #FFFFFF;
display: flex;
width: 100%;
font-size: 14px;
font-family: Verdana, Roboto, Arial, Helvetica, sans-serif;
margin-bottom: 8px;
}
.point-comments-div span.title * {
padding: 2px !important;
}
/* Botão de adicionar novo comentário */
.point-comments-div span.title img {
margin-left: auto !important;
align-self: center;
height: auto !important;
width: auto !important;
}
</style>
<style>
/* Aparência de um comentário (tabela e janela de comentários de data point) */
.event-comment {
display: block;
color: #222222;
font-size: 11px;
padding: 4px 6px;
margin: 2px;
background: #F0F8FF;
border: 1px solid #CACACA;
border-radius: 3px;
text-align: left;
}
.event-comment::before {
content: url('images/comment.png');
margin-right: 3px;
}
</style>
Código Javascript (insira como script para servidor)
// Configurar os títulos do cabeçalho da tabela
var titulo_nivel = "Nível de alarme";
var titulo_tempo = "Tempo de início";
var titulo_desc = "Descrição";
var titulo_status = "Status do evento";
var titulo_reconhecimento = "Reconhecimento";
var titulo_comando = "Comandos";
// Mostrar o texto do nível de alarme
var mostrar_nivel_alarme = true;
// Usar formatos mais longos de tempo (sempre exibir as datas)
var tempo_extenso = false;
// Mostrar comandos (reconhecer/silenciar)
var mostrar_comandos = true;
// MODIFICADO 03/02/2024
var uniqueClass = "evttable-" + pointComponent.id;
// FIM DA MODIFICAÇÃO
// Criação do HTML da tabela
var s = "";
s += "<div class='alarms-table " + uniqueClass + "'>";
s += "<table>";
// Cabeçalhos da tabela
s += "<thead>";
s += "<tr>";
s += "<th>" + titulo_nivel + "</th>";
s += "<th>" + titulo_tempo + "</th>";
s += "<th>" + titulo_desc + "</th>";
s += "<th>" + titulo_status + "</th>";
s += "<th>" + titulo_reconhecimento + "</th>";
// MODIFICADO 03/02/2024
if (mostrar_comandos) {
s += "<th>" + titulo_comando +
"<span style='margin-left: 4px;'><img class='cmd-btn' src='images/tick.png' title='Reconhecer todos' onclick='document.querySelectorAll(`." + uniqueClass + " td > .cmd-btn[src*=tick]:not([style*=hidden])`).forEach(elm => elm.click());'><img class='cmd-btn' src='images/sound_mute.png' title='Silenciar todos' onclick='MiscDwr.silenceAll();'>" +
"</span></th>";
}
// FIM DA MODIFICAÇÃO
s += "</tr>";
s += "</thead>";
// Corpo da tabela
s += "<tbody>";
var eventos = getActiveOrPendingEvents();
for (var i = 0; i < eventos.length; i++) {
var e = eventos[i];
var classe_linha = "";
classe_linha += e.isActive ? "evento-ativo " : "";
classe_linha += !e.isAcked ? "evento-pendente " : "";
s += "<tr class='" + classe_linha + "'>";
// Nível de alarme
var niveis = {0: "Nenhum", 1: "Informação", 2: "Urgente", 3: "Crítico", 4: "Risco de vida"};
var imagens = {
0: ['./images/flag_green_off.png', './images/flag_green.png'],
1: ['./images/flag_blue_off.png', './images/flag_blue.png'],
2: ['./images/flag_yellow_off.png', './images/flag_yellow.png'],
3: ['./images/flag_orange_off.png', './images/flag_orange.png'],
4: ['./images/flag_red_off.png', './images/flag_red.png']
};
s += "<td class='alarm-flag'>";
s += "<img src='" + imagens[e.alarmLevel][e.isActive] + "'>";
if (mostrar_nivel_alarme) {
s += niveis[e.alarmLevel];
}
"</td>";
// Tempo de início
s += "<td>" + e.activeTime + "</td>";
// Descrição e comentários
s += "<td><span>" + e.message + "</span>";
for (var j in e.comments) {
var c = e.comments[j];
s += "<span class='event-comment'>" + c.username + " - " + c.prettyTime + ": " + c.comment + "</span>";
}
s += "</td>";
// Status (tempo de inatividade)
s += "<td>" + e.statusMessage + "</td>";
// Reconhecimento (reconhecido/pendente)
if (e.isAcked) {
s += "<td> Reconhecido </td>";
} else {
s += "<td> Pendente </td>";
}
// Comandos
if (mostrar_comandos) {
s += "<td>";
var event_ref_code = com.serotonin.mango.vo.UserComment.TYPE_EVENT;
var sound_img = e.isSilenced ? "images/sound_mute.png" : "images/sound_none.png";
if (!e.isAcked) {
s += "<img class='cmd-btn' src='images/tick.png' title='Reconhecer' onclick='MiscDwr.acknowledgeEvent(" + e.id + ")'>";
} else {
s += "<img class='cmd-btn' style='visibility:hidden;' src='images/tick.png'>";
}
s += "<img class='cmd-btn' src='" + sound_img + "' title='Ativar/desativar som' onclick='MiscDwr.toggleSilence(" + e.id + ")'>";
s += "</td>";
}
s += "</tr>";
}
s += "<tbody>";
s += "</table>";
s += "</div>";
return s;
// Obter os eventos
function getActiveOrPendingEvents() {
// Carregar classes Java
var eventDao = include(org.scada_lts.mango.service.EventService, com.serotonin.mango.db.dao.EventDao);
var user = new com.serotonin.mango.Common().getUser();
// Consultar eventos pendentes no banco de dados
var pending = eventDao.getPendingEvents(user.getId());
// Consultar eventos ativos no banco de dados
var active = eventDao.getActiveEvents();
var evt1 = parseEvents(pending);
var evt2 = parseEvents(active);
if (evt1.length > 0) {
for (var i in evt2) {
var filter = evt1.filter(function(elm) { return elm.id == evt2[i].id; });
if (filter.length == 0) {
evt1.push(evt2[i]);
}
}
} else {
evt1 = evt2;
}
// Remover eventos com certas expressões
var list = ["entrou no sistema", "logged in"];
var modo_blacklist = true; // true: blacklist / false: whitelist
evt1 = evt1.filter(function(elm) {
for (var i in list) {
if (elm.message.toLowerCase().includes(list[i])) {
return !modo_blacklist;
}
}
return modo_blacklist;
});
evt1.sort(function(a, b) { return (b.id - a.id) });
return evt1;
}
function parseEvents(events) {
var eventArray = [];
var bundle = new com.serotonin.mango.Common().getBundle();
// Converter os dados para um objeto Javascript
for (var i = 0; i < events.size(); i++) {
var event = events.get(i);
var e = {};
// Dados básicos do evento
e.id = event.getId();
e.alarmLevel = event.getAlarmLevel();
e.isActive = Number(event.isActive());
e.isAcked = Number(event.isAcknowledged());
e.activeTime = tempo_extenso? event.getFullPrettyActiveTimestamp() : event.getPrettyActiveTimestamp();
e.message = sanitizeXss(event.getMessage().getLocalizedMessage(bundle)) || "";
e.comments = parseComments(event.getEventComments());
// Mensagem de status do evento (ativo/retornou/sem retorno)
e.statusMessage = "Ativo";
if (event.isRtnApplicable() && !event.isActive()) {
var rtnTime = tempo_extenso ? event.getFullPrettyRtnTimestamp() : event.getPrettyRtnTimestamp();
var rtnMessage = sanitizeXss(event.getRtnMessage().getLocalizedMessage(bundle));
e.statusMessage = rtnTime + " - " + rtnMessage;
} else if (!event.isRtnApplicable()) {
e.statusMessage = "\"Retornar ao normal\" desabilitado";
}
// Verificar se o evento está silenciado
if (event.isAlarm()) {
e.isSilenced = event.isSilenced();
}
eventArray.push(e);
}
// Retornar array com todos os eventos
return eventArray;
}
function parseComments(userComments) {
var comments = [];
if (!userComments) {
return [];
}
// Converter para um objeto Javascript
for (var i = 0; i < userComments.size(); i++) {
var uc = userComments.get(i);
var x = {};
x.comment = sanitizeXss(uc.getComment());
x.username = sanitizeXss(uc.getUsername());
x.timestamp = uc.getTs();
x.prettyTime = sanitizeXss(uc.getPrettyTime());
comments.push(x);
}
// Retornar um array com os comentários do evento
return comments;
}
function sanitizeXss(text) {
var sanitized = "";
// Prevents a loop at &'s replacement
String(text).split("&").forEach(function(value, index) {
if (index == 0) {
if (text.charAt(0) != "&") sanitized = value;
return;
}
if (value.search(";") > -1) {
sanitized += "&" + value;
} else {
sanitized += "&" + value;
}
});
return sanitized.replace(/</g, "<")
.replace(/>/g, ">").replace(/'/g, "'")
.replace(/"/g, '"').replace(/\//g, "/");
}
// Include Java classes conditionally (Scada-LTS compatibility feature)
function include(defaultClass, alternativeClass) {
try {
return defaultClass();
} catch (e) {
return alternativeClass();
}
}
Não posso prometer nada, mas tenho a intenção de em algum momento criar uma página no meu site com um repositório de modelos de scripts para servidor prontos. Se/quando eu fizer isso postarei no fórum o link.