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