Roudrigus commited on
Commit
8ad7bb5
·
verified ·
1 Parent(s): b31dde2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +85 -48
app.py CHANGED
@@ -71,13 +71,12 @@ from sqlalchemy import text, func, or_
71
  # 🗄️ Banco ativo (Produção/Teste/Treinamento)
72
  # Tentativa de importar utilitários do db_router, com fallback seguro.
73
  try:
74
- from db_router import current_db_choice, bank_label # já usados no seu app
75
  _HAS_ROUTER = True
76
  except Exception:
77
  _HAS_ROUTER = False
78
 
79
  def current_db_choice() -> str:
80
- # Fallback simples
81
  return st.session_state.get("__db_choice_override__", "prod")
82
 
83
  def bank_label(choice: str) -> str:
@@ -125,7 +124,7 @@ if os.getenv("CLEAR_CACHE_ON_START", "0") == "1":
125
  except Exception:
126
  pass
127
 
128
- # Rodar init_db.run() uma única vez por start de container
129
  try:
130
  import init_db as _init_db
131
  _HAS_INIT_DB = hasattr(_init_db, "run")
@@ -149,17 +148,15 @@ if os.getenv("INIT_DB_ON_START", "0") == "1" and _HAS_INIT_DB:
149
  pass
150
 
151
  # ===============================
152
- # RERUN por querystring (atalho ?rr=1)
153
  # ===============================
154
  def _get_query_params():
155
  """Compat: retorna query params como dict (Streamlit novo/antigo)."""
156
  try:
157
- # Streamlit >= 1.32
158
- return dict(st.query_params)
159
  except Exception:
160
- # Streamlit antigo (experimental)
161
  try:
162
- return dict(st.experimental_get_query_params())
163
  except Exception:
164
  return {}
165
 
@@ -173,14 +170,32 @@ def _set_query_params(new_params: dict):
173
  except Exception:
174
  pass
175
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  # 🔒 ANTI-TREMOR PATCH: não consumir rr=1 quando login/quiz ainda não concluídos
177
  def _check_rerun_qs(pagina_atual: str = ""):
178
  """
179
  Se a URL contiver rr=1 (ou true), força um rerun e limpa o parâmetro para evitar loop.
180
- Não dispara quando estiver na página 'resposta' (Inbox Admin).
181
- ✅ (PATCH) Também não dispara quando estiver em 'outlook_relatorio' para não interromper leitura COM.
182
- ✅ (PATCH) Não dispara quando estiver em 'formulario'.
183
- ✅ (ANTI-TREMOR) Não dispara enquanto login/quiz não concluídos (para não mexer na logo).
184
  """
185
  try:
186
  if st.session_state.get("__qs_rr_consumed__", False):
@@ -192,7 +207,7 @@ def _check_rerun_qs(pagina_atual: str = ""):
192
 
193
  # Evita rr=1 em módulos sensíveis a rerun/refresh
194
  if pagina_atual in ("resposta", "outlook_relatorio", "formulario"):
195
- return # evita 'piscar' e cancelamentos
196
 
197
  params = _get_query_params()
198
  rr_raw = params.get("rr", ["0"])
@@ -229,15 +244,6 @@ def _get_db_session():
229
  # 3) Fallback
230
  return SessionLocal()
231
 
232
- # ===============================
233
- # CONFIGURAÇÃO INICIAL
234
- # ===============================
235
- # Criação do schema (sem derrubar a app se o banco ainda não estiver pronto)
236
- try:
237
- Base.metadata.create_all(bind=engine)
238
- except Exception as e:
239
- st.sidebar.warning(f"Schema não foi criado automaticamente: {e}")
240
-
241
  def quiz_respondido_hoje(usuario: str) -> bool:
242
  # ✅ Usar sessão ciente do ambiente
243
  db = _get_db_session()
@@ -758,10 +764,23 @@ def _db_choice_ui():
758
  st.toast(f"Banco alterado para: {_lbl(sel_code)}", icon="🗄️")
759
  st.rerun()
760
 
 
 
 
 
 
 
 
 
 
 
761
  # ===============================
762
  # MAIN
763
  # ===============================
764
  def main():
 
 
 
765
  # Estados iniciais
766
  if "logado" not in st.session_state:
767
  st.session_state.logado = False
