stage 02 system status screen
This commit is contained in:
91
app/src/core/system_status.py
Normal file
91
app/src/core/system_status.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import platform
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from src.core.constants import APP_NAME, APP_VERSION
|
||||||
|
from src.core.config import load_settings
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(slots=True)
|
||||||
|
class ComponentStatus:
|
||||||
|
name: str
|
||||||
|
state: str
|
||||||
|
details: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(slots=True)
|
||||||
|
class SystemSnapshot:
|
||||||
|
app_name: str
|
||||||
|
app_version: str
|
||||||
|
app_env: str
|
||||||
|
python_version: str
|
||||||
|
os_name: str
|
||||||
|
timezone_name: str
|
||||||
|
components: list[ComponentStatus]
|
||||||
|
|
||||||
|
|
||||||
|
def get_system_snapshot() -> SystemSnapshot:
|
||||||
|
settings = load_settings()
|
||||||
|
|
||||||
|
components = [
|
||||||
|
ComponentStatus(
|
||||||
|
name="Бот",
|
||||||
|
state="🟢 работает",
|
||||||
|
details="Процесс бота запущен и обрабатывает команды.",
|
||||||
|
),
|
||||||
|
ComponentStatus(
|
||||||
|
name="Telegram",
|
||||||
|
state="🟢 OK",
|
||||||
|
details="Polling активен, базовая маршрутизация подключена.",
|
||||||
|
),
|
||||||
|
ComponentStatus(
|
||||||
|
name="Биржа",
|
||||||
|
state="🟡 не подключена",
|
||||||
|
details="Интеграция с биржей будет добавлена на следующем этапе.",
|
||||||
|
),
|
||||||
|
ComponentStatus(
|
||||||
|
name="База данных",
|
||||||
|
state="🟡 не подключена",
|
||||||
|
details="Слой хранения пока только подготовлен структурно.",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
return SystemSnapshot(
|
||||||
|
app_name=APP_NAME,
|
||||||
|
app_version=APP_VERSION,
|
||||||
|
app_env=settings.app_env,
|
||||||
|
python_version=platform.python_version(),
|
||||||
|
os_name=f"{platform.system()} {platform.release()}",
|
||||||
|
timezone_name=settings.tz,
|
||||||
|
components=components,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_system_text() -> str:
|
||||||
|
snapshot = get_system_snapshot()
|
||||||
|
|
||||||
|
component_lines = []
|
||||||
|
for component in snapshot.components:
|
||||||
|
component_lines.append(
|
||||||
|
f"{component.state} <b>{component.name}</b>\n"
|
||||||
|
f"— {component.details}"
|
||||||
|
)
|
||||||
|
|
||||||
|
components_block = "\n\n".join(component_lines)
|
||||||
|
|
||||||
|
return (
|
||||||
|
"<b>⚙️ Система</b>\n\n"
|
||||||
|
"<b>Статус компонентов</b>\n"
|
||||||
|
f"{components_block}\n\n"
|
||||||
|
"<b>Окружение</b>\n"
|
||||||
|
f"- приложение: {snapshot.app_name} {snapshot.app_version}\n"
|
||||||
|
f"- env: {snapshot.app_env}\n"
|
||||||
|
f"- python: {snapshot.python_version}\n"
|
||||||
|
f"- os: {snapshot.os_name}\n"
|
||||||
|
f"- timezone: {snapshot.timezone_name}\n\n"
|
||||||
|
"<b>Справка</b>\n"
|
||||||
|
"/start — стартовый экран\n"
|
||||||
|
"/menu — показать меню\n"
|
||||||
|
"/help — открыть системную справку"
|
||||||
|
)
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from aiogram import F, Router
|
from aiogram import F, Router
|
||||||
from aiogram.filters import Command
|
from aiogram.filters import Command
|
||||||
from aiogram.types import Message
|
from aiogram.types import Message
|
||||||
|
|
||||||
|
from src.core.system_status import build_system_text
|
||||||
from src.telegram.keyboards.reply import build_main_menu_keyboard
|
from src.telegram.keyboards.reply import build_main_menu_keyboard
|
||||||
from src.telegram.menus import MAIN_MENU_TEXT, SYSTEM_TEXT
|
from src.telegram.menus import MAIN_MENU_TEXT
|
||||||
|
|
||||||
|
|
||||||
router = Router(name="start")
|
router = Router(name="start")
|
||||||
@@ -11,19 +14,31 @@ router = Router(name="start")
|
|||||||
|
|
||||||
@router.message(Command("start"))
|
@router.message(Command("start"))
|
||||||
async def cmd_start(message: Message) -> None:
|
async def cmd_start(message: Message) -> None:
|
||||||
await message.answer(MAIN_MENU_TEXT, reply_markup=build_main_menu_keyboard())
|
await message.answer(
|
||||||
|
MAIN_MENU_TEXT,
|
||||||
|
reply_markup=build_main_menu_keyboard(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.message(Command("menu"))
|
@router.message(Command("menu"))
|
||||||
async def cmd_menu(message: Message) -> None:
|
async def cmd_menu(message: Message) -> None:
|
||||||
await message.answer(MAIN_MENU_TEXT, reply_markup=build_main_menu_keyboard())
|
await message.answer(
|
||||||
|
MAIN_MENU_TEXT,
|
||||||
|
reply_markup=build_main_menu_keyboard(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.message(Command("help"))
|
@router.message(Command("help"))
|
||||||
async def cmd_help(message: Message) -> None:
|
async def cmd_help(message: Message) -> None:
|
||||||
await message.answer(SYSTEM_TEXT, reply_markup=build_main_menu_keyboard())
|
await message.answer(
|
||||||
|
build_system_text(),
|
||||||
|
reply_markup=build_main_menu_keyboard(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.message(F.text == "Меню")
|
@router.message(F.text == "Меню")
|
||||||
async def menu_shortcut(message: Message) -> None:
|
async def menu_shortcut(message: Message) -> None:
|
||||||
await message.answer(MAIN_MENU_TEXT, reply_markup=build_main_menu_keyboard())
|
await message.answer(
|
||||||
|
MAIN_MENU_TEXT,
|
||||||
|
reply_markup=build_main_menu_keyboard(),
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import platform
|
from __future__ import annotations
|
||||||
|
|
||||||
from aiogram import F, Router
|
from aiogram import F, Router
|
||||||
from aiogram.types import Message
|
from aiogram.types import Message
|
||||||
|
|
||||||
from src.core.constants import APP_NAME, APP_VERSION
|
from src.core.system_status import build_system_text
|
||||||
from src.telegram.menus import SYSTEM_TEXT
|
|
||||||
|
|
||||||
|
|
||||||
router = Router(name="system")
|
router = Router(name="system")
|
||||||
@@ -12,10 +11,4 @@ router = Router(name="system")
|
|||||||
|
|
||||||
@router.message(F.text.in_({"⚙️ Система", "⚙ Система"}))
|
@router.message(F.text.in_({"⚙️ Система", "⚙ Система"}))
|
||||||
async def open_system(message: Message) -> None:
|
async def open_system(message: Message) -> None:
|
||||||
runtime_info = (
|
await message.answer(build_system_text())
|
||||||
"\n\n<b>Runtime</b>\n"
|
|
||||||
f"- app: {APP_NAME} {APP_VERSION}\n"
|
|
||||||
f"- python: {platform.python_version()}\n"
|
|
||||||
f"- os: {platform.system()} {platform.release()}"
|
|
||||||
)
|
|
||||||
await message.answer(SYSTEM_TEXT + runtime_info)
|
|
||||||
|
|||||||
14
docs/decisions/0004-system-screen.md
Normal file
14
docs/decisions/0004-system-screen.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# 0004 — System Screen
|
||||||
|
|
||||||
|
## Решение
|
||||||
|
Сделать `Система` отдельным системным экраном, который показывает не только справку, но и снимок состояния приложения.
|
||||||
|
|
||||||
|
## Причины
|
||||||
|
- это удобно для диагностики
|
||||||
|
- это удобно для локальной разработки
|
||||||
|
- это удобно для проверки на Synology
|
||||||
|
- это закладывает основу для health checks
|
||||||
|
|
||||||
|
## Последствия
|
||||||
|
- справка больше не живет отдельно от системного экрана
|
||||||
|
- системный экран становится точкой входа для технического контроля проекта
|
||||||
46
docs/stages/stage-02-system.md
Normal file
46
docs/stages/stage-02-system.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Stage 02 — System
|
||||||
|
|
||||||
|
## Цель
|
||||||
|
Сделать раздел `⚙️ Система` реальным центром контроля приложения, а не просто статическим экраном.
|
||||||
|
|
||||||
|
## Что добавляется
|
||||||
|
- сервис `system_status`, который собирает снимок состояния приложения
|
||||||
|
- единый текст системы, который используется и кнопкой `⚙️ Система`, и командой `/help`
|
||||||
|
- отображение:
|
||||||
|
- статуса бота
|
||||||
|
- статуса Telegram-слоя
|
||||||
|
- статуса интеграции с биржей
|
||||||
|
- статуса слоя хранения
|
||||||
|
- текущего окружения
|
||||||
|
|
||||||
|
## Что должен увидеть пользователь
|
||||||
|
Экран вида:
|
||||||
|
|
||||||
|
- Бот: работает
|
||||||
|
- Telegram: OK
|
||||||
|
- Биржа: не подключена
|
||||||
|
- База данных: не подключена
|
||||||
|
- env / python / os / timezone
|
||||||
|
|
||||||
|
## Почему это важно
|
||||||
|
На следующих этапах сюда будут добавляться:
|
||||||
|
- ping биржи
|
||||||
|
- статус WebSocket
|
||||||
|
- статус БД
|
||||||
|
- статус Redis
|
||||||
|
- последние ошибки
|
||||||
|
- health checks
|
||||||
|
|
||||||
|
## Как проверить
|
||||||
|
1. Запустить бота локально
|
||||||
|
2. Открыть Telegram
|
||||||
|
3. Нажать `⚙️ Система`
|
||||||
|
4. Проверить команду `/help`
|
||||||
|
5. Убедиться, что экран одинаково полезен из кнопки и из команды
|
||||||
|
|
||||||
|
## Commit message
|
||||||
|
Рекомендуемый commit:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git commit -m "stage 02 system status screen"
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user