add: первый экран TUI

This commit is contained in:
Sergei Erjemin 2025-06-08 16:24:19 +03:00
parent ec1b585fb2
commit e6dd940d8a
5 changed files with 110 additions and 5 deletions

View File

@ -6,6 +6,8 @@ authors = ["erjemin <erjemin@gmail.com>"]
readme = "README.md" readme = "README.md"
packages = [{ include = "pganec", from = "src" }] packages = [{ include = "pganec", from = "src" }]
[tool.poetry.scripts]
pganec = "pganec.main:run"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.8.1,<4.0.0" python = ">=3.8.1,<4.0.0"

View File

@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
# src/pganec/_init__.py
""" """
PGanec - TUI для резервного копирования и восстановления баз данных PostgreSQL. PGanec - TUI для резервного копирования и восстановления баз данных PostgreSQL.

View File

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# src/pganec/config.py
import logging import logging
import yaml import yaml
from typing import Optional, Dict # Для Python < 3.9 используйте Dict, для Python 3.9+ можно просто dict from typing import Optional, Dict # Для Python < 3.9 используйте Dict, для Python 3.9+ можно просто dict

View File

@ -1,8 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# src/pganec/config.py
import argparse import argparse
import logging import logging
import sys import sys
from config import load_config, ConfigError, ConfigNotFoundError, InvalidConfigFormatError from config import load_config, ConfigError, ConfigNotFoundError, InvalidConfigFormatError
from tui import PGanecApp
# --- Настройки и инициирование логирования --- # --- Настройки и инициирование логирования ---
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -57,12 +59,13 @@ if __name__ == '__main__':
sys.exit(1) sys.exit(1)
except ConfigError as e: # Общая ошибка конфигурации (должна быть последней в цепочке) except ConfigError as e: # Общая ошибка конфигурации (должна быть последней в цепочке)
print(f"Ошибка при загрузке конфигурации: {e}", file=sys.stderr) print(f"Ошибка при загрузке конфигурации: {e}", file=sys.stderr)
sys.exit(1) sys.exit(1)
except Exception as e: # Для совсем непредвиденных, не ConfigError
logger.critical(f"Необработанная ошибка во время инициализации: {e}", exc_info=True)
print(f"Произошла критическая и непредвиденная ошибка: {e}", file=sys.stderr)
sys.exit(2)
print_hi('PyCharm') # Запуск главного меню TUI
PGanecApp().run()
# with open("config.yaml", "r") as f:
# config = yaml.safe_load(f)

97
src/pganec/tui.py Normal file
View File

@ -0,0 +1,97 @@
# -*- coding: utf-8 -*-
# src/pganec/tui.py
import logging
from textual.app import App, ComposeResult
from textual.containers import Vertical, Horizontal
from textual.widgets import Button, Header, Footer, Static
logger = logging.getLogger(__name__)
class MainMenu(Static):
def compose(self) -> ComposeResult:
yield Horizontal(
Button(label="Backup (DB -> Dump)", id="backup", variant="primary"),
Button(label="Restore (Dump -> DB)", id="restore", variant="primary"),
Button(label="Copy (DB -> DB)", id="copy", variant="error"),
Button(label="Quit", id="quit", variant="error"),
id="menu",
)
yield Footer()
class PGanecApp(App):
CSS = """
#menu {
width: 100%;
align: left top;
padding: 0;
}
Button {
align: left middle;
text-align: left;
width: 25%;
color: orange;
background: transparent;
margin: 0;
padding: 0;
}
Button.-primary {
color: orange;
border: round orange;
text-align: left;
}
Button.-error {
color: grey;
border: round orange;
text-align: left;
}
Button:focus {
border: heavy green;
}
"""
BINDINGS = [
("b", "backup", "Backup"),
("r", "restore", "Restore"),
("с", "copy", "Copy"),
("q", "quit", "Quit"),
("right", "focus_next"), ("down", "focus_next"),
("left", "focus_previous"), ("up", "focus_previous"),
("enter", "activate"),
]
def compose(self) -> ComposeResult:
yield Header()
yield Static("PGanec", id="title")
yield MainMenu()
yield Footer()
async def on_button_pressed(self, event: Button.Pressed) -> None:
button_id = event.button.id
if button_id == "backup":
await self.push_screen(BackupScreen()) # пока заглушка
elif button_id == "restore":
await self.push_screen(RestoreScreen()) # пока заглушка
elif button_id == "copy":
await self.action_quit()
elif button_id == "quit":
await self.action_quit()
async def action_activate(self) -> None:
focused = self.focused
if isinstance(focused, Button):
await self.on_button_pressed(Button.Pressed(focused))
# Заглушки для других экранов:
class BackupScreen(Static):
def on_mount(self) -> None:
self.update("🛠 Здесь будет экран выбора сервера для бэкапа.")
class RestoreScreen(Static):
def on_mount(self) -> None:
self.update("♻️ Здесь будет экран выбора сервера и бэкапа для восстановления.")