mirror of https://github.com/interlegis/sapl.git
12 changed files with 947 additions and 37 deletions
@ -0,0 +1,21 @@ |
|||
# sapl/asgi.py |
|||
import os |
|||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sapl.settings") |
|||
|
|||
import django |
|||
django.setup() |
|||
|
|||
from channels.routing import ProtocolTypeRouter, URLRouter |
|||
from channels.auth import AuthMiddlewareStack |
|||
from channels.http import AsgiHandler # Django 2.2 uses AsgiHandler, not django.core.asgi |
|||
from django.urls import path |
|||
|
|||
from sapl.painel.consumers import PainelConsumer, HealthConsumer |
|||
|
|||
application = ProtocolTypeRouter({ |
|||
"http": AsgiHandler(), |
|||
"websocket": AuthMiddlewareStack(URLRouter([ |
|||
# path("ws/painel/", PainelConsumer.as_asgi()), |
|||
path("ws/painel/<int:controller_id>/", PainelConsumer.as_asgi()), |
|||
])), |
|||
}) |
|||
@ -0,0 +1,365 @@ |
|||
import html |
|||
import json |
|||
import logging |
|||
import time |
|||
|
|||
from channels.db import database_sync_to_async |
|||
from channels.generic.websocket import AsyncWebsocketConsumer, AsyncJsonWebsocketConsumer |
|||
from django.core.exceptions import ObjectDoesNotExist |
|||
from django.db.models import Q |
|||
|
|||
from sapl.base.models import CasaLegislativa, AppConfig |
|||
from sapl.sessao.models import SessaoPlenaria, OrdemDia, ExpedienteMateria, RegistroVotacao, RegistroLeitura, \ |
|||
PresencaOrdemDia, SessaoPlenariaPresenca, OradorExpediente, VotoParlamentar, AbstractOrdemDia |
|||
|
|||
log = logging.getLogger(__name__) |
|||
|
|||
|
|||
def get_dados_painel(pk: int) -> dict: |
|||
sessao = SessaoPlenaria.objects.get(id=pk) |
|||
casa = CasaLegislativa.objects.first() |
|||
app_config = AppConfig.objects.first() |
|||
|
|||
if casa and app_config and (bool(casa.logotipo)): |
|||
brasao = casa.logotipo.url \ |
|||
if app_config.mostrar_brasao_painel else None |
|||
ordem_dia = OrdemDia.objects.filter(sessao_plenaria_id=pk, votacao_aberta=True).last() |
|||
expediente = ExpedienteMateria.objects.filter(sessao_plenaria_id=pk, votacao_aberta=True).last() |
|||
|
|||
dados_sessao = { |
|||
"type": "data", |
|||
"status_painel": sessao.painel_aberto, |
|||
"brasao": brasao, |
|||
"mostrar_voto": app_config.mostrar_voto, |
|||
"sessao_plenaria": str(sessao), |
|||
"sessao_plenaria_data": sessao.data_inicio.strftime("%d/%m/%Y"), |
|||
"sessao_plenaria_hora_inicio": sessao.hora_inicio, |
|||
"sessao_solene": sessao.tipo.nome == "Solene", |
|||
"sessao_finalizada": sessao.finalizada, |
|||
"tema_solene": sessao.tema_solene, |
|||
# "cronometro_aparte": get_cronometro_status(request, "aparte"), |
|||
# "cronometro_discurso": get_cronometro_status(request, "discurso"), |
|||
# "cronometro_ordem": get_cronometro_status(request, "ordem"), |
|||
# "cronometro_consideracoes": get_cronometro_status(request, "consideracoes"), |
|||
} |
|||
|
|||
# Caso tenha alguma matéria com votação aberta, ela é mostrada no painel |
|||
# com prioridade para Ordem Dia. |
|||
if ordem_dia: |
|||
dados_sessao.update(get_presentes(pk, ordem_dia)) |
|||
dados_sessao.update(get_votos(ordem_dia, app_config.mostrar_voto)) |
|||
elif expediente: |
|||
dados_sessao.update(get_presentes(pk, expediente)) |
|||
dados_sessao.update(get_votos(expediente, app_config.mostrar_voto)) |
|||
|
|||
# Caso não tenha nenhuma aberta, |
|||
# a matéria a ser mostrada no Painel deve ser a última votada |
|||
last_ordem_voto = RegistroVotacao.objects.filter( |
|||
ordem__sessao_plenaria=sessao).order_by("data_hora").last() |
|||
last_expediente_voto = RegistroVotacao.objects.filter( |
|||
expediente__sessao_plenaria=sessao).order_by("data_hora").last() |
|||
|
|||
last_ordem_leitura = RegistroLeitura.objects.filter( |
|||
ordem__sessao_plenaria=sessao).order_by("data_hora").last() |
|||
last_expediente_leitura = RegistroLeitura.objects.filter( |
|||
expediente__sessao_plenaria=sessao).order_by("data_hora").last() |
|||
|
|||
# Obtém última matéria votada, através do timestamp mais recente |
|||
ordem_expediente = None |
|||
ultimo_timestamp = None |
|||
if last_ordem_voto: |
|||
ordem_expediente = last_ordem_voto.ordem |
|||
ultimo_timestamp = last_ordem_voto.data_hora |
|||
if (last_expediente_voto and ultimo_timestamp and last_expediente_voto.data_hora > ultimo_timestamp) or \ |
|||
(not ultimo_timestamp and last_expediente_voto): |
|||
ordem_expediente = last_expediente_voto.expediente |
|||
ultimo_timestamp = last_expediente_voto.data_hora |
|||
if (last_ordem_leitura and ultimo_timestamp and last_ordem_leitura.data_hora > ultimo_timestamp) or \ |
|||
(not ultimo_timestamp and last_ordem_leitura): |
|||
ordem_expediente = last_ordem_leitura.ordem |
|||
ultimo_timestamp = last_ordem_leitura.data_hora |
|||
if (last_expediente_leitura and ultimo_timestamp and last_expediente_leitura.data_hora > ultimo_timestamp) or \ |
|||
(not ultimo_timestamp and last_expediente_leitura): |
|||
ordem_expediente = last_expediente_leitura.expediente |
|||
ultimo_timestamp = last_expediente_leitura.data_hora |
|||
|
|||
# if ordem_expediente: |
|||
# dados_sessao.update(get_presentes(pk, ordem_expediente)) |
|||
# dados_sessao.update(get_votos(ordem_expediente, app_config.mostrar_voto)) |
|||
|
|||
# Retorna que não há nenhuma matéria já votada ou aberta |
|||
dados_sessao.update({ |
|||
'msg_painel': str('Nenhuma matéria disponivel para votação.')}) |
|||
|
|||
return dados_sessao |
|||
|
|||
|
|||
def get_votos(materia: AbstractOrdemDia, mostrar_voto: bool): |
|||
logger = logging.getLogger(__name__) |
|||
if type(materia) == OrdemDia: |
|||
if materia.tipo_votacao != 4: |
|||
registro = RegistroVotacao.objects.filter( |
|||
ordem=materia, materia=materia.materia).order_by('data_hora').last() |
|||
leitura = None |
|||
else: |
|||
leitura = RegistroLeitura.objects.filter( |
|||
ordem=materia, materia=materia.materia).order_by('data_hora').last() |
|||
registro = None |
|||
tipo = 'ordem' |
|||
elif type(materia) == ExpedienteMateria: |
|||
if materia.tipo_votacao != 4: |
|||
registro = RegistroVotacao.objects.filter( |
|||
expediente=materia, materia=materia.materia).order_by('data_hora').last() |
|||
leitura = None |
|||
else: |
|||
leitura = RegistroLeitura.objects.filter( |
|||
expediente=materia, materia=materia.materia).order_by('data_hora').last() |
|||
registro = None |
|||
tipo = 'expediente' |
|||
|
|||
response = {} |
|||
|
|||
if not registro and not leitura: |
|||
response.update({ |
|||
'numero_votos_sim': 0, |
|||
'numero_votos_nao': 0, |
|||
'numero_abstencoes': 0, |
|||
'registro': None, |
|||
'total_votos': 0, |
|||
'tipo_resultado': 'Ainda não foi votada.', |
|||
}) |
|||
|
|||
if materia.tipo_votacao == 2: |
|||
if tipo == 'ordem': |
|||
votos_parlamentares = VotoParlamentar.objects.filter( |
|||
ordem_id=materia.id).order_by( |
|||
'parlamentar__nome_parlamentar') |
|||
else: |
|||
votos_parlamentares = VotoParlamentar.objects.filter( |
|||
expediente_id=materia.id).order_by( |
|||
'parlamentar__nome_parlamentar') |
|||
|
|||
for i, p in enumerate(response.get('presentes', [])): |
|||
try: |
|||
logger.info("Tentando obter votos do parlamentar (id={}).".format(p['parlamentar_id'])) |
|||
voto = votos_parlamentares.get(parlamentar_id=p['parlamentar_id']).voto |
|||
|
|||
if voto: |
|||
if mostrar_voto: |
|||
response['presentes'][i]['voto'] = voto |
|||
else: |
|||
response['presentes'][i]['voto'] = 'Voto Informado' |
|||
except ObjectDoesNotExist: |
|||
# logger.error("Votos do parlamentar (id={}) não encontrados. Retornado vazio." |
|||
# .format(p['parlamentar_id'])) |
|||
response['presentes'][i]['voto'] = '' |
|||
elif leitura: |
|||
response.update({ |
|||
'numero_votos_sim': 0, |
|||
'numero_votos_nao': 0, |
|||
'numero_abstencoes': 0, |
|||
'registro': True, |
|||
'total_votos': 0, |
|||
'tipo_resultado': 'Matéria lida.', |
|||
}) |
|||
else: |
|||
total = (registro.numero_votos_sim + |
|||
registro.numero_votos_nao + |
|||
registro.numero_abstencoes) |
|||
|
|||
if materia.tipo_votacao == 2: |
|||
votos_parlamentares = VotoParlamentar.objects.filter( |
|||
votacao_id=registro.id).order_by( |
|||
'parlamentar__nome_parlamentar') |
|||
|
|||
for i, p in enumerate(response.get('presentes', [])): |
|||
try: |
|||
logger.debug("Tentando obter votos do parlamentar (id={}).".format(p['parlamentar_id'])) |
|||
response['presentes'][i]['voto'] = votos_parlamentares.get( |
|||
parlamentar_id=p['parlamentar_id']).voto |
|||
except ObjectDoesNotExist: |
|||
logger.error( |
|||
"Votos do parlamentar (id={}) não encontrados. Retornado None.".format(p['parlamentar_id'])) |
|||
response['presentes'][i]['voto'] = None |
|||
|
|||
response.update({ |
|||
'numero_votos_sim': registro.numero_votos_sim, |
|||
'numero_votos_nao': registro.numero_votos_nao, |
|||
'numero_abstencoes': registro.numero_abstencoes, |
|||
'registro': True, |
|||
'total_votos': total, |
|||
'tipo_resultado': registro.tipo_resultado_votacao.nome, |
|||
}) |
|||
|
|||
return response |
|||
|
|||
|
|||
def filiacao_data(parlamentar, data_inicio, data_fim=None): |
|||
from sapl.parlamentares.models import Filiacao |
|||
|
|||
filiacoes_parlamentar = Filiacao.objects.filter( |
|||
parlamentar=parlamentar) |
|||
|
|||
filiacoes = filiacoes_parlamentar.filter(Q( |
|||
data__lte=data_inicio, |
|||
data_desfiliacao__isnull=True) | Q( |
|||
data__lte=data_inicio, |
|||
data_desfiliacao__gte=data_inicio)) |
|||
|
|||
if data_fim: |
|||
filiacoes = filiacoes | filiacoes_parlamentar.filter( |
|||
data__gte=data_inicio, |
|||
data__lte=data_fim) |
|||
|
|||
return ' | '.join([f.partido.sigla for f in filiacoes]) |
|||
|
|||
|
|||
def get_presentes(pk: int, materia: AbstractOrdemDia): |
|||
if type(materia) == OrdemDia: |
|||
presentes = PresencaOrdemDia.objects.filter( |
|||
sessao_plenaria_id=pk) |
|||
else: |
|||
presentes = SessaoPlenariaPresenca.objects.filter( |
|||
sessao_plenaria_id=pk) |
|||
|
|||
sessao = SessaoPlenaria.objects.get(id=pk) |
|||
num_presentes = len(presentes) |
|||
data_sessao = sessao.data_inicio |
|||
oradores = OradorExpediente.objects.filter( |
|||
sessao_plenaria_id=pk).order_by('numero_ordem') |
|||
|
|||
oradores_list = [] |
|||
for o in oradores: |
|||
oradores_list.append( |
|||
{ |
|||
'nome': o.parlamentar.nome_parlamentar, |
|||
'numero': o.numero_ordem |
|||
}) |
|||
|
|||
presentes_list = [] |
|||
for p in presentes: |
|||
legislatura = sessao.legislatura |
|||
# Recupera os mandatos daquele parlamentar |
|||
mandatos = p.parlamentar.mandato_set.filter(legislatura=legislatura) |
|||
|
|||
if p.parlamentar.ativo and mandatos: |
|||
filiacao = filiacao_data(p.parlamentar, data_sessao, data_sessao) |
|||
if not filiacao: |
|||
partido = 'Sem Registro' |
|||
else: |
|||
partido = filiacao |
|||
|
|||
presentes_list.append( |
|||
{'id': p.id, |
|||
'parlamentar_id': p.parlamentar.id, |
|||
'nome': p.parlamentar.nome_parlamentar, |
|||
'partido': partido, |
|||
'voto': '' |
|||
}) |
|||
|
|||
elif not p.parlamentar.ativo or not mandatos: |
|||
num_presentes += -1 |
|||
|
|||
response = {} |
|||
|
|||
if materia: |
|||
if materia.tipo_votacao == 1: |
|||
tipo_votacao = 'Simbólica' |
|||
elif materia.tipo_votacao == 2: |
|||
tipo_votacao = 'Nominal' |
|||
elif materia.tipo_votacao == 3: |
|||
tipo_votacao = 'Secreta' |
|||
elif materia.tipo_votacao == 4: |
|||
tipo_votacao = 'Leitura' |
|||
|
|||
response.update({ |
|||
'tipo_resultado': materia.resultado, |
|||
'observacao_materia': html.unescape(materia.observacao), |
|||
'tipo_votacao': tipo_votacao, |
|||
'materia_legislativa_texto': str(materia.materia), |
|||
'materia_legislativa_ementa': str(materia.materia.ementa) |
|||
}) |
|||
|
|||
# presentes_list = sort_lista_chave(presentes_list, 'nome') |
|||
|
|||
response.update({ |
|||
'presentes': presentes_list, |
|||
'num_presentes': num_presentes, |
|||
'oradores': oradores_list, |
|||
'msg_painel': str('Votação aberta!'), |
|||
}) |
|||
return response |
|||
|
|||
|
|||
class PainelConsumer(AsyncJsonWebsocketConsumer): |
|||
|
|||
# def __init__(self): |
|||
# self.group = set() |
|||
# self.controller_id = None |
|||
|
|||
async def connect(self): |
|||
user = self.scope.get("user") |
|||
controller_id = self.scope["url_route"]["kwargs"]["controller_id"] |
|||
print(f"user: {user}, controller_id: {controller_id}") |
|||
|
|||
if not (user and user.is_authenticated): |
|||
log.info(f"{user} is not authenticated user!") |
|||
await self.close(code=4401) # explicit, graceful close |
|||
return |
|||
|
|||
if not await self.user_can_view(user.id, controller_id): |
|||
await self.close(code=4403) |
|||
return |
|||
|
|||
self.controller_id = controller_id |
|||
self.group = f"controller_{controller_id}" |
|||
await self.channel_layer.group_add(self.group, self.channel_name) |
|||
|
|||
await self.accept() |
|||
# await self.send_json({"type": "data", |
|||
# "text": "Connection established!"}) |
|||
|
|||
await self.send_json(get_dados_painel(controller_id)) |
|||
|
|||
async def disconnect(self, code): |
|||
if hasattr(self, "group"): |
|||
await self.channel_layer.group_discard(self.group, self.channel_name) |
|||
|
|||
# Called by server via channel_layer.group_send |
|||
async def notify(self, event): |
|||
# event: {"type": "notify", "data": {...}} |
|||
await self.send_json(event["data"]) |
|||
|
|||
async def receive(self, text_data=None, bytes_data=None): |
|||
data = json.loads(text_data or "{}") |
|||
print("Received from client:", data) # TODO: turn into log messages |
|||
if data.get("type") == "ping": |
|||
print("PING") |
|||
await self.send_json({"type": "pong", "ts": time.time()}) |
|||
return |
|||
await self.send_json({"type": "data", |
|||
"text": f"Echo: {data}"}) |
|||
|
|||
@database_sync_to_async |
|||
def user_can_view(self, user_id, controller_id) -> bool: |
|||
# Replace with your ACL check (ORM must be in a sync wrapper) |
|||
# return Controller.objects.filter(id=controller_id, owners__id=user_id).exists() |
|||
return True |
|||
|
|||
|
|||
class HealthConsumer(AsyncWebsocketConsumer): |
|||
""" |
|||
WebSockets consumer that doesn"t require authentication to |
|||
debug with wscat (wscat -c ws://127.0.0.1:8000/ws/painel/) |
|||
""" |
|||
|
|||
async def connect(self): |
|||
try: |
|||
await self.accept() |
|||
await self.send(json.dumps({"ok": True})) |
|||
except Exception as e: |
|||
log.exception("connect failed: %s", e) |
|||
# Let Channels close with 1011 if we got here |
|||
|
|||
async def receive(self, text_data=None, bytes_data=None): |
|||
await self.send(text_data or "") |
|||
@ -0,0 +1,423 @@ |
|||
{% load i18n %} |
|||
{% load common_tags %} |
|||
|
|||
{% load render_bundle from webpack_loader %} |
|||
{% load webpack_static from webpack_loader %} |
|||
<!DOCTYPE HTML> |
|||
<!--[if IE 8]> <html class="no-js lt-ie9" lang="pt-br"> <![endif]--> |
|||
<!--[if gt IE 8]><!--> |
|||
<html lang="pt-br"> |
|||
<!--<![endif]--> |
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
<!-- TODO: does it need this head_title here? --> |
|||
<title>{% block head_title %}{% trans 'SAPL - Sistema de Apoio ao Processo Legislativo' %}{% endblock %}</title> |
|||
|
|||
{% block webpack_loader_css %} |
|||
{% render_chunk_vendors 'css' %} |
|||
{% render_bundle 'global' 'css' %} |
|||
{% render_bundle 'painel' 'css' %} |
|||
{% endblock webpack_loader_css %} |
|||
|
|||
|
|||
<style type="text/css"> |
|||
html, body { |
|||
max-width: 100%; |
|||
overflow-x: hidden; |
|||
} |
|||
@media screen { |
|||
ul, li { |
|||
list-style-type: none; |
|||
} |
|||
} |
|||
</style> |
|||
</head> |
|||
<body class="painel-principal"> |
|||
<audio type="hidden" id="audio" src="{% webpack_static 'audio/ring.mp3' %}"></audio> |
|||
|
|||
<div class="d-flex justify-content-center"> |
|||
<h1 id="sessao_plenaria" class="title text-title"></h1> |
|||
</div> |
|||
<div class="row "> |
|||
<div class="col text-center"> |
|||
<span id="sessao_plenaria_data" class="text-value"></span> |
|||
</div> |
|||
<div class="col text-center"> |
|||
<span id="sessao_plenaria_hora_inicio" class="text-value"></span> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="row justify-content-center"> |
|||
<div class="col-1"> |
|||
<img src="" id="logo-painel" class="logo-painel" alt=""/> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="row justify-content-center"> |
|||
<h2 class="text-danger"><span id="message"></span></h2> |
|||
</div> |
|||
|
|||
<div class="row"> |
|||
<div class="col text-center"><span class="text-value data-hora" id="date"></span></div> |
|||
<div class="col text-center"><span class="text-value data-hora" id="relogio"></span></div> |
|||
</div> |
|||
|
|||
|
|||
<div class=""> |
|||
<div class="d-flex justify-content-start"> |
|||
<div class="col-md-4"> |
|||
<div class="text-center painel"> |
|||
<h2 class="text-subtitle">Parlamentares</h2> |
|||
<span id="parlamentares" class="text-value text-center"></span> |
|||
</div> |
|||
</div> |
|||
<div class="d-flex col-md-8 painels"> |
|||
<div class="col-md-6 text-center painel" id="aparecer_oradores"> |
|||
<h2 class="text-subtitle">Oradores</h2> |
|||
<span id="orador"></span> |
|||
</div> |
|||
|
|||
<div class="col-md-6 text-left painel"> |
|||
<div class="d-flex align-items-left justify-content-left mb-2"> |
|||
<h2 class="text-subtitle mb-0">Cronômetros</h2> |
|||
<button class="btn btn-sm btn-secondary ms-2" onclick="changeFontSize('box_cronometros', -1)"> |
|||
A- |
|||
</button> |
|||
<button class="btn btn-sm btn-secondary ms-2" onclick="changeFontSize('box_cronometros', 1)"> |
|||
A+ |
|||
</button> |
|||
</div> |
|||
<div class="text-value" id="box_cronometros"> |
|||
Discurso: <span id="cronometro_discurso"></span><br> |
|||
Aparte: <span id="cronometro_aparte"></span><br> |
|||
Questão de Ordem: <span id="cronometro_ordem"></span><br> |
|||
Considerações Finais: <span id="cronometro_consideracoes"></span> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="col-md-6 text-left painel" id="resultado_votacao_div"> |
|||
<div class="d-flex align-items-left justify-content-left mb-2"> |
|||
<h2 class="text-subtitle mb-0">Resultado</h2> |
|||
<button class="btn btn-sm btn-secondary ms-2" onclick="changeFontSize('box_votacao', -1)"> |
|||
A- |
|||
</button> |
|||
<button class="btn btn-sm btn-secondary ms-2" onclick="changeFontSize('box_votacao', 1)"> |
|||
A+ |
|||
</button> |
|||
</div> |
|||
<div id="box_votacao"> |
|||
<span id="votacao" class="text-value"></span> |
|||
<span id="resultado_votacao" lass="text-title"></span> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="col-md-6 text-center painel" id="obs_materia_div"> |
|||
<h2 class="text-subtitle" id="mat_em_votacao">Matéria em Votação</h2> |
|||
<span id="materia_legislativa_texto" class="text-value"></span> |
|||
<br> |
|||
<span id="materia_legislativa_ementa" class="text-value"></span> |
|||
<br> |
|||
<span id="observacao_materia" class="text-value"></span> |
|||
</div> |
|||
|
|||
<div class="col-md-6 text-center painel" id="tema_solene_div" style="display: none"> |
|||
<h2 class="text-subtitle">Tema da Sessão Solene</h2> |
|||
<span id="sessao_solene_tema" class="text-value"></span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</body> |
|||
{% block webpack_loader_js %} |
|||
{% render_chunk_vendors 'js' %} |
|||
{% render_bundle 'global' 'js' %} |
|||
{% render_bundle 'painel' 'js' %} |
|||
{% endblock webpack_loader_js %} |
|||
|
|||
{% block webpack_loader_chunks_js %} |
|||
{% endblock webpack_loader_chunks_js %} |
|||
<script> |
|||
const controllerId = "{{ controller_id }}"; |
|||
const proto = location.protocol === "https:" ? "wss" : "ws"; |
|||
let ws, backoff = 500, timer; |
|||
const url = `${proto}://${location.host}/ws/painel/${controllerId}/` |
|||
|
|||
function update_view(data) { |
|||
$("#sessao_plenaria").text(data["sessao_plenaria"]) |
|||
$("#sessao_plenaria_data").text("Data Início: " + data["sessao_plenaria_data"]) |
|||
$("#sessao_plenaria_hora_inicio").text("Hora Início: " + data["sessao_plenaria_hora_inicio"]) |
|||
$("#sessao_solene_tema").text(data["tema_solene"]) |
|||
if (data["status_painel"] == false) { |
|||
$("#message").text("PAINEL ENCONTRA-SE FECHADO"); |
|||
} |
|||
else { |
|||
$("#message").text(""); |
|||
} |
|||
|
|||
if (data["sessao_solene"]){ |
|||
$("#resultado_votacao_div").hide(); |
|||
$("#obs_materia_div").hide(); |
|||
$('#tema_solene_div').show(); |
|||
} |
|||
|
|||
if (data["brasao"] != null) |
|||
$("#logo-painel").attr("src", data["brasao"]); |
|||
|
|||
var presentes = $("#parlamentares"); |
|||
var votacao = $("#votacao"); |
|||
var oradores = $("#orador") |
|||
$("#votacao").text(''); |
|||
presentes.children().remove(); |
|||
votacao.children().remove(); |
|||
oradores.children().remove(); |
|||
|
|||
var oradores_list = data["oradores"]; |
|||
var presentes_list = data["presentes"]; |
|||
|
|||
if (data["status_painel"] == true) { |
|||
mostrar_voto = data["mostrar_voto"]; |
|||
presentes.append('<table id="parlamentares_list">'); |
|||
$.each(presentes_list, function (index, parlamentar) { |
|||
|
|||
|
|||
if (parlamentar.voto == 'Voto Informado' && mostrar_voto == false){ |
|||
$('#parlamentares_list').append('<tr><td style="padding-right:20px; color:yellow" >' + |
|||
parlamentar.nome + |
|||
'</td> <td style="padding-right:20px; color:yellow">' + |
|||
parlamentar.partido + '</td> <td style="padding-right:20px; color:yellow">' |
|||
+ '</td></tr>') |
|||
} |
|||
else{ |
|||
$('#parlamentares_list').append(show_voto(parlamentar)) |
|||
} |
|||
|
|||
}); |
|||
presentes.append('</table>') |
|||
|
|||
if (data["oradores"].length > 0){ |
|||
$('#aparecer_oradores').show(); |
|||
oradores.append('<table id="oradores_list">'); |
|||
$.each(oradores_list, function (index, orador) { |
|||
$('#oradores_list').append('<tr><td style="padding-right:20px; color:white" >' + |
|||
orador.numero + 'º  ' + |
|||
orador.nome +'</td></tr>') |
|||
}); |
|||
oradores.append('</table>'); |
|||
} |
|||
else { |
|||
$('#aparecer_oradores').hide(); |
|||
} |
|||
} |
|||
else{ |
|||
presentes.append('<span style="color:white" id="parlamentares_list">'); |
|||
$('#parlamentares_list').append( |
|||
'<center>A listagem de parlamentares só aparecerá quando o painel estiver aberto.</center>') |
|||
presentes.append('</span>'); |
|||
|
|||
oradores.append('<span style="color:white" id="oradores_list">'); |
|||
$('#oradores_list').append( |
|||
'<center>A listagem de oradores só aparecerá quando o painel estiver aberto.</center>') |
|||
oradores.append('</span>'); |
|||
|
|||
votacao.append('<span id="votacao">'); |
|||
$("#votacao").append('<center>A votação só aparecerá quando o painel estiver aberto</center>'); |
|||
votacao.append('</span>'); |
|||
} |
|||
|
|||
if(data["status_painel"]){ |
|||
if (data['materia_legislativa_texto']){ |
|||
var votacao = $("#votacao"); |
|||
|
|||
votacao.append("<li>Sim: " + data["numero_votos_sim"] + "</li>"); |
|||
votacao.append("<li>Não: " + data["numero_votos_nao"] + "</li>"); |
|||
votacao.append("<li>Abstenções: " + data["numero_abstencoes"] + "</li>"); |
|||
votacao.append("<li>Presentes: " + data["num_presentes"] + "</li>"); |
|||
votacao.append("<li>Total votos: " + data["total_votos"] + "</li>"); |
|||
} |
|||
else{ |
|||
$("#votacao").append('<center>Não há votação, pois não há nenhuma matéria aberta ou já votada.</center>'); |
|||
} |
|||
} |
|||
|
|||
var discurso_current = data["cronometro_discurso"]; |
|||
if (!discurso_previous){ |
|||
discurso_previous = '' |
|||
} |
|||
|
|||
if (discurso_current != discurso_previous) { |
|||
$('#cronometro_discurso').runner(discurso_current); |
|||
discurso_previous = discurso_current; |
|||
} |
|||
|
|||
var aparte_current = data["cronometro_aparte"]; |
|||
if (!aparte_previous){ |
|||
aparte_previous = '' |
|||
} |
|||
|
|||
if (aparte_current != aparte_previous) { |
|||
$('#cronometro_aparte').runner(aparte_current); |
|||
aparte_previous = aparte_current; |
|||
} |
|||
|
|||
var ordem_current = data["cronometro_ordem"]; |
|||
if (!ordem_previous){ |
|||
ordem_previous = '' |
|||
} |
|||
|
|||
if (ordem_current != ordem_previous) { |
|||
$('#cronometro_ordem').runner(ordem_current); |
|||
ordem_previous = ordem_current; |
|||
} |
|||
|
|||
var consideracoes_current = data["cronometro_consideracoes"]; |
|||
if (!consideracoes_previous){ |
|||
consideracoes_previous = '' |
|||
} |
|||
|
|||
if (consideracoes_current != consideracoes_previous) { |
|||
$('#cronometro_consideracoes').runner(consideracoes_current); |
|||
consideracoes_previous = consideracoes_current; |
|||
} |
|||
|
|||
if($('#cronometro_discurso').runner('info').formattedTime == "00:00:30") { |
|||
audioAlertFinish.play(); |
|||
} |
|||
|
|||
if($('#cronometro_aparte').runner('info').formattedTime == "00:00:30") { |
|||
audioAlertFinish.play(); |
|||
} |
|||
|
|||
if($('#cronometro_ordem').runner('info').formattedTime == "00:00:30") { |
|||
audioAlertFinish.play(); |
|||
} |
|||
|
|||
if($('#cronometro_consideracoes').runner('info').formattedTime == "00:00:30") { |
|||
audioAlertFinish.play(); |
|||
} |
|||
|
|||
if(data['sessao_finalizada']){ |
|||
$("#obs_materia_div").hide(); |
|||
$("#resultado_votacao_div").hide(); |
|||
} |
|||
else if (data['materia_legislativa_texto']){ |
|||
if (data["status_painel"] == true){ |
|||
$("#materia_legislativa_texto").text(data["materia_legislativa_texto"]); |
|||
$("#materia_legislativa_ementa").text(data["materia_legislativa_ementa"]); |
|||
} |
|||
else{ |
|||
$("#materia_legislativa_texto").text('A Matéria em votação só aparecerá quando o painel estiver aberto'); |
|||
} |
|||
} |
|||
else{ |
|||
$("#materia_legislativa_texto").text('Não há nenhuma matéria votada ou para votação.'); |
|||
} |
|||
|
|||
if (data['observacao_materia'] && data["status_painel"] == true){ |
|||
var texto = data['observacao_materia']; |
|||
if(texto.length > 151) { |
|||
$("#observacao_materia").text(texto.substr(0, 145).concat('(...)')); |
|||
} |
|||
else{ |
|||
$("#observacao_materia").text(texto); |
|||
} |
|||
} |
|||
else{ |
|||
$("#observacao_materia").text(''); |
|||
} |
|||
if (data['tipo_resultado'] && data['status_painel'] == true){ |
|||
if(data['tipo_votacao'] != 'Leitura' && !data['sessao_finalizada'] && !data["sessao_solene"]){ |
|||
$("#resultado_votacao").css("color", "#45919D"); |
|||
$("#mat_em_votacao").text("Matéria em Votação"); |
|||
$("#resultado_votacao_div").show(); |
|||
} |
|||
else{ |
|||
$("#resultado_votacao_div").hide(); |
|||
$("#mat_em_votacao").text("Matéria em Leitura"); |
|||
} |
|||
console.log(data["tipo_resultado"], data['tipo_votacao']); |
|||
$("#resultado_votacao").text(data["tipo_resultado"]); |
|||
|
|||
var resultado_votacao_upper = $("#resultado_votacao").text().toUpperCase(); |
|||
console.log(resultado_votacao_upper, data['tipo_resultado']); |
|||
if (resultado_votacao_upper.search("APROV") != -1){ |
|||
$("#resultado_votacao").css("color", "#7CFC00"); |
|||
$("#mat_em_votacao").text("Matéria Votada"); |
|||
} |
|||
else if (resultado_votacao_upper.search("REJEIT") != -1){ |
|||
$("#resultado_votacao").css("color", "red"); |
|||
$("#mat_em_votacao").text("Matéria Votada"); |
|||
} |
|||
else if (resultado_votacao_upper.search("LIDA") != -1){ |
|||
$("#mat_em_votacao").text("Matéria Lida"); |
|||
} |
|||
} |
|||
else{ |
|||
$("#resultado_votacao").text(''); |
|||
if(data['tipo_votacao'] != 'Leitura') |
|||
$("#mat_em_votacao").text("Matéria em Votação"); |
|||
else{ |
|||
$("#mat_em_votacao").text("Matéria em Leitura"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
function connect() { |
|||
console.log(`Connecting to ${url}...`); |
|||
const ws = new WebSocket(url); |
|||
let lastPong = Date.now(), pingTimer; |
|||
|
|||
// When the connection is established |
|||
ws.onopen = function () { |
|||
console.log("✅ WebSocket connected"); |
|||
// Optionally send an initial message to the server |
|||
ws.send(JSON.stringify({ type: "hello", message: "Client connected" })); |
|||
|
|||
// Ping keep-alive |
|||
pingTimer = setInterval(() => { |
|||
if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({type:"ping", ts:Date.now()})); |
|||
if (Date.now() - lastPong > 75000) ws.close(); // force reconnect |
|||
}, 30000); |
|||
}; |
|||
|
|||
ws.onmessage = function (event) { |
|||
try { |
|||
const data = JSON.parse(event.data); |
|||
console.log(data); |
|||
if (data.type === "pong") lastPong = Date.now(); |
|||
if (data.type === "data") { |
|||
console.log("📩 Message from server:", data); |
|||
update_view(data); |
|||
//if (data.text) { |
|||
// document.getElementById("output").textContent = data.text; |
|||
//} |
|||
} |
|||
} catch (e) { |
|||
console.error(e); |
|||
} |
|||
}; |
|||
|
|||
// When the connection closes |
|||
ws.onclose = function (event) { |
|||
console.log("❌ WebSocket closed:", event); |
|||
|
|||
clearInterval(pingTimer); |
|||
clearTimeout(timer); |
|||
|
|||
// retry with capped exponential backoff |
|||
timer = setTimeout(connect, Math.min(backoff, 10000)); |
|||
backoff *= 2; |
|||
}; |
|||
|
|||
// When an error occurs |
|||
ws.onerror = function (error) { |
|||
console.error("⚠️ WebSocket error:", event); |
|||
}; |
|||
} |
|||
|
|||
// ENTRYPOINT |
|||
connect(); |
|||
</script> |
|||
</html> |
|||
@ -0,0 +1,6 @@ |
|||
# DEBUG |
|||
daphne -b 127.0.0.1 -p 8000 sapl.asgi:application |
|||
|
|||
# Redis-CLI |
|||
redis-cli -h localhost -p 6379 |
|||
|
|||
Loading…
Reference in new issue