@@ -779,7 +798,7 @@ def main():
779
  # ===== Seleção de banco (sempre visível na sidebar) =====
780
  _db_choice_ui()
781
 
782
- # 🔧 Criar/atualizar schema no banco selecionado (útil após trocar p/ Teste/Treinamento)
783
  try:
784
  from banco import init_schema
785
  with st.sidebar.expander("⚙️ Manutenção do banco atual", expanded=False):
@@ -789,18 +808,37 @@ def main():
789
  st.sidebar.success("Schema criado/atualizado no banco selecionado com sucesso.")
790
  except Exception as e:
791
  st.sidebar.error(f"Falha ao criar/atualizar schema: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
792
  except Exception:
793
- # Se o módulo não expor init_schema, apenas ignora silenciosamente
794
  pass
795
 
 
 
 
 
 
 
796
  # LOGIN
797
  if not st.session_state.logado:
798
  st.session_state.quiz_verificado = False
799
  # 🔒 ANTI-TREMOR: renderiza logo top apenas 1x
800
  exibir_logo_once(top=True, sidebar=False)
801
  login()
802
-
803
- # 🔕 REMOVIDO: mensagem de dica de autenticação no Spaces (conforme solicitado)
804
  return
805
 
806
  # 👥 Heartbeat + Badge de usuários logados (APENAS ADMIN)
@@ -824,11 +862,11 @@ def main():
824
  # 🔘 Botão Admin: habilitar/desabilitar painel de diagnóstico
825
  st.sidebar.markdown("---")
826
  if st.session_state.get("__auth_diag__"):
827
- if st.sidebar.button("🧪 Desativar diagnóstico de login (Admin)"):
828
  st.session_state["__auth_diag__"] = False
829
  st.rerun()
830
  else:
831
- if st.sidebar.button("🧪 Ativar diagnóstico de login (Admin)"):
832
  st.session_state["__auth_diag__"] = True
833
  st.rerun()
834
 
@@ -841,14 +879,14 @@ def main():
841
  # ============================
842
  from sqlalchemy import inspect # import local para evitar custo desnecessário
843
  with st.sidebar.expander("🧪 Diagnóstico Profundo do Banco", expanded=False):
844
- db = _get_db_session()
845
  try:
846
- eng = db.bind
847
  st.caption(f"Engine URL: {eng.url}")
848
 
849
  # 1) SELECT 1
850
  try:
851
- db.execute(text("SELECT 1"))
852
  st.success("SELECT 1 OK")
853
  except Exception as e:
854
  st.error(f"SELECT 1 falhou: {e}")
@@ -872,7 +910,7 @@ def main():
872
 
873
  # 5) Contagem
874
  try:
875
- cnt = db.execute(text(f"SELECT COUNT(*) FROM {target_table}")).fetchone()[0]
876
  st.write("Quantidade de registros:", cnt)
877
  except Exception as e:
878
  st.error(f"COUNT(*) falhou: {e}")
@@ -881,7 +919,7 @@ def main():
881
  try:
882
  sel_cols = [c for c in ["usuario", "email", "perfil", "nome"] if c in cols]
883
  sel_expr = ", ".join(sel_cols) if sel_cols else "*"
884
- amostra = db.execute(text(f"SELECT {sel_expr} FROM {target_table} LIMIT 5"))
885
  rows = [dict(r._mapping) for r in amostra]
886
  st.write("Amostra:", rows)
887
  except Exception as e:
@@ -894,7 +932,7 @@ def main():
894
  test_user = st.text_input("Usuário para testar hash", "", key="__bcrypt_user__")
895
  test_pass = st.text_input("Senha para testar hash", "", type="password", key="__bcrypt_pass__")
896
 
897
- if st.button("Testar bcrypt com este usuário", key="__btn_bcrypt_test__"):
898
  try:
899
  pass_cols = [c for c in ["senha_hash", "password_hash", "senha", "hash"] if c in cols]
900
  user_cols = [c for c in ["usuario", "username", "login"] if c in cols]
@@ -903,7 +941,7 @@ def main():
903
  else:
904
  c_pass = pass_cols[0]
905
  c_user = user_cols[0]
906
- res = db.execute(
907
  text(f"SELECT {c_user} AS u, {c_pass} AS h FROM {target_table} WHERE {c_user} = :u LIMIT 1"),
908
  {"u": test_user.strip()}
909
  ).fetchone()
@@ -926,7 +964,7 @@ def main():
926
  st.error(f"Erro no teste: {e}")
927
  finally:
928
  try:
929
- db.close()
930
  except Exception:
931
  pass
932
 
@@ -935,7 +973,7 @@ def main():
935
  # ============================
936
  with st.sidebar.expander("🧰 Manutenção (Admin)", expanded=False):
937
  col_m1, col_m2 = st.columns(2)
938
- if col_m1.button("🧹 Limpar cache agora", use_container_width=True):
939
  try:
940
  st.cache_data.clear()
941
  except Exception:
@@ -947,18 +985,17 @@ def main():
947
  st.sidebar.success("Caches limpos.")
948
  st.rerun()
949
 
950
- # Rodar init_db.run() com dupla confirmação
951
  if _HAS_INIT_DB:
952
  c1, c2 = st.columns(2)
953
- sure1 = c1.checkbox("Confirmo")
954
- sure2 = c2.checkbox("Estou ciente")
955
- if st.button("🧬 Rodar init_db.run()", use_container_width=True):
956
  if not (sure1 and sure2):
957
  st.warning("Marque as duas confirmações para executar.")
958
  else:
959
  try:
960
  _init_db.run()
961
- # Atualiza marker
962
  with open(_INIT_MARK, "w", encoding="utf-8") as f:
963
  f.write(f"init manual at {datetime.now().isoformat()}\n")
964
  st.success("init_db.run() executado com sucesso.")
@@ -968,7 +1005,7 @@ def main():
968
  # 🔄 Botão de Recarregar (mantém a sessão ativa) + ⏱️ Controle do intervalo
969
  st.sidebar.markdown("---")
970
  col_reload, col_interval = st.sidebar.columns([1, 1])
971
- if col_reload.button("🔄 Recarregar (sem sair)", key="__btn_reload_now__"):
972
  st.rerun()
973
 
974
  if hasattr(st, "popover"):
@@ -979,7 +1016,7 @@ def main():
979
  value=int(st.session_state["__auto_refresh_interval_sec__"]),
980
  step=5, key="__auto_refresh_input__"
981
  )
