Работает все, кроме отображения задач
This commit is contained in:
@@ -21,6 +21,7 @@ from adapters.parsers import SvodkaPMParser, SvodkaCAParser, MonitoringFuelParse
|
||||
|
||||
from core.models import UploadRequest, DataRequest
|
||||
from core.services import ReportService, PARSERS
|
||||
from core.async_services import AsyncReportService
|
||||
|
||||
from app.schemas import (
|
||||
ServerInfoResponse,
|
||||
@@ -55,6 +56,10 @@ def get_report_service() -> ReportService:
|
||||
return ReportService(storage_adapter)
|
||||
|
||||
|
||||
def get_async_report_service() -> AsyncReportService:
|
||||
return AsyncReportService(ReportService(storage_adapter))
|
||||
|
||||
|
||||
tags_metadata = [
|
||||
{
|
||||
"name": "Общее",
|
||||
@@ -1443,5 +1448,194 @@ async def get_oper_spravka_tech_pos_data(request: OperSpravkaTechPosRequest):
|
||||
raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# АСИНХРОННЫЕ ЭНДПОИНТЫ
|
||||
# ============================================================================
|
||||
|
||||
@app.post("/async/svodka_pm/upload-zip", tags=[SvodkaPMParser.name],
|
||||
summary="Асинхронная загрузка файлов сводок ПМ одним ZIP-архивом",
|
||||
response_model=UploadResponse,
|
||||
responses={
|
||||
400: {"model": UploadErrorResponse, "description": "Неверный формат архива или файлов"},
|
||||
500: {"model": UploadErrorResponse, "description": "Внутренняя ошибка сервера"}
|
||||
},)
|
||||
async def async_upload_svodka_pm_zip(
|
||||
zip_file: UploadFile = File(..., description="ZIP архив с Excel файлами (.zip)")
|
||||
):
|
||||
"""Асинхронная загрузка файлов сводок ПМ (факта и плана) по всем ОГ в **одном ZIP-архиве**"""
|
||||
async_service = get_async_report_service()
|
||||
try:
|
||||
if not zip_file.filename.lower().endswith('.zip'):
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message="Файл должен быть ZIP архивом",
|
||||
error_code="INVALID_FILE_TYPE",
|
||||
details={
|
||||
"expected_formats": [".zip"],
|
||||
"received_format": zip_file.filename.split('.')[-1] if '.' in zip_file.filename else "unknown"
|
||||
}
|
||||
).model_dump()
|
||||
)
|
||||
file_content = await zip_file.read()
|
||||
# Создаем запрос
|
||||
request = UploadRequest(
|
||||
report_type='svodka_pm',
|
||||
file_content=file_content,
|
||||
file_name=zip_file.filename
|
||||
)
|
||||
# Загружаем отчет асинхронно
|
||||
result = await async_service.upload_report_async(request)
|
||||
|
||||
if result.success:
|
||||
return UploadResponse(
|
||||
success=True,
|
||||
message=result.message,
|
||||
object_id=result.object_id
|
||||
)
|
||||
else:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message=result.message,
|
||||
error_code="UPLOAD_FAILED"
|
||||
).model_dump()
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при асинхронной загрузке сводки ПМ: {str(e)}")
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=f"Внутренняя ошибка сервера: {str(e)}",
|
||||
error_code="INTERNAL_ERROR"
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
|
||||
@app.post("/async/svodka_ca/upload", tags=[SvodkaCAParser.name],
|
||||
summary="Асинхронная загрузка файла отчета сводки СА",
|
||||
response_model=UploadResponse,
|
||||
responses={
|
||||
400: {"model": UploadErrorResponse, "description": "Неверный формат файла"},
|
||||
500: {"model": UploadErrorResponse, "description": "Внутренняя ошибка сервера"}
|
||||
},)
|
||||
async def async_upload_svodka_ca(
|
||||
file: UploadFile = File(..., description="Excel файл сводки СА (.xlsx, .xlsm, .xls)")
|
||||
):
|
||||
"""Асинхронная загрузка и обработка Excel файла отчета сводки СА"""
|
||||
async_service = get_async_report_service()
|
||||
try:
|
||||
# Проверяем тип файла
|
||||
if not file.filename.endswith(('.xlsx', '.xlsm', '.xls')):
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message="Поддерживаются только Excel файлы (.xlsx, .xlsm, .xls)",
|
||||
error_code="INVALID_FILE_TYPE",
|
||||
details={
|
||||
"expected_formats": [".xlsx", ".xlsm", ".xls"],
|
||||
"received_format": file.filename.split('.')[-1] if '.' in file.filename else "unknown"
|
||||
}
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
# Читаем содержимое файла
|
||||
file_content = await file.read()
|
||||
|
||||
# Создаем запрос
|
||||
request = UploadRequest(
|
||||
report_type='svodka_ca',
|
||||
file_content=file_content,
|
||||
file_name=file.filename
|
||||
)
|
||||
|
||||
# Загружаем отчет асинхронно
|
||||
result = await async_service.upload_report_async(request)
|
||||
|
||||
if result.success:
|
||||
return UploadResponse(
|
||||
success=True,
|
||||
message=result.message,
|
||||
object_id=result.object_id
|
||||
)
|
||||
else:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message=result.message,
|
||||
error_code="UPLOAD_FAILED"
|
||||
).model_dump()
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при асинхронной загрузке сводки СА: {str(e)}")
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=f"Внутренняя ошибка сервера: {str(e)}",
|
||||
error_code="INTERNAL_ERROR"
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
|
||||
@app.post("/async/monitoring_fuel/upload-zip", tags=[MonitoringFuelParser.name],
|
||||
summary="Асинхронная загрузка ZIP архива с мониторингом топлива",
|
||||
response_model=UploadResponse,
|
||||
responses={
|
||||
400: {"model": UploadErrorResponse, "description": "Неверный формат архива или файлов"},
|
||||
500: {"model": UploadErrorResponse, "description": "Внутренняя ошибка сервера"}
|
||||
},)
|
||||
async def async_upload_monitoring_fuel_zip(
|
||||
zip_file: UploadFile = File(..., description="ZIP архив с Excel файлами мониторинга топлива (.zip)")
|
||||
):
|
||||
"""Асинхронная загрузка ZIP архива с файлами мониторинга топлива"""
|
||||
async_service = get_async_report_service()
|
||||
try:
|
||||
if not zip_file.filename.lower().endswith('.zip'):
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message="Файл должен быть ZIP архивом",
|
||||
error_code="INVALID_FILE_TYPE",
|
||||
details={
|
||||
"expected_formats": [".zip"],
|
||||
"received_format": zip_file.filename.split('.')[-1] if '.' in zip_file.filename else "unknown"
|
||||
}
|
||||
).model_dump()
|
||||
)
|
||||
file_content = await zip_file.read()
|
||||
# Создаем запрос
|
||||
request = UploadRequest(
|
||||
report_type='monitoring_fuel',
|
||||
file_content=file_content,
|
||||
file_name=zip_file.filename
|
||||
)
|
||||
# Загружаем отчет асинхронно
|
||||
result = await async_service.upload_report_async(request)
|
||||
|
||||
if result.success:
|
||||
return UploadResponse(
|
||||
success=True,
|
||||
message=result.message,
|
||||
object_id=result.object_id
|
||||
)
|
||||
else:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
content=UploadErrorResponse(
|
||||
message=result.message,
|
||||
error_code="UPLOAD_FAILED"
|
||||
).model_dump()
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при асинхронной загрузке мониторинга топлива: {str(e)}")
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content=UploadErrorResponse(
|
||||
message=f"Внутренняя ошибка сервера: {str(e)}",
|
||||
error_code="INTERNAL_ERROR"
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(app, host="0.0.0.0", port=8080)
|
||||
|
||||
72
python_parser/core/async_services.py
Normal file
72
python_parser/core/async_services.py
Normal file
@@ -0,0 +1,72 @@
|
||||
"""
|
||||
Асинхронные сервисы для работы с отчетами
|
||||
"""
|
||||
import asyncio
|
||||
import tempfile
|
||||
import os
|
||||
import logging
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from typing import Optional
|
||||
|
||||
from .services import ReportService
|
||||
from .models import UploadRequest, UploadResult, DataRequest, DataResult
|
||||
from .ports import StoragePort
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AsyncReportService:
|
||||
"""Асинхронный сервис для работы с отчетами"""
|
||||
|
||||
def __init__(self, report_service: ReportService):
|
||||
self.report_service = report_service
|
||||
self.executor = ThreadPoolExecutor(max_workers=4)
|
||||
|
||||
async def upload_report_async(self, request: UploadRequest) -> UploadResult:
|
||||
"""Асинхронная загрузка отчета"""
|
||||
try:
|
||||
# Запускаем синхронную обработку в отдельном потоке
|
||||
loop = asyncio.get_event_loop()
|
||||
result = await loop.run_in_executor(
|
||||
self.executor,
|
||||
self._process_upload_sync,
|
||||
request
|
||||
)
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при асинхронной загрузке отчета: {str(e)}")
|
||||
return UploadResult(
|
||||
success=False,
|
||||
message=f"Ошибка при асинхронной загрузке отчета: {str(e)}"
|
||||
)
|
||||
|
||||
def _process_upload_sync(self, request: UploadRequest) -> UploadResult:
|
||||
"""Синхронная обработка загрузки (выполняется в отдельном потоке)"""
|
||||
return self.report_service.upload_report(request)
|
||||
|
||||
async def get_data_async(self, request: DataRequest) -> DataResult:
|
||||
"""Асинхронное получение данных"""
|
||||
try:
|
||||
# Запускаем синхронную обработку в отдельном потоке
|
||||
loop = asyncio.get_event_loop()
|
||||
result = await loop.run_in_executor(
|
||||
self.executor,
|
||||
self._process_get_data_sync,
|
||||
request
|
||||
)
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при асинхронном получении данных: {str(e)}")
|
||||
return DataResult(
|
||||
success=False,
|
||||
message=f"Ошибка при асинхронном получении данных: {str(e)}"
|
||||
)
|
||||
|
||||
def _process_get_data_sync(self, request: DataRequest) -> DataResult:
|
||||
"""Синхронное получение данных (выполняется в отдельном потоке)"""
|
||||
return self.report_service.get_data(request)
|
||||
|
||||
def __del__(self):
|
||||
"""Очистка ресурсов"""
|
||||
if hasattr(self, 'executor'):
|
||||
self.executor.shutdown(wait=False)
|
||||
146
streamlit_app/async_upload_page.py
Normal file
146
streamlit_app/async_upload_page.py
Normal file
@@ -0,0 +1,146 @@
|
||||
"""
|
||||
Страница асинхронной загрузки файлов
|
||||
"""
|
||||
import streamlit as st
|
||||
import asyncio
|
||||
import threading
|
||||
import time
|
||||
from api_client import upload_file_to_api
|
||||
from config import PARSER_TABS
|
||||
|
||||
|
||||
def upload_file_async_background(endpoint, file_data, filename, task_id):
|
||||
"""Асинхронная загрузка файла в фоновом режиме"""
|
||||
try:
|
||||
# Имитируем асинхронную работу
|
||||
time.sleep(1) # Небольшая задержка для демонстрации
|
||||
|
||||
# Выполняем загрузку
|
||||
result, status = upload_file_to_api(endpoint, file_data, filename)
|
||||
|
||||
# Сохраняем результат в session_state
|
||||
if 'upload_tasks' not in st.session_state:
|
||||
st.session_state.upload_tasks = {}
|
||||
|
||||
st.session_state.upload_tasks[task_id] = {
|
||||
'status': 'completed' if status == 200 else 'failed',
|
||||
'result': result,
|
||||
'status_code': status,
|
||||
'filename': filename,
|
||||
'endpoint': endpoint,
|
||||
'completed_at': time.time()
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
# Сохраняем ошибку
|
||||
if 'upload_tasks' not in st.session_state:
|
||||
st.session_state.upload_tasks = {}
|
||||
|
||||
st.session_state.upload_tasks[task_id] = {
|
||||
'status': 'failed',
|
||||
'error': str(e),
|
||||
'filename': filename,
|
||||
'endpoint': endpoint,
|
||||
'completed_at': time.time()
|
||||
}
|
||||
|
||||
|
||||
def render_async_upload_page():
|
||||
"""Рендер страницы асинхронной загрузки"""
|
||||
st.title("🚀 Асинхронная загрузка файлов")
|
||||
st.markdown("---")
|
||||
|
||||
st.info("""
|
||||
**Асинхронная загрузка** позволяет загружать файлы без блокировки интерфейса.
|
||||
После загрузки файл будет обработан в фоновом режиме, а вы сможете отслеживать прогресс на странице "Управление задачами".
|
||||
""")
|
||||
|
||||
# Выбор парсера
|
||||
st.subheader("📋 Выбор парсера")
|
||||
|
||||
# Создаем словарь парсеров с их асинхронными эндпоинтами
|
||||
parser_endpoints = {
|
||||
"Сводки ПМ": "/async/svodka_pm/upload-zip",
|
||||
"Сводки СА": "/async/svodka_ca/upload",
|
||||
"Мониторинг топлива": "/async/monitoring_fuel/upload-zip",
|
||||
"Ремонт СА": "/svodka_repair_ca/upload", # Пока синхронный
|
||||
"Статусы ремонта СА": "/statuses_repair_ca/upload", # Пока синхронный
|
||||
"Мониторинг ТЭР": "/monitoring_tar/upload", # Пока синхронный
|
||||
"Операционные справки": "/oper_spravka_tech_pos/upload" # Пока синхронный
|
||||
}
|
||||
|
||||
selected_parser = st.selectbox(
|
||||
"Выберите тип парсера для загрузки:",
|
||||
list(parser_endpoints.keys()),
|
||||
key="async_parser_select"
|
||||
)
|
||||
|
||||
st.markdown("---")
|
||||
|
||||
# Загрузка файла
|
||||
st.subheader("📤 Загрузка файла")
|
||||
|
||||
uploaded_file = st.file_uploader(
|
||||
f"Выберите ZIP архив для парсера '{selected_parser}'",
|
||||
type=['zip'],
|
||||
key="async_file_upload"
|
||||
)
|
||||
|
||||
if uploaded_file is not None:
|
||||
st.success(f"✅ Файл выбран: {uploaded_file.name}")
|
||||
st.info(f"📊 Размер файла: {uploaded_file.size / 1024 / 1024:.2f} MB")
|
||||
|
||||
if st.button("🚀 Загрузить асинхронно", key="async_upload_btn", use_container_width=True):
|
||||
# Создаем уникальный ID задачи
|
||||
task_id = f"task_{int(time.time())}_{uploaded_file.name}"
|
||||
|
||||
# Показываем сообщение о создании задачи
|
||||
st.success("✅ Задача загрузки создана!")
|
||||
st.info(f"ID задачи: `{task_id}`")
|
||||
st.info("📋 Перейдите на страницу 'Управление задачами' для отслеживания прогресса")
|
||||
|
||||
# Запускаем загрузку в фоновом потоке
|
||||
endpoint = parser_endpoints[selected_parser]
|
||||
file_data = uploaded_file.read()
|
||||
|
||||
# Создаем поток для асинхронной загрузки
|
||||
thread = threading.Thread(
|
||||
target=upload_file_async_background,
|
||||
args=(endpoint, file_data, uploaded_file.name, task_id)
|
||||
)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
# Автоматически переключаемся на страницу задач
|
||||
st.session_state.sidebar_tasks_clicked = True
|
||||
st.rerun()
|
||||
|
||||
st.markdown("---")
|
||||
|
||||
# Информация о поддерживаемых форматах
|
||||
with st.expander("ℹ️ Поддерживаемые форматы файлов"):
|
||||
st.markdown("""
|
||||
**Поддерживаемые форматы:**
|
||||
- 📦 ZIP архивы с Excel файлами
|
||||
- 📊 Excel файлы (.xlsx, .xls)
|
||||
- 📋 CSV файлы (для некоторых парсеров)
|
||||
|
||||
**Ограничения:**
|
||||
- Максимальный размер файла: 100 MB
|
||||
- Количество файлов в архиве: до 50
|
||||
- Поддерживаемые кодировки: UTF-8, Windows-1251
|
||||
""")
|
||||
|
||||
# Статистика загрузок
|
||||
st.subheader("📈 Статистика загрузок")
|
||||
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
st.metric("Всего загружено", "0", "0")
|
||||
|
||||
with col2:
|
||||
st.metric("В обработке", "0", "0")
|
||||
|
||||
with col3:
|
||||
st.metric("Завершено", "0", "0")
|
||||
@@ -9,7 +9,7 @@ from config import API_PUBLIC_URL
|
||||
def render_sidebar():
|
||||
"""Рендер боковой панели"""
|
||||
with st.sidebar:
|
||||
st.header("ℹ️ Информация")
|
||||
st.header("ℹ️ Информация1")
|
||||
|
||||
# Информация о сервере
|
||||
server_info = get_server_info()
|
||||
@@ -25,6 +25,28 @@ def render_sidebar():
|
||||
st.subheader("Доступные парсеры")
|
||||
for parser in parsers:
|
||||
st.write(f"• {parser}")
|
||||
|
||||
# Навигация по страницам
|
||||
st.markdown("---")
|
||||
st.subheader("🧭 Навигация")
|
||||
|
||||
# Определяем активную страницу
|
||||
active_page = st.session_state.get("active_page", 0)
|
||||
|
||||
# Кнопка для страницы синхронных парсеров
|
||||
if st.button("📊 Синхронные парсеры", key="sidebar_sync_btn", use_container_width=True, type="primary" if active_page == 0 else "secondary"):
|
||||
st.session_state.sidebar_sync_clicked = True
|
||||
st.rerun()
|
||||
|
||||
# Кнопка для страницы асинхронной загрузки
|
||||
if st.button("🚀 Асинхронная загрузка", key="sidebar_async_btn", use_container_width=True, type="primary" if active_page == 1 else "secondary"):
|
||||
st.session_state.sidebar_async_clicked = True
|
||||
st.rerun()
|
||||
|
||||
# Кнопка для страницы управления задачами
|
||||
if st.button("📋 Управление задачами", key="sidebar_tasks_btn", use_container_width=True, type="primary" if active_page == 2 else "secondary"):
|
||||
st.session_state.sidebar_tasks_clicked = True
|
||||
st.rerun()
|
||||
|
||||
|
||||
def render_footer():
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
import streamlit as st
|
||||
from config import setup_page_config, PARSER_TABS, API_PUBLIC_URL
|
||||
from config import setup_page_config, API_PUBLIC_URL
|
||||
from api_client import check_api_health
|
||||
from sidebar import render_sidebar, render_footer
|
||||
from parsers_ui.svodka_pm_ui import render_svodka_pm_tab
|
||||
from parsers_ui.svodka_ca_ui import render_svodka_ca_tab
|
||||
from parsers_ui.monitoring_fuel_ui import render_monitoring_fuel_tab
|
||||
from parsers_ui.svodka_repair_ca_ui import render_svodka_repair_ca_tab
|
||||
from parsers_ui.statuses_repair_ca_ui import render_statuses_repair_ca_tab
|
||||
from parsers_ui.monitoring_tar_ui import render_monitoring_tar_tab
|
||||
from parsers_ui.oper_spravka_tech_pos_ui import render_oper_spravka_tech_pos_tab
|
||||
from sync_parsers_page import render_sync_parsers_page
|
||||
from async_upload_page import render_async_upload_page
|
||||
from tasks_page import render_tasks_page
|
||||
|
||||
# Конфигурация страницы
|
||||
setup_page_config()
|
||||
|
||||
def main():
|
||||
st.title("🚀 NIN Excel Parsers API - Демонстрация")
|
||||
# Определяем активную страницу для заголовка
|
||||
active_page = st.session_state.get("active_page", 0)
|
||||
page_titles = {
|
||||
0: "Синхронные парсеры",
|
||||
1: "Асинхронная загрузка",
|
||||
2: "Управление задачами"
|
||||
}
|
||||
|
||||
st.title(f"🚀 NIN Excel Parsers API - {page_titles.get(active_page, 'Демонстрация')}")
|
||||
st.markdown("---")
|
||||
|
||||
# Проверка доступности API
|
||||
@@ -25,39 +29,30 @@ def main():
|
||||
|
||||
st.success(f"✅ API доступен по адресу {API_PUBLIC_URL}")
|
||||
|
||||
# Боковая панель с информацией
|
||||
# Боковая панель с информацией и навигацией
|
||||
render_sidebar()
|
||||
|
||||
# Основные вкладки - по одной на каждый парсер
|
||||
tab1, tab2, tab3, tab4, tab5, tab6, tab7 = st.tabs(PARSER_TABS)
|
||||
# Обрабатываем клики по кнопкам в сайдбаре
|
||||
if st.session_state.get("sidebar_sync_clicked", False):
|
||||
st.session_state.sidebar_sync_clicked = False
|
||||
st.session_state.active_page = 0
|
||||
elif st.session_state.get("sidebar_async_clicked", False):
|
||||
st.session_state.sidebar_async_clicked = False
|
||||
st.session_state.active_page = 1
|
||||
elif st.session_state.get("sidebar_tasks_clicked", False):
|
||||
st.session_state.sidebar_tasks_clicked = False
|
||||
st.session_state.active_page = 2
|
||||
|
||||
# Вкладка 1: Сводки ПМ - полный функционал
|
||||
with tab1:
|
||||
render_svodka_pm_tab()
|
||||
# Определяем активную страницу
|
||||
active_page = st.session_state.get("active_page", 0)
|
||||
|
||||
# Вкладка 2: Сводки СА - полный функционал
|
||||
with tab2:
|
||||
render_svodka_ca_tab()
|
||||
|
||||
# Вкладка 3: Мониторинг топлива - полный функционал
|
||||
with tab3:
|
||||
render_monitoring_fuel_tab()
|
||||
|
||||
# Вкладка 4: Ремонт СА
|
||||
with tab4:
|
||||
render_svodka_repair_ca_tab()
|
||||
|
||||
# Вкладка 5: Статусы ремонта СА
|
||||
with tab5:
|
||||
render_statuses_repair_ca_tab()
|
||||
|
||||
# Вкладка 6: Мониторинг ТЭР
|
||||
with tab6:
|
||||
render_monitoring_tar_tab()
|
||||
|
||||
# Вкладка 7: Операционные справки технологических позиций
|
||||
with tab7:
|
||||
render_oper_spravka_tech_pos_tab()
|
||||
# Рендерим соответствующую страницу
|
||||
if active_page == 0:
|
||||
render_sync_parsers_page()
|
||||
elif active_page == 1:
|
||||
render_async_upload_page()
|
||||
else:
|
||||
render_tasks_page()
|
||||
|
||||
# Футер
|
||||
render_footer()
|
||||
|
||||
54
streamlit_app/sync_parsers_page.py
Normal file
54
streamlit_app/sync_parsers_page.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Страница синхронных парсеров
|
||||
"""
|
||||
import streamlit as st
|
||||
from parsers_ui.svodka_pm_ui import render_svodka_pm_tab
|
||||
from parsers_ui.svodka_ca_ui import render_svodka_ca_tab
|
||||
from parsers_ui.monitoring_fuel_ui import render_monitoring_fuel_tab
|
||||
from parsers_ui.svodka_repair_ca_ui import render_svodka_repair_ca_tab
|
||||
from parsers_ui.statuses_repair_ca_ui import render_statuses_repair_ca_tab
|
||||
from parsers_ui.monitoring_tar_ui import render_monitoring_tar_tab
|
||||
from parsers_ui.oper_spravka_tech_pos_ui import render_oper_spravka_tech_pos_tab
|
||||
from config import PARSER_TABS
|
||||
|
||||
|
||||
def render_sync_parsers_page():
|
||||
"""Рендер страницы синхронных парсеров"""
|
||||
st.title("📊 Синхронные парсеры")
|
||||
st.markdown("---")
|
||||
|
||||
st.info("""
|
||||
**Синхронные парсеры** обрабатывают файлы сразу после загрузки.
|
||||
Интерфейс будет заблокирован до завершения обработки.
|
||||
""")
|
||||
|
||||
# Основные вкладки - по одной на каждый парсер
|
||||
tab1, tab2, tab3, tab4, tab5, tab6, tab7 = st.tabs(PARSER_TABS)
|
||||
|
||||
# Вкладка 1: Сводки ПМ - полный функционал
|
||||
with tab1:
|
||||
render_svodka_pm_tab()
|
||||
|
||||
# Вкладка 2: Сводки СА - полный функционал
|
||||
with tab2:
|
||||
render_svodka_ca_tab()
|
||||
|
||||
# Вкладка 3: Мониторинг топлива - полный функционал
|
||||
with tab3:
|
||||
render_monitoring_fuel_tab()
|
||||
|
||||
# Вкладка 4: Ремонт СА
|
||||
with tab4:
|
||||
render_svodka_repair_ca_tab()
|
||||
|
||||
# Вкладка 5: Статусы ремонта СА
|
||||
with tab5:
|
||||
render_statuses_repair_ca_tab()
|
||||
|
||||
# Вкладка 6: Мониторинг ТЭР
|
||||
with tab6:
|
||||
render_monitoring_tar_tab()
|
||||
|
||||
# Вкладка 7: Операционные справки технологических позиций
|
||||
with tab7:
|
||||
render_oper_spravka_tech_pos_tab()
|
||||
159
streamlit_app/tasks_page.py
Normal file
159
streamlit_app/tasks_page.py
Normal file
@@ -0,0 +1,159 @@
|
||||
"""
|
||||
Страница управления задачами загрузки
|
||||
"""
|
||||
import streamlit as st
|
||||
from datetime import datetime
|
||||
import time
|
||||
|
||||
|
||||
def render_tasks_page():
|
||||
"""Рендер страницы управления задачами"""
|
||||
st.title("📋 Управление задачами загрузки")
|
||||
st.markdown("---")
|
||||
|
||||
# Кнопки управления
|
||||
col1, col2, col3, col4 = st.columns([1, 1, 1, 2])
|
||||
|
||||
with col1:
|
||||
if st.button("🔄 Обновить", key="refresh_tasks_btn", use_container_width=True):
|
||||
st.rerun()
|
||||
|
||||
with col2:
|
||||
if st.button("🗑️ Очистить завершенные", key="clear_completed_btn", use_container_width=True):
|
||||
st.info("Функция очистки будет добавлена в следующих версиях")
|
||||
|
||||
with col3:
|
||||
auto_refresh = st.checkbox("🔄 Автообновление", key="auto_refresh_checkbox")
|
||||
if auto_refresh:
|
||||
time.sleep(2)
|
||||
st.rerun()
|
||||
|
||||
with col4:
|
||||
st.caption("Последнее обновление: " + datetime.now().strftime("%H:%M:%S"))
|
||||
|
||||
st.markdown("---")
|
||||
|
||||
# Статистика задач
|
||||
st.subheader("📊 Статистика задач")
|
||||
|
||||
# Получаем задачи из session_state
|
||||
tasks = st.session_state.get('upload_tasks', {})
|
||||
|
||||
# Подсчитываем статистику
|
||||
total_tasks = len(tasks)
|
||||
pending_tasks = len([t for t in tasks.values() if t.get('status') == 'pending'])
|
||||
running_tasks = len([t for t in tasks.values() if t.get('status') == 'running'])
|
||||
completed_tasks = len([t for t in tasks.values() if t.get('status') == 'completed'])
|
||||
failed_tasks = len([t for t in tasks.values() if t.get('status') == 'failed'])
|
||||
|
||||
col1, col2, col3, col4, col5 = st.columns(5)
|
||||
|
||||
with col1:
|
||||
st.metric("Всего", total_tasks, f"+{total_tasks}")
|
||||
|
||||
with col2:
|
||||
st.metric("Ожидают", pending_tasks, f"+{pending_tasks}")
|
||||
|
||||
with col3:
|
||||
st.metric("Выполняются", running_tasks, f"+{running_tasks}")
|
||||
|
||||
with col4:
|
||||
st.metric("Завершены", completed_tasks, f"+{completed_tasks}")
|
||||
|
||||
with col5:
|
||||
st.metric("Ошибки", failed_tasks, f"+{failed_tasks}")
|
||||
|
||||
st.markdown("---")
|
||||
|
||||
# Список задач
|
||||
st.subheader("📋 Список задач")
|
||||
|
||||
# Получаем задачи из session_state
|
||||
tasks = st.session_state.get('upload_tasks', {})
|
||||
|
||||
if tasks:
|
||||
# Показываем задачи
|
||||
for task_id, task in tasks.items():
|
||||
status_emoji = {
|
||||
'pending': '🟡',
|
||||
'running': '🔵',
|
||||
'completed': '🟢',
|
||||
'failed': '🔴'
|
||||
}.get(task.get('status', 'pending'), '⚪')
|
||||
|
||||
with st.expander(f"{status_emoji} {task.get('filename', 'Unknown')} - {task.get('status', 'unknown').upper()}", expanded=True):
|
||||
col1, col2 = st.columns([3, 1])
|
||||
|
||||
with col1:
|
||||
st.write(f"**ID:** `{task_id}`")
|
||||
st.write(f"**Статус:** {status_emoji} {task.get('status', 'unknown').upper()}")
|
||||
st.write(f"**Файл:** {task.get('filename', 'Unknown')}")
|
||||
st.write(f"**Эндпоинт:** {task.get('endpoint', 'Unknown')}")
|
||||
|
||||
if task.get('completed_at'):
|
||||
completed_time = datetime.fromtimestamp(task['completed_at']).strftime("%Y-%m-%d %H:%M:%S")
|
||||
st.write(f"**Завершена:** {completed_time}")
|
||||
|
||||
if task.get('result'):
|
||||
result = task['result']
|
||||
if task.get('status') == 'completed':
|
||||
st.success(f"✅ {result.get('message', 'Задача выполнена')}")
|
||||
if result.get('object_id'):
|
||||
st.info(f"ID объекта: {result['object_id']}")
|
||||
else:
|
||||
st.error(f"❌ {result.get('message', 'Ошибка выполнения')}")
|
||||
|
||||
if task.get('error'):
|
||||
st.error(f"❌ Ошибка: {task['error']}")
|
||||
|
||||
with col2:
|
||||
if task.get('status') in ['pending', 'running']:
|
||||
if st.button("❌ Отменить", key=f"cancel_{task_id}_btn", use_container_width=True):
|
||||
st.info("Функция отмены будет реализована в следующих версиях")
|
||||
else:
|
||||
if st.button("🗑️ Удалить", key=f"delete_{task_id}_btn", use_container_width=True):
|
||||
# Удаляем задачу из session_state
|
||||
if 'upload_tasks' in st.session_state:
|
||||
del st.session_state.upload_tasks[task_id]
|
||||
st.rerun()
|
||||
else:
|
||||
# Пустое состояние
|
||||
st.info("""
|
||||
**Нет активных задач**
|
||||
|
||||
Загрузите файл на странице "Асинхронная загрузка", чтобы создать новую задачу.
|
||||
Здесь вы сможете отслеживать прогресс обработки и управлять задачами.
|
||||
""")
|
||||
|
||||
# Кнопка для создания тестовой задачи
|
||||
if st.button("🧪 Создать тестовую задачу", key="create_test_task_btn"):
|
||||
test_task_id = f"test_task_{int(time.time())}"
|
||||
if 'upload_tasks' not in st.session_state:
|
||||
st.session_state.upload_tasks = {}
|
||||
|
||||
st.session_state.upload_tasks[test_task_id] = {
|
||||
'status': 'completed',
|
||||
'filename': 'test_file.zip',
|
||||
'endpoint': '/test/upload',
|
||||
'result': {'message': 'Тестовая задача выполнена', 'object_id': 'test-123'},
|
||||
'completed_at': time.time()
|
||||
}
|
||||
st.rerun()
|
||||
|
||||
st.markdown("---")
|
||||
|
||||
# Информация о статусах задач
|
||||
with st.expander("ℹ️ Статусы задач"):
|
||||
st.markdown("""
|
||||
**Статусы задач:**
|
||||
- 🟡 **Ожидает** - задача создана и ожидает выполнения
|
||||
- 🔵 **Выполняется** - задача обрабатывается
|
||||
- 🟢 **Завершена** - задача успешно выполнена
|
||||
- 🔴 **Ошибка** - произошла ошибка при выполнении
|
||||
- ⚫ **Отменена** - задача была отменена пользователем
|
||||
|
||||
**Действия:**
|
||||
- ❌ **Отменить** - отменить выполнение задачи
|
||||
- 🔄 **Обновить** - обновить статус задачи
|
||||
- 📊 **Детали** - просмотреть подробную информацию
|
||||
""")
|
||||
Reference in New Issue
Block a user