SFM/app/app_config.py
Raykov-MS cceec9c4fb 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.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 15:34:53 +03:00

65 lines
2.8 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.

from __future__ import annotations
import os
import streamlit as st
def resolve_user_id() -> str:
profile = st.session_state.get("user_profile", {})
if not isinstance(profile, dict):
return "anonymous|anonymous"
email = str(
profile.get("email") or profile.get("preferred_username") or "anonymous"
)
full_name = str(profile.get("name") or profile.get("full_name") or "anonymous")
return f"{email}|{full_name}"
def resolve_opencloud_username() -> str | None:
"""preferred_username из Keycloak — им же заказчик регистрировал App Token
в веб-интерфейсе OpenCloud (сверено вручную, совпадает без домена/регистра).
Для локального теста (mock-провайдер) — fallback на .env.
"""
profile = st.session_state.get("user_profile", {})
if isinstance(profile, dict):
value = profile.get("preferred_username")
if value:
return str(value)
return os.getenv("SFM_OPENCLOUD_USERNAME") or None
def resolve_opencloud_access_token() -> str | None:
"""Достаёт App Token заказчика для авторизации в OpenCloud (Basic Auth).
ИСТОРИЯ (см. app/opencloud_access.py): изначально пытались использовать
Bearer ID-токен Keycloak-клиента datalab — Graph API OpenCloud отвечал
401 (несовпадение audience). Подтверждено на проде 16.09: рабочий путь —
HTTP Basic Auth с username (resolve_opencloud_username) и долгоживущим
App Token, который заказчик сам создаёт в веб-интерфейсе OpenCloud.
Токены хранятся зашифрованными в БД (app/opencloud_token_store.py),
заказчики присылают их нам сами при онбординге.
Приложение синхронное (Streamlit) — обёртка asyncio.run вокруг async
store оправдана только частотой вызова (раз на запуск обработки), при
росте нагрузки стоит пересмотреть.
"""
username = resolve_opencloud_username()
if not username:
return None
import asyncio
from app.opencloud_token_store import OpenCloudTokenStoreError, get_token
try:
return asyncio.run(get_token(username))
except OpenCloudTokenStoreError as exc:
st.error(f"Ошибка доступа к токену OpenCloud: {exc}")
return None
except Exception: # noqa: BLE001
# Локальный тест без поднятой БД/переменных окружения — fallback.
return os.getenv("SFM_OPENCLOUD_ACCESS_TOKEN") or None