Personalização da Lista de Alarmes

Bom dia meus caros,

No meu projeto de Supervisório, utilizando o scadaBR 1.2, contemplo um senário onde monitoro diferentes sistemas com operadores (pessoas) monitorando os respectivos sistemas.
Tenho um problema no qual a lista de alarmes apresenta todos os alarmes, independente do sistema e/ou operador, e isso não me parecia um problema, até um operador começar a reconhecer o alarme do sistema do outro operador.
A procura de uma solução, encontrei um tópico descontinuado que tratava este assunto, contudo está sem resposta: https://forum.scadabr.com.br/t/personalizacao-do-objeto-lista-de-alarmes-no-graphicview/1725
Alguns dos colegas mais avançados já trabalharam ou fazem ideia de como solucionar este “Problema”?

Pela lista de alarmes propriamente dita é mais difícil realizar personalizações. Uma alternativa é criar uma tabela de alarmes à parte com scripts para servidor.

Eu tenho um código antigo nesse sentido. Vou ver se consigo adaptá-lo para postar aqui.

1 curtida

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 += "&amp;" + value;
	  }
	});
	
	return sanitized.replace(/</g, "&lt;")
		  .replace(/>/g, "&gt;").replace(/'/g, "&#x27;")
		  .replace(/"/g, '&quot;').replace(/\//g, "&#x2F;");
}

// 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.

1 curtida

Celso, ja testei um funcionou perfeitamente. Vou fuçar nas configurações e personalizar aqui do meu jeito. Cara, se vc conseguir um espaço no seu site dedicado a essas estripulias, com certeza será muito bem quisto, e vídeos né … na minha opinião, qualquer leigo assim como eu e com seus vídeos, consegue aprender scadaBR. Agradeço muito pelo seu empenho.