- 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.
100 lines
4.3 KiB
Python
100 lines
4.3 KiB
Python
"""Тестовая страница: та же логика прикрепления .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}")
|