SFM/scripts/local_zip_test_app.py
Raykov-MS 7b90daa396 Remove SMB entirely, switch input to attached zip, add self-service token popover
- Drop SMB/SAMBA subsystem completely: app/samba_access.py, app/path_settings.py,
  app/path_store.py, app/pipeline/file_manager.py and their tests, plus the
  smbprotocol dependency and all related UI (login/password fields, path editor,
  SMB diagnostics) in main.py.
- run_batch now takes a .zip attached via Streamlit's file_uploader (same as the
  local dev tool) instead of reading from SMB or a fictitious OpenCloud "Attach"
  folder; renamed from run_local_zip_batch since it's now the single canonical
  batch entrypoint used by both main.py and scripts/local_zip_test_app.py.
- Fixed run number resolution: added next_opencloud_run_number() which lists the
  Out/year/month/day folder via WebDAV PROPFIND, so repeated runs land in
  incrementing subfolders instead of always overwriting "1".
- Added a self-service "Добавить токен" popover in main.py so users can link
  their own OpenCloud App Token to their Keycloak account (opencloud_token_store)
  without an admin running the CLI script; reworded the missing-token error to
  point at it.
2026-09-17 15:34:53 +03:00

100 lines
4.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Тестовая страница: та же логика прикрепления .zip с XML, что и в проде
(main.py), но с ручным вводом username/App Token вместо Keycloak-сессии —
чтобы гонять всю цепочку (парсинг + загрузка в OpenCloud) локально, без
поднятого raisa_streamlit_oauth.
Запуск:
streamlit run scripts/local_zip_test_app.py
Username/токен можно один раз прописать в .env (см. .env.example) — поля
на странице предзаполнятся, но их всегда можно поменять руками. Личное
пространство ищется автоматически через GET /graph/v1.0/me/drives, см.
app/opencloud_access.py.
"""
from __future__ import annotations
import os
import sys
import tempfile
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
import streamlit as st
from dotenv import load_dotenv
from app.pipeline.service import run_batch
load_dotenv()
st.set_page_config(page_title="СФМ — локальный тест", page_icon="📦")
st.title("Локальный тест: zip → обработка → OpenCloud")
st.caption(
"Прикрепляете .zip с XML-файлами, он распаковывается и обрабатывается "
"тем же кодом парсинга, что и прод (app/pipeline/parser.py), а отчёт, "
"протокол и лог грузятся в OpenCloud (app/opencloud_access.py). Та же "
"логика, что и в main.py, только username/token вводятся руками."
)
username = st.text_input(
"Username (для Basic Auth в OpenCloud)",
value=os.getenv("SFM_OPENCLOUD_USERNAME", ""),
help="Логин, под которым заказчик создал App Token в OpenCloud. Можно прописать в .env как SFM_OPENCLOUD_USERNAME.",
)
access_token = st.text_input(
"App Token",
value=os.getenv("SFM_OPENCLOUD_ACCESS_TOKEN", ""),
type="password",
help=(
"Долгоживущий App Token из веб-интерфейса OpenCloud (не Bearer из Keycloak — "
"авторизация теперь через HTTP Basic Auth). Можно прописать в .env как "
"SFM_OPENCLOUD_ACCESS_TOKEN."
),
)
launcher = st.text_input("Запустил (для протокола)", value="local-test")
uploaded_zip = st.file_uploader("Архив с XML-файлами (.zip)", type=["zip"])
if st.button("Начать обработку", type="primary", use_container_width=True):
if not username:
st.error("Укажите username.")
elif not access_token:
st.error("Укажите App Token.")
elif uploaded_zip is None:
st.error("Прикрепите .zip-архив.")
else:
with tempfile.TemporaryDirectory() as tmp_dir:
zip_path = Path(tmp_dir) / uploaded_zip.name
zip_path.write_bytes(uploaded_zip.getvalue())
with st.spinner("Распаковываем, обрабатываем, грузим в OpenCloud..."):
result = run_batch(
zip_path=zip_path,
launcher=launcher or "local-test",
opencloud_username=username,
opencloud_access_token=access_token,
)
if result.status == "success":
st.success("Обработка завершена успешно.")
elif result.status == "partial":
st.warning("Обработка завершена частично.")
else:
st.error("Обработка завершена с ошибками.")
st.write(
{
"processed": result.processed,
"partial": result.partial,
"errors": result.errors,
"report_path": result.report_path,
"protocol_path": result.protocol_path,
"log_path": result.log_path,
"duration_seconds": result.duration_seconds,
}
)
if result.messages:
st.caption("Сообщения обработки:")
for message in result.messages:
st.write(f"- {message}")