Update app.py
Browse files
app.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
# -*- coding: utf-8 -*-
|
|
|
|
| 2 |
import streamlit as st
|
| 3 |
from dotenv import load_dotenv
|
| 4 |
from datetime import date, datetime, time
|
|
@@ -57,7 +58,7 @@ from login import login
|
|
| 57 |
from utils_permissoes import verificar_permissao
|
| 58 |
from utils_layout import exibir_logo
|
| 59 |
from modules_map import MODULES
|
| 60 |
-
from banco import engine, Base, SessionLocal
|
| 61 |
from models import QuizPontuacao
|
| 62 |
from models import IOIRunSugestao
|
| 63 |
from models import AvisoGlobal
|
|
@@ -536,6 +537,46 @@ def _show_birthday_banner_if_needed():
|
|
| 536 |
unsafe_allow_html=True
|
| 537 |
)
|
| 538 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 539 |
# ===============================
|
| 540 |
# MAIN
|
| 541 |
# ===============================
|
|
@@ -551,15 +592,18 @@ def main():
|
|
| 551 |
st.session_state.user_responses_viewed = False
|
| 552 |
if "nav_target" not in st.session_state:
|
| 553 |
st.session_state.nav_target = None
|
| 554 |
-
|
| 555 |
-
# ✅ Estado do intervalo de autoatualização (padrão aumentado p/ 60s; 0 = desligado)
|
| 556 |
st.session_state.setdefault("__auto_refresh_interval_sec__", 60)
|
|
|
|
| 557 |
|
| 558 |
# LOGIN
|
| 559 |
if not st.session_state.logado:
|
| 560 |
st.session_state.quiz_verificado = False
|
| 561 |
exibir_logo(top=True, sidebar=False)
|
| 562 |
login()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 563 |
return
|
| 564 |
|
| 565 |
# 👥 Heartbeat + Badge de usuários logados (APENAS ADMIN)
|
|
@@ -580,14 +624,27 @@ def main():
|
|
| 580 |
unsafe_allow_html=True
|
| 581 |
)
|
| 582 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 583 |
# 🔄 Botão de Recarregar (mantém a sessão ativa) + ⏱️ Controle do intervalo
|
| 584 |
st.sidebar.markdown("---")
|
| 585 |
-
# Linha com botão de recarregar e popover para o intervalo
|
| 586 |
col_reload, col_interval = st.sidebar.columns([1, 1])
|
| 587 |
if col_reload.button("🔄 Recarregar (sem sair)", key="__btn_reload_now__"):
|
| 588 |
st.rerun()
|
| 589 |
|
| 590 |
-
# Popover (se disponível) para configurar intervalo; fallback para expander
|
| 591 |
if hasattr(st, "popover"):
|
| 592 |
with col_interval.popover("⏱️ Autoatualização"):
|
| 593 |
new_val = st.number_input(
|
|
@@ -690,13 +747,11 @@ def main():
|
|
| 690 |
|
| 691 |
# --- Usuário: respostas novas (após último 'visto') ---
|
| 692 |
if perfil != "admin":
|
| 693 |
-
# Última vez que o usuário realmente abriu e visualizou as respostas
|
| 694 |
last_seen_dt = st.session_state.get("__user_last_answer_seen__")
|
| 695 |
|
| 696 |
try:
|
| 697 |
db = _get_db_session()
|
| 698 |
|
| 699 |
-
# Qual é a resposta mais recente existente
|
| 700 |
last_answer_dt_row = (
|
| 701 |
db.query(IOIRunSugestao.data_resposta)
|
| 702 |
.filter(
|
|
@@ -709,11 +764,9 @@ def main():
|
|
| 709 |
)
|
| 710 |
last_answer_dt = last_answer_dt_row[0] if last_answer_dt_row else None
|
| 711 |
|
| 712 |
-
# Se há algo mais novo do que o 'visto', marcamos como não visto
|
| 713 |
if last_answer_dt and (not last_seen_dt or last_answer_dt > last_seen_dt):
|
| 714 |
st.session_state.user_responses_viewed = False
|
| 715 |
|
| 716 |
-
# ✅ Conta SOMENTE respostas novas (depois do 'last_seen_dt')
|
| 717 |
novas_respostas = (
|
| 718 |
db.query(IOIRunSugestao)
|
| 719 |
.filter(
|
|
@@ -731,7 +784,6 @@ def main():
|
|
| 731 |
except Exception:
|
| 732 |
pass
|
| 733 |
|
| 734 |
-
# ✅ Exibir card de nova mensagem até o usuário clicar em "Ver respostas"
|
| 735 |
if novas_respostas > 0 and not st.session_state.get("user_responses_viewed", False):
|
| 736 |
st.sidebar.markdown(
|
| 737 |
"""
|
|
@@ -744,7 +796,6 @@ def main():
|
|
| 744 |
unsafe_allow_html=True
|
| 745 |
)
|
| 746 |
|
| 747 |
-
# (Opcional) Toast discreto — aparece uma única vez por sessão enquanto houver novidade
|
| 748 |
if not st.session_state.get("__user_toast_shown__"):
|
| 749 |
try:
|
| 750 |
st.toast("Você tem novas respostas do IOI‑RUN. Clique em '📥 Ver respostas'.", icon="💬")
|
|
@@ -753,12 +804,10 @@ def main():
|
|
| 753 |
st.session_state["__user_toast_shown__"] = True
|
| 754 |
|
| 755 |
if st.sidebar.button("📥 Ver respostas"):
|
| 756 |
-
# Não atualizamos last_seen aqui; isso é feito dentro do módulo do usuário
|
| 757 |
st.session_state.nav_target = "sugestoes_ioirun"
|
| 758 |
st.session_state.user_responses_viewed = True
|
| 759 |
st.rerun()
|
| 760 |
else:
|
| 761 |
-
# Se não há novidades, libera o toast para a próxima vez que houver
|
| 762 |
st.session_state["__user_toast_shown__"] = False
|
| 763 |
|
| 764 |
# ------------------------- Menu lateral -------------------------
|
|
@@ -888,7 +937,6 @@ def main():
|
|
| 888 |
is_recebimento = (pagina_id == "recebimento")
|
| 889 |
interval_sec = int(st.session_state.get("__auto_refresh_interval_sec__", 60))
|
| 890 |
if (interval_sec > 0) and not (is_inbox_admin or is_outlook_rel or is_formulario or is_recebimento):
|
| 891 |
-
# key dinâmica por intervalo evita conflitos ao trocar o valor
|
| 892 |
st_autorefresh(interval=interval_sec * 1000, key=f"sidebar_autorefresh_{interval_sec}s")
|
| 893 |
except Exception:
|
| 894 |
pass
|
|
@@ -993,6 +1041,7 @@ Selecione o módulo abaixo ou navegue pelo menu — o conteúdo ajusta automatic
|
|
| 993 |
st.session_state["nav_target"] = None
|
| 994 |
st.session_state["__nav_lock__"] = False
|
| 995 |
|
|
|
|
| 996 |
if __name__ == "__main__":
|
| 997 |
main()
|
| 998 |
# -------------------------
|
|
@@ -1018,4 +1067,4 @@ if __name__ == "__main__":
|
|
| 1018 |
</p>
|
| 1019 |
""",
|
| 1020 |
unsafe_allow_html=True
|
| 1021 |
-
)
|
|
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
+
import os
|
| 3 |
import streamlit as st
|
| 4 |
from dotenv import load_dotenv
|
| 5 |
from datetime import date, datetime, time
|
|
|
|
| 58 |
from utils_permissoes import verificar_permissao
|
| 59 |
from utils_layout import exibir_logo
|
| 60 |
from modules_map import MODULES
|
| 61 |
+
from banco import engine, Base, SessionLocal, db_info
|
| 62 |
from models import QuizPontuacao
|
| 63 |
from models import IOIRunSugestao
|
| 64 |
from models import AvisoGlobal
|
|
|
|
| 537 |
unsafe_allow_html=True
|
| 538 |
)
|
| 539 |
|
| 540 |
+
|
| 541 |
+
# ===============================
|
| 542 |
+
# 🔎 Painel de Diagnóstico de Autenticação (Admin)
|
| 543 |
+
# ===============================
|
| 544 |
+
def _render_auth_diag_panel():
|
| 545 |
+
"""Renderiza informações de diagnóstico de autenticação (apenas Admin)."""
|
| 546 |
+
try:
|
| 547 |
+
info = db_info()
|
| 548 |
+
except Exception:
|
| 549 |
+
info = {"url": "(indisponível)", "using_router": _HAS_ROUTER}
|
| 550 |
+
|
| 551 |
+
st.sidebar.markdown("### 🔎 Diagnóstico de Autenticação")
|
| 552 |
+
st.sidebar.caption(f"DISABLE_AUTH = {os.getenv('DISABLE_AUTH')}")
|
| 553 |
+
st.sidebar.caption(f"ALLOW_EMERGENCY_LOGIN = {os.getenv('ALLOW_EMERGENCY_LOGIN')}")
|
| 554 |
+
st.sidebar.caption(f"EMERG_USER set? = {bool(os.getenv('EMERG_USER'))}")
|
| 555 |
+
st.sidebar.caption(f"EMERG_PASS_BCRYPT set? = {bool(os.getenv('EMERG_PASS_BCRYPT'))}")
|
| 556 |
+
st.sidebar.caption(f"DEMO_USER = {os.getenv('DEMO_USER') or '∅'}")
|
| 557 |
+
st.sidebar.caption(f"DEMO_PERFIL = {os.getenv('DEMO_PERFIL') or '∅'}")
|
| 558 |
+
st.sidebar.caption(f"DEMO_EMAIL = {os.getenv('DEMO_EMAIL') or '∅'}")
|
| 559 |
+
|
| 560 |
+
st.sidebar.caption(f"Router habilitado = {_HAS_ROUTER}")
|
| 561 |
+
try:
|
| 562 |
+
_b_label = bank_label(current_db_choice()) if _HAS_ROUTER else (
|
| 563 |
+
"🟢 Produção" if current_db_choice() == "prod" else "🔴 Teste"
|
| 564 |
+
)
|
| 565 |
+
st.sidebar.caption(f"Banco ativo (label) = {_b_label}")
|
| 566 |
+
except Exception as e:
|
| 567 |
+
st.sidebar.caption(f"Banco ativo (erro) = {e}")
|
| 568 |
+
|
| 569 |
+
st.sidebar.caption(f"DB URL = {info.get('url')}")
|
| 570 |
+
st.sidebar.caption(f"SessionState.logado = {st.session_state.get('logado')}")
|
| 571 |
+
st.sidebar.caption(f"SessionState.usuario = {st.session_state.get('usuario')}")
|
| 572 |
+
st.sidebar.caption(f"SessionState.perfil = {st.session_state.get('perfil')}")
|
| 573 |
+
try:
|
| 574 |
+
from login import login as _login_test # noqa: F401
|
| 575 |
+
st.sidebar.success("login.py importado ✅")
|
| 576 |
+
except Exception as e:
|
| 577 |
+
st.sidebar.error(f"Falha ao importar login.py: {e}")
|
| 578 |
+
|
| 579 |
+
|
| 580 |
# ===============================
|
| 581 |
# MAIN
|
| 582 |
# ===============================
|
|
|
|
| 592 |
st.session_state.user_responses_viewed = False
|
| 593 |
if "nav_target" not in st.session_state:
|
| 594 |
st.session_state.nav_target = None
|
|
|
|
|
|
|
| 595 |
st.session_state.setdefault("__auto_refresh_interval_sec__", 60)
|
| 596 |
+
st.session_state.setdefault("__auth_diag__", False) # 🔧 estado do painel de diagnóstico (Admin)
|
| 597 |
|
| 598 |
# LOGIN
|
| 599 |
if not st.session_state.logado:
|
| 600 |
st.session_state.quiz_verificado = False
|
| 601 |
exibir_logo(top=True, sidebar=False)
|
| 602 |
login()
|
| 603 |
+
|
| 604 |
+
# ⚠️ Opcional: dica breve quando nenhum caminho de auth está definido
|
| 605 |
+
if not os.getenv("DISABLE_AUTH") and not os.getenv("ALLOW_EMERGENCY_LOGIN"):
|
| 606 |
+
st.info("🔒 Autenticação ativa. Para testes rápidos no Spaces, defina **DISABLE_AUTH=1** ou habilite o **login emergencial** (ALLOW_EMERGENCY_LOGIN=1 + EMERG_*). Depois, **Restart this Space**.")
|
| 607 |
return
|
| 608 |
|
| 609 |
# 👥 Heartbeat + Badge de usuários logados (APENAS ADMIN)
|
|
|
|
| 624 |
unsafe_allow_html=True
|
| 625 |
)
|
| 626 |
|
| 627 |
+
# 🔘 Botão Admin: habilitar/desabilitar painel de diagnóstico
|
| 628 |
+
st.sidebar.markdown("---")
|
| 629 |
+
if st.session_state.get("__auth_diag__"):
|
| 630 |
+
if st.sidebar.button("🧪 Desativar diagnóstico de login (Admin)"):
|
| 631 |
+
st.session_state["__auth_diag__"] = False
|
| 632 |
+
st.rerun()
|
| 633 |
+
else:
|
| 634 |
+
if st.sidebar.button("🧪 Ativar diagnóstico de login (Admin)"):
|
| 635 |
+
st.session_state["__auth_diag__"] = True
|
| 636 |
+
st.rerun()
|
| 637 |
+
|
| 638 |
+
# Render do painel (se ativo)
|
| 639 |
+
if st.session_state.get("__auth_diag__"):
|
| 640 |
+
_render_auth_diag_panel()
|
| 641 |
+
|
| 642 |
# 🔄 Botão de Recarregar (mantém a sessão ativa) + ⏱️ Controle do intervalo
|
| 643 |
st.sidebar.markdown("---")
|
|
|
|
| 644 |
col_reload, col_interval = st.sidebar.columns([1, 1])
|
| 645 |
if col_reload.button("🔄 Recarregar (sem sair)", key="__btn_reload_now__"):
|
| 646 |
st.rerun()
|
| 647 |
|
|
|
|
| 648 |
if hasattr(st, "popover"):
|
| 649 |
with col_interval.popover("⏱️ Autoatualização"):
|
| 650 |
new_val = st.number_input(
|
|
|
|
| 747 |
|
| 748 |
# --- Usuário: respostas novas (após último 'visto') ---
|
| 749 |
if perfil != "admin":
|
|
|
|
| 750 |
last_seen_dt = st.session_state.get("__user_last_answer_seen__")
|
| 751 |
|
| 752 |
try:
|
| 753 |
db = _get_db_session()
|
| 754 |
|
|
|
|
| 755 |
last_answer_dt_row = (
|
| 756 |
db.query(IOIRunSugestao.data_resposta)
|
| 757 |
.filter(
|
|
|
|
| 764 |
)
|
| 765 |
last_answer_dt = last_answer_dt_row[0] if last_answer_dt_row else None
|
| 766 |
|
|
|
|
| 767 |
if last_answer_dt and (not last_seen_dt or last_answer_dt > last_seen_dt):
|
| 768 |
st.session_state.user_responses_viewed = False
|
| 769 |
|
|
|
|
| 770 |
novas_respostas = (
|
| 771 |
db.query(IOIRunSugestao)
|
| 772 |
.filter(
|
|
|
|
| 784 |
except Exception:
|
| 785 |
pass
|
| 786 |
|
|
|
|
| 787 |
if novas_respostas > 0 and not st.session_state.get("user_responses_viewed", False):
|
| 788 |
st.sidebar.markdown(
|
| 789 |
"""
|
|
|
|
| 796 |
unsafe_allow_html=True
|
| 797 |
)
|
| 798 |
|
|
|
|
| 799 |
if not st.session_state.get("__user_toast_shown__"):
|
| 800 |
try:
|
| 801 |
st.toast("Você tem novas respostas do IOI‑RUN. Clique em '📥 Ver respostas'.", icon="💬")
|
|
|
|
| 804 |
st.session_state["__user_toast_shown__"] = True
|
| 805 |
|
| 806 |
if st.sidebar.button("📥 Ver respostas"):
|
|
|
|
| 807 |
st.session_state.nav_target = "sugestoes_ioirun"
|
| 808 |
st.session_state.user_responses_viewed = True
|
| 809 |
st.rerun()
|
| 810 |
else:
|
|
|
|
| 811 |
st.session_state["__user_toast_shown__"] = False
|
| 812 |
|
| 813 |
# ------------------------- Menu lateral -------------------------
|
|
|
|
| 937 |
is_recebimento = (pagina_id == "recebimento")
|
| 938 |
interval_sec = int(st.session_state.get("__auto_refresh_interval_sec__", 60))
|
| 939 |
if (interval_sec > 0) and not (is_inbox_admin or is_outlook_rel or is_formulario or is_recebimento):
|
|
|
|
| 940 |
st_autorefresh(interval=interval_sec * 1000, key=f"sidebar_autorefresh_{interval_sec}s")
|
| 941 |
except Exception:
|
| 942 |
pass
|
|
|
|
| 1041 |
st.session_state["nav_target"] = None
|
| 1042 |
st.session_state["__nav_lock__"] = False
|
| 1043 |
|
| 1044 |
+
|
| 1045 |
if __name__ == "__main__":
|
| 1046 |
main()
|
| 1047 |
# -------------------------
|
|
|
|
| 1067 |
</p>
|
| 1068 |
""",
|
| 1069 |
unsafe_allow_html=True
|
| 1070 |
+
)
|