982
- if st.button("Aplicar intervalo", key="__btn_apply_auto_refresh__"):
983
  st.session_state["__auto_refresh_interval_sec__"] = int(new_val)
984
  try:
985
  if int(new_val) > 0:
@@ -997,7 +1034,7 @@ def main():
997
  value=int(st.session_state["__auto_refresh_interval_sec__"]),
998
  step=5, key="__auto_refresh_input__"
999
  )
1000
- if st.button("Aplicar intervalo", key="__btn_apply_auto_refresh__"):
1001
  st.session_state["__auto_refresh_interval_sec__"] = int(new_val)
1002
  try:
1003
  if int(new_val) > 0:
@@ -1067,7 +1104,7 @@ def main():
1067
  )
1068
 
1069
  # 👉 Direciona para o MESMO módulo do menu (resposta.main())
1070
- if st.sidebar.button("📬 Abrir Caixa de Entrada (Admin)"):
1071
  st.session_state.nav_target = "resposta"
1072
  st.rerun()
1073
 
@@ -1129,7 +1166,7 @@ def main():
1129
  pass
1130
  st.session_state["__user_toast_shown__"] = True
1131
 
1132
- if st.sidebar.button("📥 Ver respostas"):
1133
  st.session_state.nav_target = "sugestoes_ioirun"
1134
  st.session_state.user_responses_viewed = True
1135
  st.rerun()
@@ -1274,7 +1311,7 @@ def main():
1274
  # Logout
1275
  st.sidebar.markdown("---")
1276
  if st.session_state.get("logado"):
1277
- if st.sidebar.button("🚪 Sair (Logout)"):
1278
  logout()
1279
 
1280
  st.divider()
 
71
  # 🗄️ Banco ativo (Produção/Teste/Treinamento)
72
  # Tentativa de importar utilitários do db_router, com fallback seguro.
73
  try:
74
+ from db_router import current_db_choice, bank_label
75
  _HAS_ROUTER = True
76
  except Exception:
77
  _HAS_ROUTER = False
78
 
79
  def current_db_choice() -> str:
 
80
  return st.session_state.get("__db_choice_override__", "prod")
