Add src/selectel_ml_tui/cli.py
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
"""CLI-интерфейс selectel-ml-tui."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
|
||||
import click
|
||||
|
||||
from selectel_ml_tui import __version__
|
||||
from selectel_ml_tui.config import (
|
||||
Credentials,
|
||||
Settings,
|
||||
config_dir,
|
||||
data_dir,
|
||||
load_credentials,
|
||||
load_settings,
|
||||
save_credentials,
|
||||
save_settings,
|
||||
)
|
||||
|
||||
|
||||
def _mask(value: str) -> str:
|
||||
"""Замаскировать секрет: показать первые 4 символа и длину."""
|
||||
if not value:
|
||||
return "(не задан)"
|
||||
return f"{value[:4]}…({len(value)})"
|
||||
|
||||
|
||||
def _print_config(settings: Settings, credentials: Credentials) -> None:
|
||||
click.echo(f"API base URL: {settings.api_base_url}")
|
||||
click.echo(f"Identity URL: {settings.identity_url}")
|
||||
click.echo(f"Refresh interval: {settings.refresh_interval} c")
|
||||
router_id = settings.router_id or "(не задан, будет выбран первый)"
|
||||
click.echo(f"Router id: {router_id}")
|
||||
if settings.region:
|
||||
click.echo(f"Region: {settings.region}")
|
||||
click.echo(f"Service user: {credentials.service_user or '(не задан)'}")
|
||||
click.echo(f"Project id: {credentials.project_id or '(не задан)'}")
|
||||
click.echo(f"Domain id: {credentials.domain_id or '(не задан)'}")
|
||||
click.echo(f"Static token: {_mask(credentials.static_token)}")
|
||||
click.echo(f"Password: {_mask(credentials.password)}")
|
||||
|
||||
|
||||
def _prompt_str(
|
||||
label: str,
|
||||
current: str,
|
||||
hide_input: bool = False,
|
||||
default_note: str = "(Enter — оставить)",
|
||||
) -> str:
|
||||
prompt = f"{label} [{current or 'пусто'}] {default_note}: "
|
||||
value = click.prompt(prompt, default="", hide_input=hide_input, show_default=False)
|
||||
if value == "":
|
||||
return current
|
||||
return cast(str, value)
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.version_option(__version__, prog_name="selectel-ml-tui")
|
||||
def main() -> None:
|
||||
"""TUI-приложение для отображения потребления ИИ-роутера Selectel."""
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.option("--path", is_flag=True, help="Показать пути к конфигурации и выйти")
|
||||
@click.option("--show", is_flag=True, help="Показать текущие настройки и выйти")
|
||||
@click.option(
|
||||
"--non-interactive",
|
||||
is_flag=True,
|
||||
help="Не запрашивать ввод (эквивалентно --show)",
|
||||
)
|
||||
def config(path: bool, show: bool, non_interactive: bool) -> None:
|
||||
"""Настроить приложение (интерактивный мастер) или показать настройки."""
|
||||
if path:
|
||||
click.echo(f"config dir: {config_dir()}")
|
||||
click.echo(f"data dir: {data_dir()}")
|
||||
return
|
||||
|
||||
settings = load_settings()
|
||||
credentials = load_credentials()
|
||||
|
||||
if show or non_interactive:
|
||||
_print_config(settings, credentials)
|
||||
return
|
||||
|
||||
click.echo("Настройка selectel-ml-tui. Пустые значения сохраняют текущие.")
|
||||
settings.api_base_url = _prompt_str("API base URL", settings.api_base_url)
|
||||
while True:
|
||||
raw = _prompt_str(
|
||||
"Интервал обновления (сек, min 10)",
|
||||
str(settings.refresh_interval),
|
||||
)
|
||||
try:
|
||||
value = int(raw)
|
||||
except ValueError:
|
||||
click.echo("Ошибка: введите целое число секунд")
|
||||
continue
|
||||
if value < 10:
|
||||
click.echo("Ошибка: интервал не может быть меньше 10 секунд")
|
||||
continue
|
||||
settings.refresh_interval = value
|
||||
break
|
||||
settings.router_id = _prompt_str(
|
||||
"ID ИИ-роутера (необязательно)", settings.router_id
|
||||
)
|
||||
settings.region = _prompt_str("Регион (необязательно)", settings.region)
|
||||
|
||||
credentials.service_user = _prompt_str(
|
||||
"Сервисный пользователь", credentials.service_user
|
||||
)
|
||||
credentials.domain_id = _prompt_str(
|
||||
"Номер аккаунта (domain)", credentials.domain_id
|
||||
)
|
||||
credentials.project_id = _prompt_str("ID проекта (ML)", credentials.project_id)
|
||||
credentials.password = _prompt_str(
|
||||
"Пароль сервисного пользователя",
|
||||
credentials.password,
|
||||
hide_input=True,
|
||||
)
|
||||
credentials.static_token = _prompt_str(
|
||||
"Статический токен (X-Token)",
|
||||
credentials.static_token,
|
||||
hide_input=True,
|
||||
)
|
||||
|
||||
save_settings(settings)
|
||||
save_credentials(credentials)
|
||||
click.echo(f"Сохранено: {config_dir()}")
|
||||
_print_config(settings, credentials)
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.option(
|
||||
"--format",
|
||||
"fmt",
|
||||
type=click.Choice(["csv", "json"]),
|
||||
default="json",
|
||||
help="Формат экспорта",
|
||||
)
|
||||
@click.option(
|
||||
"--output",
|
||||
"-o",
|
||||
type=click.Path(path_type=Path),
|
||||
default=None,
|
||||
help="Путь к файлу экспорта (по умолчанию history.csv/json в каталоге данных)",
|
||||
)
|
||||
def export(fmt: str, output: Path | None) -> None:
|
||||
"""Экспортировать историю потребления в CSV/JSON."""
|
||||
from selectel_ml_tui.storage import Storage
|
||||
|
||||
path = output if output is not None else data_dir() / f"history.{fmt}"
|
||||
storage = Storage()
|
||||
try:
|
||||
if fmt == "csv":
|
||||
storage.export_csv(path)
|
||||
else:
|
||||
storage.export_json(path)
|
||||
finally:
|
||||
storage.close()
|
||||
click.echo(f"Экспортировано: {path}")
|
||||
|
||||
|
||||
@main.command()
|
||||
def doctor() -> None:
|
||||
"""Проверить окружение (ОС, терминал, кодировку) и пути."""
|
||||
from selectel_ml_tui.platform import check_environment, get_platform_info
|
||||
|
||||
info = get_platform_info()
|
||||
click.echo(f"ОС: {info.system}")
|
||||
click.echo(f"Python: {info.python_version}")
|
||||
failed = False
|
||||
for check in check_environment():
|
||||
mark = "OK " if check.ok else "WARN"
|
||||
failed = failed or not check.ok
|
||||
click.echo(f"[{mark}] {check.name}: {check.detail}")
|
||||
credentials = load_credentials()
|
||||
has_token = bool(credentials.static_token or credentials.password)
|
||||
hint = "не настроен — выполните: selectel-ml-tui config"
|
||||
token_status = "настроен" if has_token else hint
|
||||
click.echo(f"[{'OK ' if has_token else 'WARN'}] token: {token_status}")
|
||||
click.echo(f"Config dir: {config_dir()}")
|
||||
click.echo(f"Data dir: {data_dir()}")
|
||||
if failed:
|
||||
raise click.exceptions.Exit(1)
|
||||
|
||||
|
||||
@main.command()
|
||||
def run() -> None:
|
||||
"""Запустить TUI-интерфейс (дашборд)."""
|
||||
from selectel_ml_tui.ui.app import run_app
|
||||
|
||||
credentials = load_credentials()
|
||||
if not credentials.static_token and not credentials.password:
|
||||
msg = "Учётные данные не настроены. Выполните: selectel-ml-tui config"
|
||||
click.echo(msg, err=True)
|
||||
raise click.exceptions.Exit(1)
|
||||
run_app()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user