Stage 06.2 - system settings navigation and product information screens
This commit is contained in:
@@ -1,2 +1,4 @@
|
|||||||
|
# app/src/core/constants.py
|
||||||
|
|
||||||
APP_NAME = "Dzentra Bot"
|
APP_NAME = "Dzentra Bot"
|
||||||
APP_VERSION = "2.0.0"
|
APP_VERSION = "2.0.0"
|
||||||
@@ -183,8 +183,8 @@ def build_system_text(*, include_updated_at: bool = False) -> str:
|
|||||||
|
|
||||||
text = (
|
text = (
|
||||||
"<b>🖥️ Система</b>\n"
|
"<b>🖥️ Система</b>\n"
|
||||||
f"🔸 <b>{snapshot.mode_label}</b>\n"
|
f"🔸 <b>{snapshot.mode_label}</b>\n\n"
|
||||||
f"⏱️ {snapshot.timezone_name}\n\n"
|
# f"⏱️ {snapshot.timezone_name}\n\n"
|
||||||
f"{components_block}"
|
f"{components_block}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
# app/src/telegram/handlers/auto.py
|
# app/src/telegram/handlers/auto.py
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from aiogram import F, Router
|
from aiogram import F, Router
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
from aiogram.types import Message
|
from aiogram.types import CallbackQuery, InlineKeyboardMarkup, Message
|
||||||
|
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||||
|
|
||||||
from src.telegram.menus import AUTO_TEXT
|
from src.telegram.menus import AUTO_TEXT
|
||||||
|
|
||||||
@@ -10,8 +13,37 @@ from src.telegram.menus import AUTO_TEXT
|
|||||||
router = Router(name="auto")
|
router = Router(name="auto")
|
||||||
|
|
||||||
|
|
||||||
@router.message(F.text == "🤖 Авто")
|
def _auto_keyboard() -> InlineKeyboardMarkup:
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
builder.button(text="🛠️ Настройки", callback_data="settings:auto")
|
||||||
|
builder.adjust(1)
|
||||||
|
return builder.as_markup()
|
||||||
|
|
||||||
|
|
||||||
|
async def _render_auto_screen(
|
||||||
|
target_message: Message,
|
||||||
|
*,
|
||||||
|
edit_mode: bool,
|
||||||
|
) -> None:
|
||||||
|
if edit_mode:
|
||||||
|
await target_message.edit_text(AUTO_TEXT, reply_markup=_auto_keyboard())
|
||||||
|
else:
|
||||||
|
await target_message.answer(AUTO_TEXT, reply_markup=_auto_keyboard())
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(F.text.in_({"🤖 Автоторговля", "🤖 Авто"}))
|
||||||
async def open_auto(message: Message, state: FSMContext) -> None:
|
async def open_auto(message: Message, state: FSMContext) -> None:
|
||||||
# Глобальный экран: всегда выходим из текущего FSM-сценария.
|
|
||||||
await state.clear()
|
await state.clear()
|
||||||
await message.answer(AUTO_TEXT)
|
await _render_auto_screen(message, edit_mode=False)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data == "auto:home")
|
||||||
|
async def open_auto_from_callback(callback: CallbackQuery, state: FSMContext) -> None:
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
if callback.message is None:
|
||||||
|
await callback.answer("Сообщение не найдено", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
await _render_auto_screen(callback.message, edit_mode=True)
|
||||||
|
await callback.answer()
|
||||||
@@ -8,6 +8,8 @@ from aiogram.types import CallbackQuery, InlineKeyboardMarkup, Message
|
|||||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||||
|
|
||||||
from src.core.system_status import build_system_text, get_system_snapshot, has_system_alerts
|
from src.core.system_status import build_system_text, get_system_snapshot, has_system_alerts
|
||||||
|
from src.core.config import load_settings
|
||||||
|
from src.core.constants import APP_NAME, APP_VERSION
|
||||||
from src.trading.journal.service import JournalService
|
from src.trading.journal.service import JournalService
|
||||||
|
|
||||||
|
|
||||||
@@ -161,6 +163,77 @@ async def open_system_management(callback: CallbackQuery) -> None:
|
|||||||
await callback.answer()
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data == "settings:auto")
|
||||||
|
async def open_auto_settings(callback: CallbackQuery) -> None:
|
||||||
|
if callback.message is None:
|
||||||
|
await callback.answer("Сообщение не найдено", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
text = (
|
||||||
|
"<b>🤖 Автоторговля</b>\n\n"
|
||||||
|
"<b>СИСТЕМА</b> · Настройки\n\n"
|
||||||
|
"Статус: —\n"
|
||||||
|
"Стратегия: —\n"
|
||||||
|
"Риск: —\n\n"
|
||||||
|
"В разработке."
|
||||||
|
)
|
||||||
|
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
builder.button(text="⬅️ Назад", callback_data="system:management")
|
||||||
|
builder.button(text="🤖 Автоторговля", callback_data="auto:home")
|
||||||
|
builder.adjust(2)
|
||||||
|
|
||||||
|
await callback.message.edit_text(text, reply_markup=builder.as_markup())
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data == "settings:trade")
|
||||||
|
async def open_trade_settings(callback: CallbackQuery) -> None:
|
||||||
|
if callback.message is None:
|
||||||
|
await callback.answer("Сообщение не найдено", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
text = (
|
||||||
|
"<b>📊 Торговля</b>\n\n"
|
||||||
|
"<b>СИСТЕМА</b> · Настройки\n\n"
|
||||||
|
"Инструмент: —\n"
|
||||||
|
"Тип ордера по умолчанию: —\n"
|
||||||
|
"Пресеты количества: —\n\n"
|
||||||
|
"В разработке."
|
||||||
|
)
|
||||||
|
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
builder.button(text="⬅️ Назад", callback_data="system:management")
|
||||||
|
builder.button(text="📊 Торговля", callback_data="trade:home")
|
||||||
|
builder.adjust(2)
|
||||||
|
|
||||||
|
await callback.message.edit_text(text, reply_markup=builder.as_markup())
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data == "settings:general")
|
||||||
|
async def open_general_settings(callback: CallbackQuery) -> None:
|
||||||
|
if callback.message is None:
|
||||||
|
await callback.answer("Сообщение не найдено", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
text = (
|
||||||
|
"<b>🌍 Общие</b>\n\n"
|
||||||
|
"<b>СИСТЕМА</b> · Настройки\n\n"
|
||||||
|
"Режим аккаунта: —\n"
|
||||||
|
"Часовой пояс: —\n"
|
||||||
|
"Язык интерфейса: ru\n\n"
|
||||||
|
"В разработке."
|
||||||
|
)
|
||||||
|
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
builder.button(text="⬅️ Назад", callback_data="system:management")
|
||||||
|
builder.adjust(1)
|
||||||
|
|
||||||
|
await callback.message.edit_text(text, reply_markup=builder.as_markup())
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data == "settings:journal")
|
@router.callback_query(F.data == "settings:journal")
|
||||||
async def open_journal_settings(callback: CallbackQuery) -> None:
|
async def open_journal_settings(callback: CallbackQuery) -> None:
|
||||||
if callback.message is None:
|
if callback.message is None:
|
||||||
@@ -184,7 +257,7 @@ async def open_journal_settings(callback: CallbackQuery) -> None:
|
|||||||
builder.button(text="🗄 Архив", callback_data="settings:journal_archive")
|
builder.button(text="🗄 Архив", callback_data="settings:journal_archive")
|
||||||
builder.button(text="📦 Лимит", callback_data="settings:journal_limit")
|
builder.button(text="📦 Лимит", callback_data="settings:journal_limit")
|
||||||
builder.button(text="⏳ Хранение", callback_data="settings:journal_retention")
|
builder.button(text="⏳ Хранение", callback_data="settings:journal_retention")
|
||||||
builder.button(text="⬅️ Назад", callback_data="system:control")
|
builder.button(text="⬅️ Назад", callback_data="system:management")
|
||||||
builder.button(text="📒 Журнал", callback_data="journal:1")
|
builder.button(text="📒 Журнал", callback_data="journal:1")
|
||||||
builder.adjust(2, 2, 2)
|
builder.adjust(2, 2, 2)
|
||||||
|
|
||||||
@@ -195,6 +268,88 @@ async def open_journal_settings(callback: CallbackQuery) -> None:
|
|||||||
await callback.answer()
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data == "settings:journal_archive")
|
||||||
|
async def open_journal_archive_settings(callback: CallbackQuery) -> None:
|
||||||
|
if callback.message is None:
|
||||||
|
await callback.answer("Сообщение не найдено", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
text = (
|
||||||
|
"<b>🗄 Архив</b>\n\n"
|
||||||
|
"<b>СИСТЕМА</b> · Настройки · Журнал\n\n"
|
||||||
|
"Автоматический архив журнала: —\n"
|
||||||
|
"Формат архива: —\n"
|
||||||
|
"Периодичность: —\n\n"
|
||||||
|
"В разработке."
|
||||||
|
)
|
||||||
|
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
builder.button(text="⬅️ Назад", callback_data="settings:journal")
|
||||||
|
builder.button(text="📒 Журнал", callback_data="journal:1")
|
||||||
|
builder.adjust(2)
|
||||||
|
|
||||||
|
await callback.message.edit_text(text, reply_markup=builder.as_markup())
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data == "settings:journal_limit")
|
||||||
|
async def open_journal_limit_settings(callback: CallbackQuery) -> None:
|
||||||
|
if callback.message is None:
|
||||||
|
await callback.answer("Сообщение не найдено", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
text = (
|
||||||
|
"<b>📦 Лимит</b>\n\n"
|
||||||
|
"<b>СИСТЕМА</b> · Настройки · Журнал\n\n"
|
||||||
|
"Текущий лимит: —\n\n"
|
||||||
|
"Доступные варианты:"
|
||||||
|
)
|
||||||
|
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
builder.button(text="1 000", callback_data="settings:journal_limit_stub")
|
||||||
|
builder.button(text="5 000", callback_data="settings:journal_limit_stub")
|
||||||
|
builder.button(text="10 000", callback_data="settings:journal_limit_stub")
|
||||||
|
builder.button(text="∞", callback_data="settings:journal_limit_stub")
|
||||||
|
builder.button(text="⬅️ Назад", callback_data="settings:journal")
|
||||||
|
builder.adjust(2, 2, 1)
|
||||||
|
|
||||||
|
await callback.message.edit_text(text, reply_markup=builder.as_markup())
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data == "settings:journal_retention")
|
||||||
|
async def open_journal_retention_settings(callback: CallbackQuery) -> None:
|
||||||
|
if callback.message is None:
|
||||||
|
await callback.answer("Сообщение не найдено", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
text = (
|
||||||
|
"<b>⏳ Хранение</b>\n\n"
|
||||||
|
"<b>СИСТЕМА</b> · Настройки · Журнал\n\n"
|
||||||
|
"Текущий срок хранения: —\n\n"
|
||||||
|
"Доступные варианты:"
|
||||||
|
)
|
||||||
|
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
builder.button(text="7 дней", callback_data="settings:journal_retention_stub")
|
||||||
|
builder.button(text="30 дней", callback_data="settings:journal_retention_stub")
|
||||||
|
builder.button(text="90 дней", callback_data="settings:journal_retention_stub")
|
||||||
|
builder.button(text="∞", callback_data="settings:journal_retention_stub")
|
||||||
|
builder.button(text="⬅️ Назад", callback_data="settings:journal")
|
||||||
|
builder.adjust(2, 2, 1)
|
||||||
|
|
||||||
|
await callback.message.edit_text(text, reply_markup=builder.as_markup())
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.in_({
|
||||||
|
"settings:journal_limit_stub",
|
||||||
|
"settings:journal_retention_stub",
|
||||||
|
}))
|
||||||
|
async def journal_settings_stub(callback: CallbackQuery) -> None:
|
||||||
|
await callback.answer("Настройка скоро появится", show_alert=True)
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data == "system:back")
|
@router.callback_query(F.data == "system:back")
|
||||||
async def back_to_system(callback: CallbackQuery) -> None:
|
async def back_to_system(callback: CallbackQuery) -> None:
|
||||||
if callback.message is None:
|
if callback.message is None:
|
||||||
@@ -216,4 +371,39 @@ async def back_to_system(callback: CallbackQuery) -> None:
|
|||||||
|
|
||||||
@router.callback_query(F.data == "system:about")
|
@router.callback_query(F.data == "system:about")
|
||||||
async def open_system_about(callback: CallbackQuery) -> None:
|
async def open_system_about(callback: CallbackQuery) -> None:
|
||||||
await callback.answer("О продукте скоро появится")
|
if callback.message is None:
|
||||||
|
await callback.answer("Сообщение не найдено", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
settings = load_settings()
|
||||||
|
journal = JournalService()
|
||||||
|
|
||||||
|
journal.log_ui_info(
|
||||||
|
event_type="system_about_opened",
|
||||||
|
message="Открыта информация о продукте.",
|
||||||
|
screen="system",
|
||||||
|
action="about",
|
||||||
|
user_id=callback.from_user.id if callback.from_user else None,
|
||||||
|
chat_id=callback.message.chat.id if callback.message.chat else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
text = (
|
||||||
|
"<b>ℹ️ Информация</b>\n\n"
|
||||||
|
"<b>СИСТЕМА</b>\n\n"
|
||||||
|
f"<b>{APP_NAME}</b>\n"
|
||||||
|
f"Версия: {APP_VERSION}\n"
|
||||||
|
f"Режим: {'DEMO' if 'demo' in settings.exchange_base_url.lower() else 'LIVE'}\n"
|
||||||
|
f"Часовой пояс: {settings.tz}\n\n"
|
||||||
|
"Торговый Telegram-бот для контроля рынка, портфеля, журнала событий "
|
||||||
|
"и будущей автоторговли."
|
||||||
|
)
|
||||||
|
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
builder.button(text="⬅️ Назад", callback_data="system:back")
|
||||||
|
builder.adjust(1)
|
||||||
|
|
||||||
|
await callback.message.edit_text(
|
||||||
|
text,
|
||||||
|
reply_markup=builder.as_markup(),
|
||||||
|
)
|
||||||
|
await callback.answer()
|
||||||
@@ -33,7 +33,7 @@ def _trade_home_keyboard() -> InlineKeyboardMarkup:
|
|||||||
builder.button(text="📝 Ордер", callback_data="trade:new_order")
|
builder.button(text="📝 Ордер", callback_data="trade:new_order")
|
||||||
builder.button(text="📂 Ордера", callback_data="trade:orders")
|
builder.button(text="📂 Ордера", callback_data="trade:orders")
|
||||||
builder.button(text="📜 История", callback_data="trade:history")
|
builder.button(text="📜 История", callback_data="trade:history")
|
||||||
builder.button(text="⚙️ Настройки", callback_data="trade:settings")
|
builder.button(text="🛠️ Настройки", callback_data="settings:trade")
|
||||||
builder.adjust(2, 2)
|
builder.adjust(2, 2)
|
||||||
return builder.as_markup()
|
return builder.as_markup()
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
# 0014 — System Navigation and Journal Settings
|
|
||||||
|
|
||||||
## Решение
|
|
||||||
Вынести журнал и системные настройки в отдельный раздел “Система” с многоуровневой навигацией.
|
|
||||||
|
|
||||||
## Причины
|
|
||||||
- разгрузить главное меню
|
|
||||||
- разделить operational UI и settings UI
|
|
||||||
- упростить масштабирование новых системных экранов
|
|
||||||
|
|
||||||
## Последствия
|
|
||||||
- появляется единый navigation pattern
|
|
||||||
- журнал становится отдельным subsystem
|
|
||||||
- проще расширять настройки
|
|
||||||
17
docs/decisions/0014-system-settings-navigation.md
Normal file
17
docs/decisions/0014-system-settings-navigation.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# 0014 — System settings as centralized navigation hub
|
||||||
|
|
||||||
|
## Решение
|
||||||
|
|
||||||
|
Все настройки продукта централизуются через экран Система → Настройки.
|
||||||
|
|
||||||
|
## Причины
|
||||||
|
|
||||||
|
- единая точка входа
|
||||||
|
|
||||||
|
- масштабируемость
|
||||||
|
|
||||||
|
- понятная UX-структура
|
||||||
|
|
||||||
|
## Последствия
|
||||||
|
|
||||||
|
- все разделы получают связку экран ↔ настройки
|
||||||
@@ -76,7 +76,9 @@
|
|||||||
✔ system navigation redesign
|
✔ system navigation redesign
|
||||||
|
|
||||||
### 06.2
|
### 06.2
|
||||||
⏳ archive / backup
|
✔ system settings navigation
|
||||||
|
✔ product information screen
|
||||||
|
✔ settings stubs
|
||||||
|
|
||||||
### 06.3
|
### 06.3
|
||||||
⏳ retention / limits
|
⏳ retention / limits
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
# Stage 06.2 — System Settings Navigation and Product Information
|
||||||
|
|
||||||
|
## Что сделано
|
||||||
|
|
||||||
|
### 1. Экран ℹ️ Информация
|
||||||
|
Добавлен полноценный экран информации о продукте.
|
||||||
|
|
||||||
|
Показывает:
|
||||||
|
- название продукта
|
||||||
|
- версия
|
||||||
|
- режим работы (DEMO / LIVE)
|
||||||
|
- timezone
|
||||||
|
- описание продукта
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Новый navigation pattern
|
||||||
|
|
||||||
|
Добавлена двусторонняя навигация:
|
||||||
|
|
||||||
|
СИСТЕМА ↔ НАСТРОЙКИ ↔ ЭКРАНЫ
|
||||||
|
|
||||||
|
Реализовано для:
|
||||||
|
|
||||||
|
- 🤖 Автоторговля
|
||||||
|
- 📊 Торговля
|
||||||
|
- 📒 Журнал
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Настройки Журнала
|
||||||
|
|
||||||
|
Добавлены отдельные экраны:
|
||||||
|
|
||||||
|
- 🗑 Очистка
|
||||||
|
- 🗄 Архив
|
||||||
|
- 📦 Лимит
|
||||||
|
- ⏳ Хранение
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Заглушки настроек
|
||||||
|
|
||||||
|
Созданы шаблонные экраны:
|
||||||
|
|
||||||
|
#### 🤖 Автоторговля
|
||||||
|
- статус
|
||||||
|
- стратегия
|
||||||
|
- риск
|
||||||
|
|
||||||
|
#### 📊 Торговля
|
||||||
|
- инструмент
|
||||||
|
- тип ордера
|
||||||
|
- пресеты количества
|
||||||
|
|
||||||
|
#### 🌍 Общие
|
||||||
|
- режим
|
||||||
|
- timezone
|
||||||
|
- язык
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Обновление UI
|
||||||
|
|
||||||
|
Убран timezone с главного экрана Система.
|
||||||
|
|
||||||
|
Экран стал компактнее.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Commit
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -m "Stage 06.2 - system settings navigation and product information screens"
|
||||||
Reference in New Issue
Block a user