81
 
82
  def bank_label(choice: str) -> str:
 
124
  except Exception:
125
  pass
126
 
127
+ # Rodar init_db.run() uma única vez por start de container (⚠️ roda no banco padrão do ambiente)
128
  try:
129
  import init_db as _init_db
130
  _HAS_INIT_DB = hasattr(_init_db, "run")
 
148
  pass
149
 
150
  # ===============================
151
+ # RERUN por querystring (atalho ?rr=1) e aplicar ?db=teste|treinamento
152
  # ===============================
153
  def _get_query_params():
154
  """Compat: retorna query params como dict (Streamlit novo/antigo)."""
155
  try:
156
+ return dict(st.query_params) # Streamlit >= 1.32
 
157
  except Exception:
 
158
  try:
159
+ return dict(st.experimental_get_query_params()) # Antigo
160
  except Exception:
161
  return {}
162
 
 
170
  except Exception:
171
  pass
172
 
173
+ def _apply_db_choice_from_qs():
174
+ """Permite selecionar o banco via URL (?db=test|treinamento)."""
175
+ try:
176
+ params = _get_query_params()
177
+ db = params.get("db")
178
+ if not db:
179
+ return
180
+ db = db[0] if isinstance(db, (list, tuple)) else db
181
+ sel = str(db).strip().lower()
182
+ if _set_db_choice_func:
183
+ try:
184
+ _set_db_choice_func(sel)
185
+ except Exception:
186
+ os.environ["DB_CHOICE"] = sel
187
+ st.session_state["__db_choice_override__"] = sel
188
+ else:
189
+ os.environ["DB_CHOICE"] = sel
190
+ st.session_state["__db_choice_override__"] = sel
191
+ except Exception:
192
+ pass
193
+
194
  # 🔒 ANTI-TREMOR PATCH: não consumir rr=1 quando login/quiz ainda não concluídos
195
  def _check_rerun_qs(pagina_atual: str = ""):
196
  """
197
  Se a URL contiver rr=1 (ou true), força um rerun e limpa o parâmetro para evitar loop.
198
+ Bloqueios para módulos sensíveis e login/quiz.
 
 
 
199
  """
200
  try:
201
  if st.session_state.get("__qs_rr_consumed__", False):
 
207
 
208
  # Evita rr=1 em módulos sensíveis a rerun/refresh
209
  if pagina_atual in ("resposta", "outlook_relatorio", "formulario"):
210
+ return
211
 
212
  params = _get_query_params()
213
  rr_raw = params.get("rr", ["0"])
 
244
  # 3) Fallback
245
  return SessionLocal()
246
 
 
 
 
 
 
 
 
 
 
247
  def quiz_respondido_hoje(usuario: str) -> bool:
248
  # ✅ Usar sessão ciente do ambiente
249
  db = _get_db_session()
 
764
  st.toast(f"Banco alterado para: {_lbl(sel_code)}", icon="🗄️")
765
  st.rerun()
766
 
767
+ # 🔎 DEBUG da escolha/URL (ajuda a diagnosticar se o router está aplicando)
768
+ try:
769
+ from db_router import current_db_choice as _cur, bank_label as _lbl2, get_engine as _eng
770
+ ch = _cur()
771
+ eng = _eng()
772
+ st.sidebar.caption(f"⚙️ DEBUG • Banco atual: {_lbl2(ch)} ({ch})")
773
+ st.sidebar.caption(f"⚙️ DEBUG • URL: {eng.url}")
774
+ except Exception as e:
775
+ st.sidebar.caption(f"⚙️ DEBUG router fail: {e}")
776
+
777
  # ===============================
778
  # MAIN
779
  # ===============================
780
  def main():
781
+ # Aplicar escolha via URL (ex.: ?db=test)
782
+ _apply_db_choice_from_qs()
783
+
784
  # Estados iniciais
785
  if "logado" not in st.session_state:
786
  st.session_state.logado = False
 
798
  # ===== Seleção de banco (sempre visível na sidebar) =====
799
  _db_choice_ui()
800
 
801
+ # 🔧 Manutenção do banco atual: Schema + init_db.run()
802
  try:
803
  from banco import init_schema
804
  with st.sidebar.expander("⚙️ Manutenção do banco atual", expanded=False):
 
808
  st.sidebar.success("Schema criado/atualizado no banco selecionado com sucesso.")
809
  except Exception as e:
810
  st.sidebar.error(f"Falha ao criar/atualizar schema: {e}")
811
+
812
+ # Botão para rodar init_db.run() no banco atual
813
+ if _HAS_INIT_DB:
814
+ c1, c2 = st.columns(2)
815
+ sure1 = c1.checkbox("Confirmo", key="__init_confirm1__")
816
+ sure2 = c2.checkbox("Estou ciente", key="__init_confirm2__")
817
+ if st.button("Rodar init_db.run()", key="__btn_run_initdb__", help="Cria usuários padrão e garante schema", type="secondary"):
818
+ if not (sure1 and sure2):
819
+ st.warning("Marque as duas confirmações para executar.")
820
+ else:
821
+ try:
822
+ _init_db.run()
823
+ st.success("init_db.run() executado com sucesso no banco selecionado.")
824
+ except Exception as e:
825
+ st.error(f"Falha ao rodar init_db.run(): {e}")
826
  except Exception:
 
827
  pass
828
 
829
+ # ⚙️ Criação automática do schema (leve) após seleção do banco (rodará no banco correto)
830
+ try:
831
+ Base.metadata.create_all(bind=engine)
832
+ except Exception as e:
833
+ st.sidebar.warning(f"Schema não foi criado automaticamente: {e}")
834
+
835
  # LOGIN
836
  if not st.session_state.logado:
837
  st.session_state.quiz_verificado = False
838
  # 🔒 ANTI-TREMOR: renderiza logo top apenas 1x
839
  exibir_logo_once(top=True, sidebar=False)
840
  login()
841
+ # 🔕 REMOVIDO: mensagem de dica de autenticação no Spaces
 
842
  return
843
 
844
  # 👥 Heartbeat + Badge de usuários logados (APENAS ADMIN)
 
862
  # 🔘 Botão Admin: habilitar/desabilitar painel de diagnóstico
863
  st.sidebar.markdown("---")
864
  if st.session_state.get("__auth_diag__"):
865
+ if st.sidebar.button("🧪 Desativar diagnóstico de login (Admin)", key="__btn_auth_diag_off__", type="secondary"):
866
  st.session_state["__auth_diag__"] = False
867
  st.rerun()
868
  else:
869
+ if st.sidebar.button("🧪 Ativar diagnóstico de login (Admin)", key="__btn_auth_diag_on__", type="secondary"):
870
  st.session_state["__auth_diag__"] = True
871
  st.rerun()
872
 
 
879
  # ============================
880
  from sqlalchemy import inspect # import local para evitar custo desnecessário
881
  with st.sidebar.expander("🧪 Diagnóstico Profundo do Banco", expanded=False):
882
+ dbx = _get_db_session()
883
  try:
884
+ eng = dbx.bind
885
  st.caption(f"Engine URL: {eng.url}")
886
 
887
  # 1) SELECT 1
888
  try:
889
+ dbx.execute(text("SELECT 1"))
890
  st.success("SELECT 1 OK")
891
  except Exception as e:
892
  st.error(f"SELECT 1 falhou: {e}")
 
910
 
911
  # 5) Contagem
912
  try:
913
+ cnt = dbx.execute(text(f"SELECT COUNT(*) FROM {target_table}")).fetchone()[0]
914
  st.write("Quantidade de registros:", cnt)
915
  except Exception as e:
916
  st.error(f"COUNT(*) falhou: {e}")
 
919
  try:
920
  sel_cols = [c for c in ["usuario", "email", "perfil", "nome"] if c in cols]
921
  sel_expr = ", ".join(sel_cols) if sel_cols else "*"
922
+ amostra = dbx.execute(text(f"SELECT {sel_expr} FROM {target_table} LIMIT 5"))
923
  rows = [dict(r._mapping) for r in amostra]
924
  st.write("Amostra:", rows)
925
  except Exception as e:
 
932
  test_user = st.text_input("Usuário para testar hash", "", key="__bcrypt_user__")
933
  test_pass = st.text_input("Senha para testar hash", "", type="password", key="__bcrypt_pass__")
934
 
935
+ if st.button("Testar bcrypt com este usuário", key="__btn_bcrypt_test__", type="secondary"):
936
  try:
937
  pass_cols = [c for c in ["senha_hash", "password_hash", "senha", "hash"] if c in cols]
938
  user_cols = [c for c in ["usuario", "username", "login"] if c in cols]
 
941
  else:
942
  c_pass = pass_cols[0]
943
  c_user = user_cols[0]
944
+ res = dbx.execute(
945
  text(f"SELECT {c_user} AS u, {c_pass} AS h FROM {target_table} WHERE {c_user} = :u LIMIT 1"),
946
  {"u": test_user.strip()}
947
  ).fetchone()
 
964
  st.error(f"Erro no teste: {e}")
965
  finally:
966
  try:
967
+ dbx.close()
968
  except Exception:
969
  pass
970
 
 
973
  # ============================
974
  with st.sidebar.expander("🧰 Manutenção (Admin)", expanded=False):
975
  col_m1, col_m2 = st.columns(2)
976
+ if col_m1.button("🧹 Limpar cache agora", key="__btn_clear_cache__", help="Limpa cache de dados/recursos", type="secondary"):
977
  try:
978
  st.cache_data.clear()
979
  except Exception:
 
985
  st.sidebar.success("Caches limpos.")
986
  st.rerun()
987
 
988
+ # Rodar init_db.run() com dupla confirmação (Admin)
989
  if _HAS_INIT_DB:
990
  c1, c2 = st.columns(2)
991
+ sure1 = c1.checkbox("Confirmo", key="__btn_run_initdb_admin_c1__")
992
+ sure2 = c2.checkbox("Estou ciente", key="__btn_run_initdb_admin_c2__")
993
+ if st.button("🧬 Rodar init_db.run()", key="__btn_run_initdb_admin__", type="secondary"):
994
  if not (sure1 and sure2):
995
  st.warning("Marque as duas confirmações para executar.")
996
  else:
997
  try:
998
  _init_db.run()
 
999
  with open(_INIT_MARK, "w", encoding="utf-8") as f:
1000
  f.write(f"init manual at {datetime.now().isoformat()}\n")
1001
  st.success("init_db.run() executado com sucesso.")
 
1005
  # 🔄 Botão de Recarregar (mantém a sessão ativa) + ⏱️ Controle do intervalo
1006
  st.sidebar.markdown("---")
1007
  col_reload, col_interval = st.sidebar.columns([1, 1])
1008
+ if col_reload.button("🔄 Recarregar (sem sair)", key="__btn_reload_now__", type="secondary"):
1009
  st.rerun()
1010
 
1011
  if hasattr(st, "popover"):
 
1016
  value=int(st.session_state["__auto_refresh_interval_sec__"]),
1017
  step=5, key="__auto_refresh_input__"
1018
  )
1019
+ if st.button("Aplicar intervalo", key="__btn_apply_auto_refresh__", type="secondary"):
1020
  st.session_state["__auto_refresh_interval_sec__"] = int(new_val)
1021
  try:
1022
  if int(new_val) > 0:
 
1034
  value=int(st.session_state["__auto_refresh_interval_sec__"]),
1035
  step=5, key="__auto_refresh_input__"
1036
  )
1037
+ if st.button("Aplicar intervalo", key="__btn_apply_auto_refresh__", type="secondary"):
1038
  st.session_state["__auto_refresh_interval_sec__"] = int(new_val)
1039
  try:
1040
  if int(new_val) > 0:
 
1104
  )
1105
 
1106
  # 👉 Direciona para o MESMO módulo do menu (resposta.main())
1107
+ if st.sidebar.button("📬 Abrir Caixa de Entrada (Admin)", key="__btn_open_inbox__", type="secondary"):
1108
  st.session_state.nav_target = "resposta"
1109
  st.rerun()
1110
 
 
1166
  pass
1167
  st.session_state["__user_toast_shown__"] = True
1168
 
1169
+ if st.sidebar.button("📥 Ver respostas", key="__btn_view_answers__", type="secondary"):
1170
  st.session_state.nav_target = "sugestoes_ioirun"
1171
  st.session_state.user_responses_viewed = True
1172
  st.rerun()
 
1311
  # Logout
1312
  st.sidebar.markdown("---")
1313
  if st.session_state.get("logado"):
1314
+ if st.sidebar.button("🚪 Sair (Logout)", key="__btn_logout__", type="primary"):
1315
  logout()
1316
 
1317
  st.divider()