feat(ui): emoji icons on menu buttons and screen titles
This commit is contained in:
1 parent
a471b1dfe2
commit
6ef321dc9f
13 files changed
+187
-159
No files matched your search
@@ -178,17 +178,17 @@ def alert_keyboard(payload: dict) -> InlineKeyboardMarkup | None:
|
||||
kb = InlineKeyboardBuilder()
|
||||
if env.komodo.enabled and target_id:
|
||||
if target_type == "Stack":
|
||||
kb.button(text="Стек", callback_data=f"go:stack:{target_id}")
|
||||
kb.button(text="Логи", callback_data=f"go:log:{target_id}")
|
||||
kb.button(text="📦 Стек", callback_data=f"go:stack:{target_id}")
|
||||
kb.button(text="📜 Логи", callback_data=f"go:log:{target_id}")
|
||||
elif target_type == "Server":
|
||||
kb.button(text="Сервер", callback_data=f"go:srv:{target_id}")
|
||||
kb.button(text="🖥 Сервер", callback_data=f"go:srv:{target_id}")
|
||||
elif target_type == "Action":
|
||||
kb.button(text="Действие", callback_data=f"go:act:a:{target_id}")
|
||||
kb.button(text="🎛 Действие", callback_data=f"go:act:a:{target_id}")
|
||||
elif target_type == "Procedure":
|
||||
kb.button(text="Процедура", callback_data=f"go:act:p:{target_id}")
|
||||
kb.button(text="🧩 Процедура", callback_data=f"go:act:p:{target_id}")
|
||||
mute = f"al:mute:{kind}"
|
||||
if kind != "Test" and len(mute) <= CALLBACK_LIMIT:
|
||||
kb.button(text="Не присылать такие", callback_data=mute)
|
||||
kb.button(text="🔕 Не присылать такие", callback_data=mute)
|
||||
buttons = len(list(kb.buttons))
|
||||
if not buttons:
|
||||
return None
|
||||
|
||||
@@ -38,10 +38,10 @@ async def _services_block() -> list[str]:
|
||||
uptime = (window - lost) / window * 100 if window else None
|
||||
falls = sum(row[2] for row in rows)
|
||||
if not falls:
|
||||
return [f"<b>Сервисы</b>: аптайм {pct(uptime)}, падений не было"]
|
||||
return [f"📋 <b>Сервисы</b>: аптайм {pct(uptime)}, падений не было"]
|
||||
word = plural(falls, "падение", "падения", "падений")
|
||||
lines = [
|
||||
f"<b>Сервисы</b>: аптайм {pct(uptime)}, {falls} {word}, "
|
||||
f"📋 <b>Сервисы</b>: аптайм {pct(uptime)}, {falls} {word}, "
|
||||
f"даунтайм {human_duration(lost)}"
|
||||
]
|
||||
for down, service, count in sorted(rows, key=lambda row: -row[0])[:SHOWN]:
|
||||
@@ -71,10 +71,10 @@ async def _fleet_block() -> list[str]:
|
||||
komodo.names(),
|
||||
)
|
||||
except KomodoError as exc:
|
||||
return [f"<b>Флот</b>: {esc(exc)}"]
|
||||
return [f"🛰 <b>Флот</b>: {esc(exc)}"]
|
||||
tz = db.settings.get("tz")
|
||||
failed = [u for u in updates if u.done and not u.success]
|
||||
lines = [f"<b>Флот</b>: операций {len(updates)}, неудачных {len(failed)}"]
|
||||
lines = [f"🛰 <b>Флот</b>: операций {len(updates)}, неудачных {len(failed)}"]
|
||||
grouped: dict[tuple[str, str], list[Update]] = {}
|
||||
for update in failed:
|
||||
grouped.setdefault((update.operation, update.target_id), []).append(update)
|
||||
@@ -93,7 +93,7 @@ async def digest_text() -> str:
|
||||
blocks = [await _services_block()]
|
||||
if env.komodo.enabled:
|
||||
blocks.append(await _fleet_block())
|
||||
lines = ["<b>Сводка за сутки</b>"]
|
||||
lines = ["🗞 <b>Сводка за сутки</b>"]
|
||||
for block in blocks:
|
||||
if block:
|
||||
lines += ["", *block]
|
||||
|
||||
@@ -19,7 +19,7 @@ from healthbot.actionargs import (
|
||||
)
|
||||
from healthbot.filters import Admin
|
||||
from healthbot.handlers.menu import sync_commands
|
||||
from healthbot.keyboards import back_to, back_to_menu
|
||||
from healthbot.keyboards import back_to, back_to_menu, pin_text
|
||||
from healthbot.komodo import Detail, KomodoError, Resource, Update, komodo
|
||||
from healthbot.runs import track
|
||||
from healthbot.ui import (
|
||||
@@ -47,7 +47,8 @@ WIDE_FORM = 6
|
||||
MAX_LABEL = 40
|
||||
KIND_TITLE = {"a": "Действия", "p": "Процедуры"}
|
||||
KIND_ONE = {"a": "Действие", "p": "Процедура"}
|
||||
KIND_BACK = {"a": "К действию", "p": "К процедуре"}
|
||||
KIND_BACK = {"a": "⬅️ К действию", "p": "⬅️ К процедуре"}
|
||||
KIND_ICON = {"a": "🎛", "p": "🧩"}
|
||||
USAGE_FIELDS = 3
|
||||
RUN_STATE = {"Ok": "успешно", "Failed": "с ошибкой", "Running": "идёт сейчас"}
|
||||
MODE_TEXT = {PinMode.INSTANT: "запускается сразу", PinMode.FORM: "открывает форму"}
|
||||
@@ -96,12 +97,13 @@ async def list_view(kind: str, page: int) -> tuple[str, types.InlineKeyboardMark
|
||||
rows = [2] * (len(chunk) // 2) + [1] * (len(chunk) % 2)
|
||||
nav = pager(kb, f"act:list:{kind}", page, total)
|
||||
kb.button(
|
||||
text=f"{KIND_TITLE[other]}: {other_count}", callback_data=f"act:list:{other}:0"
|
||||
text=f"{KIND_ICON[other]} {KIND_TITLE[other]}: {other_count}",
|
||||
callback_data=f"act:list:{other}:0",
|
||||
)
|
||||
kb.button(text="Закреплённые", callback_data="set:pins")
|
||||
kb.button(text="Меню", callback_data="menu:main")
|
||||
kb.button(text="📌 Закреплённые", callback_data="set:pins")
|
||||
kb.button(text="🏠 Меню", callback_data="menu:main")
|
||||
kb.adjust(*rows, *([nav] if nav else []), 2, 1)
|
||||
text = f"<b>{KIND_TITLE[kind]}</b>: {len(items)}"
|
||||
text = f"{KIND_ICON[kind]} <b>{KIND_TITLE[kind]}</b>: {len(items)}"
|
||||
if not items:
|
||||
text += "\nКлюч бота не видит ни одного. Нужен доступ Execute в Komodo."
|
||||
return text, kb.as_markup()
|
||||
@@ -170,7 +172,7 @@ async def card_view(
|
||||
await state.set_state(None)
|
||||
await state.update_data(form=form)
|
||||
|
||||
lines = [f"<b>{esc(detail.name)}</b>"]
|
||||
lines = [f"{KIND_ICON[kind]} <b>{esc(detail.name)}</b>"]
|
||||
if detail.description:
|
||||
lines.append(esc(detail.description))
|
||||
if detail.stages:
|
||||
@@ -195,18 +197,18 @@ async def card_view(
|
||||
for index, key in enumerate(defaults):
|
||||
value = form["values"].get(key, defaults[key])
|
||||
kb.button(
|
||||
text=label(f"{key}: {render(value)}", 40),
|
||||
text=label(f"✏️ {key}: {render(value)}", 40),
|
||||
callback_data=f"act:f:{index}:{ref}",
|
||||
)
|
||||
width = 2 if len(defaults) > WIDE_FORM else 1
|
||||
rows = [width] * (len(defaults) // width) + [1] * (len(defaults) % width)
|
||||
kb.button(text="Запустить", callback_data=f"act:run:{ref}", style=SUCCESS)
|
||||
kb.button(text="Закрепить", callback_data=f"act:pin:{ref}")
|
||||
kb.button(text="▶️ Запустить", callback_data=f"act:run:{ref}", style=SUCCESS)
|
||||
kb.button(text="📌 Закрепить", callback_data=f"act:pin:{ref}")
|
||||
tail = 2
|
||||
if form["values"]:
|
||||
kb.button(text="Сбросить", callback_data=f"act:reset:{ref}")
|
||||
kb.button(text="↩️ Сбросить", callback_data=f"act:reset:{ref}")
|
||||
tail = 3
|
||||
kb.button(text="К списку", callback_data=f"act:list:{kind}:0")
|
||||
kb.button(text="⬅️ К списку", callback_data=f"act:list:{kind}:0")
|
||||
kb.adjust(*rows, tail, 1)
|
||||
return "\n".join(lines), kb.as_markup()
|
||||
|
||||
@@ -341,10 +343,10 @@ async def act_field(callback: types.CallbackQuery, state: FSMContext) -> None:
|
||||
kb = InlineKeyboardBuilder()
|
||||
if key in form["values"]:
|
||||
kb.button(
|
||||
text=label(f"Вернуть {render(default)}", 40),
|
||||
text=label(f"↩️ Вернуть {render(default)}", 40),
|
||||
callback_data=f"act:fd:{_ref(form)}",
|
||||
)
|
||||
kb.button(text="Отмена", callback_data=f"act:back:{_ref(form)}")
|
||||
kb.button(text="✖️ Отмена", callback_data=f"act:back:{_ref(form)}")
|
||||
kb.adjust(1)
|
||||
await show(
|
||||
callback,
|
||||
@@ -380,7 +382,7 @@ async def on_value(message: types.Message, state: FSMContext) -> None:
|
||||
await show(
|
||||
message,
|
||||
f"<b>{esc(key)}</b>: {esc(exc)}. Пришли ещё раз.",
|
||||
back_to("Отмена", f"act:back:{_ref(form)}"),
|
||||
back_to("✖️ Отмена", f"act:back:{_ref(form)}"),
|
||||
)
|
||||
return
|
||||
except KomodoError as exc:
|
||||
@@ -413,7 +415,10 @@ async def _execute( # noqa: PLR0913
|
||||
origin,
|
||||
title or name,
|
||||
starter,
|
||||
back=[(KIND_BACK[kind], f"act:open:{kind}:{target_id}"), ("Меню", "menu:main")],
|
||||
back=[
|
||||
(KIND_BACK[kind], f"act:open:{kind}:{target_id}"),
|
||||
("🏠 Меню", "menu:main"),
|
||||
],
|
||||
verbose=kind == "a",
|
||||
extra=_args_code(args),
|
||||
new=new,
|
||||
@@ -430,8 +435,8 @@ async def act_run(callback: types.CallbackQuery, state: FSMContext) -> None:
|
||||
await _execute(callback, form["kind"], form["id"], form["name"], form["values"])
|
||||
return
|
||||
kb = InlineKeyboardBuilder()
|
||||
kb.button(text="Запустить", callback_data=f"act:go:{_ref(form)}", style=SUCCESS)
|
||||
kb.button(text="Отмена", callback_data=f"act:back:{_ref(form)}")
|
||||
kb.button(text="▶️ Запустить", callback_data=f"act:go:{_ref(form)}", style=SUCCESS)
|
||||
kb.button(text="✖️ Отмена", callback_data=f"act:back:{_ref(form)}")
|
||||
kb.adjust(2)
|
||||
changes = f"\nИзменено: {_values_line(form['values'])}" if form["values"] else ""
|
||||
await show(
|
||||
@@ -461,9 +466,9 @@ async def act_pin(callback: types.CallbackQuery, state: FSMContext) -> None:
|
||||
)
|
||||
kb = InlineKeyboardBuilder()
|
||||
ref = _ref(form)
|
||||
kb.button(text="Запускать сразу", callback_data=f"act:pinmode:instant:{ref}")
|
||||
kb.button(text="Открывать форму", callback_data=f"act:pinmode:form:{ref}")
|
||||
kb.button(text="Отмена", callback_data=f"act:back:{ref}")
|
||||
kb.button(text="⚡ Запускать сразу", callback_data=f"act:pinmode:instant:{ref}")
|
||||
kb.button(text="📝 Открывать форму", callback_data=f"act:pinmode:form:{ref}")
|
||||
kb.button(text="✖️ Отмена", callback_data=f"act:back:{ref}")
|
||||
kb.adjust(2, 1)
|
||||
await show(
|
||||
callback,
|
||||
@@ -486,17 +491,17 @@ def pin_view(pin: Pin) -> tuple[str, types.InlineKeyboardMarkup]:
|
||||
values,
|
||||
]
|
||||
kb = InlineKeyboardBuilder()
|
||||
kb.button(text="Переименовать", callback_data=f"pin:ren:{pin.id}")
|
||||
kb.button(text="✏️ Переименовать", callback_data=f"pin:ren:{pin.id}")
|
||||
other = PinMode.FORM if pin.mode is PinMode.INSTANT else PinMode.INSTANT
|
||||
kb.button(
|
||||
text="Открывать форму" if other is PinMode.FORM else "Запускать сразу",
|
||||
text="📝 Открывать форму" if other is PinMode.FORM else "⚡ Запускать сразу",
|
||||
callback_data=f"pin:mode:{pin.id}",
|
||||
)
|
||||
kb.button(text="Выше", callback_data=f"pin:up:{pin.id}")
|
||||
kb.button(text="Ниже", callback_data=f"pin:down:{pin.id}")
|
||||
kb.button(text="Открепить", callback_data=f"pin:del:{pin.id}", style=DANGER)
|
||||
kb.button(text="Закреплённые", callback_data="set:pins")
|
||||
kb.button(text="Меню", callback_data="menu:main")
|
||||
kb.button(text="⬆️ Выше", callback_data=f"pin:up:{pin.id}")
|
||||
kb.button(text="⬇️ Ниже", callback_data=f"pin:down:{pin.id}")
|
||||
kb.button(text="🗑 Открепить", callback_data=f"pin:del:{pin.id}", style=DANGER)
|
||||
kb.button(text="📌 Закреплённые", callback_data="set:pins")
|
||||
kb.button(text="🏠 Меню", callback_data="menu:main")
|
||||
kb.adjust(2, 2, 1, 2)
|
||||
return "\n".join(lines), kb.as_markup()
|
||||
|
||||
@@ -527,12 +532,12 @@ async def pins_view() -> tuple[str, types.InlineKeyboardMarkup]:
|
||||
pins = await db.pins.all()
|
||||
kb = InlineKeyboardBuilder()
|
||||
for pin in pins:
|
||||
kb.button(text=label(pin.label, 40), callback_data=f"pin:open:{pin.id}")
|
||||
kb.button(text=label(pin_text(pin), 40), callback_data=f"pin:open:{pin.id}")
|
||||
if enabled():
|
||||
kb.button(text="Действия", callback_data="act:list:a:0")
|
||||
kb.button(text="Меню", callback_data="menu:main")
|
||||
kb.button(text="🎛 Действия", callback_data="act:list:a:0")
|
||||
kb.button(text="🏠 Меню", callback_data="menu:main")
|
||||
kb.adjust(*[1] * len(pins), 2)
|
||||
text = "<b>Закреплённые</b>"
|
||||
text = "📌 <b>Закреплённые</b>"
|
||||
if not pins:
|
||||
text += "\nПока пусто. Открой действие и нажми «Закрепить»."
|
||||
return text, kb.as_markup()
|
||||
@@ -630,7 +635,7 @@ async def pin_rename(callback: types.CallbackQuery, state: FSMContext) -> None:
|
||||
await show(
|
||||
callback,
|
||||
f"Пришли новое название для кнопки <b>{esc(pin.label)}</b>.",
|
||||
back_to("Отмена", f"pin:open:{pin.id}"),
|
||||
back_to("✖️ Отмена", f"pin:open:{pin.id}"),
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
@@ -685,7 +690,7 @@ async def on_command(message: types.Message, state: FSMContext) -> None:
|
||||
await show(
|
||||
message,
|
||||
"Запуск действий командами выключен.",
|
||||
back_to("Настройки", "set:main"),
|
||||
back_to("⚙️ Настройки", "set:main"),
|
||||
)
|
||||
return
|
||||
if resource is None and not (enabled() and db.settings.get("commands")):
|
||||
|
||||
@@ -134,12 +134,12 @@ def _choose_view(data: dict) -> tuple[str, types.InlineKeyboardMarkup]:
|
||||
lines.append("За чем следить?")
|
||||
|
||||
if path:
|
||||
kb.button(text="Назад", callback_data="add:up")
|
||||
kb.button(text="⬆️ Назад", callback_data="add:up")
|
||||
rows.append(1)
|
||||
kb.button(text=f"HTTP-статус {data['status']}", callback_data="add:status")
|
||||
kb.button(text="Текст в ответе", callback_data="add:text")
|
||||
kb.button(text="Тело целиком", callback_data="add:body")
|
||||
kb.button(text="Отмена", callback_data="add:cancel")
|
||||
kb.button(text=f"🔢 HTTP-статус {data['status']}", callback_data="add:status")
|
||||
kb.button(text="🔤 Текст в ответе", callback_data="add:text")
|
||||
kb.button(text="📄 Тело целиком", callback_data="add:body")
|
||||
kb.button(text="✖️ Отмена", callback_data="add:cancel")
|
||||
kb.adjust(*rows, 2, 2)
|
||||
return "\n".join(lines), kb.as_markup()
|
||||
|
||||
@@ -151,10 +151,10 @@ async def _probe(url: str) -> Probe:
|
||||
|
||||
def _unreachable_kb(*, editing: bool) -> types.InlineKeyboardMarkup:
|
||||
kb = InlineKeyboardBuilder()
|
||||
kb.button(text="Повторить", callback_data="add:retry")
|
||||
kb.button(text="🔄 Повторить", callback_data="add:retry")
|
||||
if not editing:
|
||||
kb.button(text="Добавить всё равно", callback_data="add:force")
|
||||
kb.button(text="Отмена", callback_data="add:cancel")
|
||||
kb.button(text="➕ Добавить всё равно", callback_data="add:force")
|
||||
kb.button(text="✖️ Отмена", callback_data="add:cancel")
|
||||
kb.adjust(2, 1)
|
||||
return kb.as_markup()
|
||||
|
||||
@@ -227,7 +227,7 @@ async def _add_many(message: types.Message, urls: list[str]) -> None:
|
||||
lines = [f"Добавил {len(added)} из {len(urls)}, слежу за HTTP-статусом.", *added]
|
||||
if skipped:
|
||||
lines += ["", "Пропустил:", *skipped]
|
||||
await show(message, "\n".join(lines), back_to("Сервисы", "menu:list:0"))
|
||||
await show(message, "\n".join(lines), back_to("📋 Сервисы", "menu:list:0"))
|
||||
|
||||
|
||||
async def _on_urls(message: types.Message, state: FSMContext, urls: list[str]) -> None:
|
||||
@@ -265,7 +265,7 @@ async def on_new_url(message: types.Message, state: FSMContext) -> None:
|
||||
await show(message, "Сервис уже удалён.", back_to_menu())
|
||||
return
|
||||
urls = extract_urls(message.text)
|
||||
back = back_to("Отмена", f"svc:open:{service.id}")
|
||||
back = back_to("✖️ Отмена", f"svc:open:{service.id}")
|
||||
if urls is None or len(urls) != 1:
|
||||
await show(message, "Нужна одна ссылка.", back)
|
||||
return
|
||||
@@ -292,7 +292,7 @@ async def svc_url(callback: types.CallbackQuery, state: FSMContext) -> None:
|
||||
callback,
|
||||
f"Пришли новый адрес для <b>{esc(service.name)}</b>.\n"
|
||||
f"Сейчас: <code>{esc(service.url)}</code>",
|
||||
back_to("Отмена", f"svc:open:{service.id}"),
|
||||
back_to("✖️ Отмена", f"svc:open:{service.id}"),
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
@@ -372,7 +372,7 @@ async def on_pick_text(callback: types.CallbackQuery, state: FSMContext) -> None
|
||||
await show(
|
||||
callback,
|
||||
"Пришли текст, который должен быть в ответе.",
|
||||
back_to("Отмена", "add:cancel"),
|
||||
back_to("✖️ Отмена", "add:cancel"),
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
@@ -385,7 +385,7 @@ async def on_needle(message: types.Message, state: FSMContext) -> None:
|
||||
await show(
|
||||
message,
|
||||
"Сейчас такого текста в ответе нет. Пришли другой.",
|
||||
back_to("Отмена", "add:cancel"),
|
||||
back_to("✖️ Отмена", "add:cancel"),
|
||||
)
|
||||
return
|
||||
await _save(message, state, match_type=MatchType.CONTAINS, needle=needle)
|
||||
|
||||
@@ -39,6 +39,7 @@ OPS: dict[str, tuple[str, str, str | None]] = {
|
||||
"sto": ("Stop", "StopStack", DANGER),
|
||||
"sta": ("Start", "StartStack", None),
|
||||
}
|
||||
OP_ICONS = {"dep": "🚀", "res": "🔁", "sto": "⏹", "sta": "▶️"}
|
||||
|
||||
|
||||
async def _guard(callback: types.CallbackQuery) -> bool:
|
||||
@@ -71,7 +72,7 @@ async def fleet_view() -> tuple[str, types.InlineKeyboardMarkup]:
|
||||
komodo.servers(), komodo.stacks(), komodo.open_alerts()
|
||||
)
|
||||
servers.sort(key=lambda s: s.name)
|
||||
lines = [f"<b>Флот</b>: серверов {len(servers)}, стеков {len(stacks)}", ""]
|
||||
lines = [f"🛰 <b>Флот</b>: серверов {len(servers)}, стеков {len(stacks)}", ""]
|
||||
kb = InlineKeyboardBuilder()
|
||||
for server in servers:
|
||||
mine = [s for s in stacks if s.server_id == server.id]
|
||||
@@ -95,14 +96,14 @@ async def fleet_view() -> tuple[str, types.InlineKeyboardMarkup]:
|
||||
broken = [s for s in stacks if not s.healthy]
|
||||
extra = 0
|
||||
if broken:
|
||||
kb.button(text=f"Не работают: {len(broken)}", callback_data="fleet:bad")
|
||||
kb.button(text=f"⚠️ Не работают: {len(broken)}", callback_data="fleet:bad")
|
||||
extra += 1
|
||||
if alerts:
|
||||
kb.button(text=f"Алерты: {len(alerts)}", callback_data="fleet:alerts")
|
||||
kb.button(text=f"🔔 Алерты: {len(alerts)}", callback_data="fleet:alerts")
|
||||
extra += 1
|
||||
kb.button(text="Журнал", callback_data="fleet:journal")
|
||||
kb.button(text="Обновить", callback_data="fleet:main")
|
||||
kb.button(text="Меню", callback_data="menu:main")
|
||||
kb.button(text="🕘 Журнал", callback_data="fleet:journal")
|
||||
kb.button(text="🔄 Обновить", callback_data="fleet:main")
|
||||
kb.button(text="🏠 Меню", callback_data="menu:main")
|
||||
kb.adjust(*rows, *([extra] if extra else []), 3)
|
||||
return "\n".join(lines), kb.as_markup()
|
||||
|
||||
@@ -122,7 +123,7 @@ async def server_view(
|
||||
servers, stacks = await asyncio.gather(komodo.servers(), komodo.stacks())
|
||||
server = next((s for s in servers if s.id == server_id), None)
|
||||
if server is None:
|
||||
return "Сервер не найден.", back_to("К флоту", "fleet:main")
|
||||
return "Сервер не найден.", back_to("⬅️ К флоту", "fleet:main")
|
||||
|
||||
lines = [f"{server.mark} <b>{esc(server.name)}</b>"]
|
||||
if server.state == "NotOk":
|
||||
@@ -161,8 +162,8 @@ async def server_view(
|
||||
kb = InlineKeyboardBuilder()
|
||||
rows = _stack_buttons(kb, mine[page * STACKS_PAGE : (page + 1) * STACKS_PAGE])
|
||||
nav = pager(kb, f"fleet:srv:{server_id}", page, total_pages)
|
||||
kb.button(text="Обновить", callback_data=f"fleet:srv:{server_id}:{page}")
|
||||
kb.button(text="К флоту", callback_data="fleet:main")
|
||||
kb.button(text="🔄 Обновить", callback_data=f"fleet:srv:{server_id}:{page}")
|
||||
kb.button(text="⬅️ К флоту", callback_data="fleet:main")
|
||||
kb.adjust(*rows, *([nav] if nav else []), 2)
|
||||
return "\n".join(lines), kb.as_markup()
|
||||
|
||||
@@ -170,7 +171,9 @@ async def server_view(
|
||||
async def stack_view(stack_id: str) -> tuple[str, types.InlineKeyboardMarkup]:
|
||||
stack = await komodo.stack(stack_id)
|
||||
if stack is None:
|
||||
return "Стек не найден, возможно его удалили.", back_to("К флоту", "fleet:main")
|
||||
return "Стек не найден, возможно его удалили.", back_to(
|
||||
"⬅️ К флоту", "fleet:main"
|
||||
)
|
||||
|
||||
lines = [
|
||||
f"{stack.mark} <b>{esc(stack.name)}</b>",
|
||||
@@ -202,12 +205,16 @@ async def stack_view(stack_id: str) -> tuple[str, types.InlineKeyboardMarkup]:
|
||||
shown = ("dep", "res", "sto") if stack.state != "down" else ("dep", "sta")
|
||||
for key in shown:
|
||||
text, _, style = OPS[key]
|
||||
kb.button(text=text, callback_data=f"fleet:ask:{key}:{stack.id}", style=style)
|
||||
kb.button(text="Логи", callback_data=f"fleet:logs:{stack.id}")
|
||||
kb.button(text="История", callback_data=f"fleet:hist:{stack.id}")
|
||||
kb.button(text="Обновить", callback_data=f"fleet:stack:{stack.id}")
|
||||
kb.button(
|
||||
text=f"{OP_ICONS[key]} {text}",
|
||||
callback_data=f"fleet:ask:{key}:{stack.id}",
|
||||
style=style,
|
||||
)
|
||||
kb.button(text="📜 Логи", callback_data=f"fleet:logs:{stack.id}")
|
||||
kb.button(text="🕘 История", callback_data=f"fleet:hist:{stack.id}")
|
||||
kb.button(text="🔄 Обновить", callback_data=f"fleet:stack:{stack.id}")
|
||||
kb.button(
|
||||
text="К серверу" if stack.server_id else "К флоту",
|
||||
text="⬅️ К серверу" if stack.server_id else "⬅️ К флоту",
|
||||
callback_data=f"fleet:srv:{stack.server_id}:0"
|
||||
if stack.server_id
|
||||
else "fleet:main",
|
||||
@@ -296,14 +303,14 @@ async def _bad_view() -> tuple[str, types.InlineKeyboardMarkup]:
|
||||
(s for s in await komodo.stacks() if not s.healthy),
|
||||
key=lambda s: (s.server_name, s.name),
|
||||
)
|
||||
lines = ["<b>Не работают</b>", ""]
|
||||
lines = ["⚠️ <b>Не работают</b>", ""]
|
||||
lines.extend(
|
||||
f"{s.mark} {esc(s.name)} · {esc(s.server_name)} · {esc(s.state)}"
|
||||
for s in broken
|
||||
)
|
||||
kb = InlineKeyboardBuilder()
|
||||
rows = _stack_buttons(kb, broken[:40])
|
||||
kb.button(text="К флоту", callback_data="fleet:main")
|
||||
kb.button(text="⬅️ К флоту", callback_data="fleet:main")
|
||||
kb.adjust(*rows, 1)
|
||||
return "\n".join(lines), kb.as_markup()
|
||||
|
||||
@@ -318,13 +325,13 @@ async def fleet_bad(callback: types.CallbackQuery) -> None:
|
||||
async def _alerts_view() -> tuple[str, types.InlineKeyboardMarkup]:
|
||||
tz = db.settings.get("tz")
|
||||
alerts = await komodo.open_alerts()
|
||||
lines = ["<b>Открытые алерты</b>"]
|
||||
lines = ["🔔 <b>Открытые алерты</b>"]
|
||||
if not alerts:
|
||||
lines.append("Сейчас ни одного.")
|
||||
for alert in alerts[:10]:
|
||||
since = stamp(from_ms(alert.get("ts") or 0), tz)
|
||||
lines += ["", format_alert(alert), f"с {since}"]
|
||||
return "\n".join(lines), back_to("К флоту", "fleet:main")
|
||||
return "\n".join(lines), back_to("⬅️ К флоту", "fleet:main")
|
||||
|
||||
|
||||
@router.callback_query(F.data == "fleet:alerts")
|
||||
@@ -350,9 +357,11 @@ async def fleet_ask(callback: types.CallbackQuery) -> None:
|
||||
return
|
||||
kb = InlineKeyboardBuilder()
|
||||
kb.button(
|
||||
text=text, callback_data=f"fleet:do:{key}:{stack_id}", style=style or PRIMARY
|
||||
text=f"{OP_ICONS[key]} {text}",
|
||||
callback_data=f"fleet:do:{key}:{stack_id}",
|
||||
style=style or PRIMARY,
|
||||
)
|
||||
kb.button(text="Отмена", callback_data=f"fleet:stack:{stack_id}")
|
||||
kb.button(text="✖️ Отмена", callback_data=f"fleet:stack:{stack_id}")
|
||||
kb.adjust(2)
|
||||
await show(
|
||||
callback,
|
||||
@@ -384,8 +393,8 @@ async def fleet_do(callback: types.CallbackQuery) -> None:
|
||||
f"{text} {name}",
|
||||
starter,
|
||||
back=[
|
||||
("К стеку", f"fleet:stack:{stack_id}"),
|
||||
("Логи", f"fleet:logs:{stack_id}"),
|
||||
("⬅️ К стеку", f"fleet:stack:{stack_id}"),
|
||||
("📜 Логи", f"fleet:logs:{stack_id}"),
|
||||
],
|
||||
verbose=False,
|
||||
)
|
||||
@@ -402,7 +411,7 @@ async def _log_view(
|
||||
) -> tuple[str, types.InlineKeyboardMarkup]:
|
||||
stack = await komodo.stack(stack_id)
|
||||
if stack is None:
|
||||
return "Стек не найден.", back_to("К флоту", "fleet:main")
|
||||
return "Стек не найден.", back_to("⬅️ К флоту", "fleet:main")
|
||||
services = _picked(stack, index)
|
||||
log = await komodo.stack_log(stack_id, services, LOG_TAIL)
|
||||
if services or len(stack.services) == 1:
|
||||
@@ -410,15 +419,15 @@ async def _log_view(
|
||||
scope = f" · {esc(services[0])}" if services else ""
|
||||
body = pre(log, LOG_CHARS) if log else "<i>лог пуст</i>"
|
||||
kb = InlineKeyboardBuilder()
|
||||
kb.button(text="Обновить", callback_data=f"fleet:log:{stack_id}:{index}")
|
||||
kb.button(text="Файлом", callback_data=f"fleet:logf:{stack_id}:{index}")
|
||||
kb.button(text="🔄 Обновить", callback_data=f"fleet:log:{stack_id}:{index}")
|
||||
kb.button(text="📎 Файлом", callback_data=f"fleet:logf:{stack_id}:{index}")
|
||||
rows = [2]
|
||||
if len(stack.services) > 1:
|
||||
kb.button(text="Другой сервис", callback_data=f"fleet:logs:{stack_id}")
|
||||
kb.button(text="К стеку", callback_data=f"fleet:stack:{stack_id}")
|
||||
kb.button(text="🔀 Другой сервис", callback_data=f"fleet:logs:{stack_id}")
|
||||
kb.button(text="⬅️ К стеку", callback_data=f"fleet:stack:{stack_id}")
|
||||
rows.append(2)
|
||||
else:
|
||||
kb.button(text="К стеку", callback_data=f"fleet:stack:{stack_id}")
|
||||
kb.button(text="⬅️ К стеку", callback_data=f"fleet:stack:{stack_id}")
|
||||
rows.append(1)
|
||||
kb.adjust(*rows)
|
||||
return f"<b>{esc(stack.name)}</b>{scope}\n{body}", kb.as_markup()
|
||||
@@ -427,7 +436,7 @@ async def _log_view(
|
||||
async def _log_picker(stack_id: str) -> tuple[str, types.InlineKeyboardMarkup]:
|
||||
stack = await komodo.stack(stack_id)
|
||||
if stack is None:
|
||||
return "Стек не найден.", back_to("К флоту", "fleet:main")
|
||||
return "Стек не найден.", back_to("⬅️ К флоту", "fleet:main")
|
||||
if len(stack.services) <= 1:
|
||||
return await _log_view(stack_id, ALL_SERVICES)
|
||||
kb = InlineKeyboardBuilder()
|
||||
@@ -435,8 +444,10 @@ async def _log_picker(stack_id: str) -> tuple[str, types.InlineKeyboardMarkup]:
|
||||
kb.button(
|
||||
text=label(service, 28), callback_data=f"fleet:log:{stack_id}:{index}"
|
||||
)
|
||||
kb.button(text="Все сервисы", callback_data=f"fleet:log:{stack_id}:{ALL_SERVICES}")
|
||||
kb.button(text="К стеку", callback_data=f"fleet:stack:{stack_id}")
|
||||
kb.button(
|
||||
text="🗂 Все сервисы", callback_data=f"fleet:log:{stack_id}:{ALL_SERVICES}"
|
||||
)
|
||||
kb.button(text="⬅️ К стеку", callback_data=f"fleet:stack:{stack_id}")
|
||||
count = len(stack.services)
|
||||
kb.adjust(*[2] * (count // 2), *[1] * (count % 2), 2)
|
||||
return f"<b>{esc(stack.name)}</b>: чьи логи?", kb.as_markup()
|
||||
@@ -559,7 +570,7 @@ async def _history_view(stack_id: str) -> tuple[str, types.InlineKeyboardMarkup]
|
||||
title = esc(stack.name) if stack else "Стек"
|
||||
kb = InlineKeyboardBuilder()
|
||||
count = _failed_buttons(kb, failed)
|
||||
kb.button(text="К стеку", callback_data=f"fleet:stack:{stack_id}")
|
||||
kb.button(text="⬅️ К стеку", callback_data=f"fleet:stack:{stack_id}")
|
||||
kb.adjust(*[1] * count, 1)
|
||||
body = "\n".join(lines) if lines else "Операций ещё не было."
|
||||
return f"<b>{title}</b>: операции\n\n{body}", kb.as_markup()
|
||||
@@ -578,11 +589,11 @@ async def _journal_view() -> tuple[str, types.InlineKeyboardMarkup]:
|
||||
lines, failed = await _update_lines(updates, JOURNAL, with_target=True)
|
||||
kb = InlineKeyboardBuilder()
|
||||
count = _failed_buttons(kb, failed)
|
||||
kb.button(text="Обновить", callback_data="fleet:journal")
|
||||
kb.button(text="К флоту", callback_data="fleet:main")
|
||||
kb.button(text="🔄 Обновить", callback_data="fleet:journal")
|
||||
kb.button(text="⬅️ К флоту", callback_data="fleet:main")
|
||||
kb.adjust(*[1] * count, 2)
|
||||
body = "\n".join(lines) if lines else "Операций ещё не было."
|
||||
return f"<b>Журнал</b>\n\n{body}", kb.as_markup()
|
||||
return f"🕘 <b>Журнал</b>\n\n{body}", kb.as_markup()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "fleet:journal")
|
||||
|
||||
@@ -78,13 +78,13 @@ async def _services_block() -> list[str]:
|
||||
works = plural(working, "работает", "работают", "работают")
|
||||
if not down and not paused:
|
||||
every = "все " if working > 1 else ""
|
||||
return [f"<b>Сервисы</b>: {every}{working} {works}"]
|
||||
return [f"📋 <b>Сервисы</b>: {every}{working} {works}"]
|
||||
parts = [f"{working} {works}"]
|
||||
if down:
|
||||
parts.append(f"{len(down)} {plural(len(down), 'лежит', 'лежат', 'лежат')}")
|
||||
if paused:
|
||||
parts.append(f"{paused} на паузе")
|
||||
lines = [f"<b>Сервисы</b>: {', '.join(parts)}"]
|
||||
lines = [f"📋 <b>Сервисы</b>: {', '.join(parts)}"]
|
||||
lines.extend([await _down_line(s) for s in down[:SHOWN_PROBLEMS]])
|
||||
return lines
|
||||
|
||||
@@ -95,11 +95,11 @@ async def _fleet_block() -> list[str]:
|
||||
komodo.servers(), komodo.stacks(), komodo.open_alerts()
|
||||
)
|
||||
except KomodoError as exc:
|
||||
return [f"<b>Флот</b>: {esc(exc)}"]
|
||||
return [f"🛰 <b>Флот</b>: {esc(exc)}"]
|
||||
reachable = {s.id for s in servers if s.state == "Ok"}
|
||||
good = sum(s.healthy for s in stacks)
|
||||
summary = "все работают" if good == len(stacks) else f"работают {good}"
|
||||
lines = [f"<b>Флот</b>: серверов {len(servers)}, стеков {len(stacks)}, {summary}"]
|
||||
lines = [f"🛰 <b>Флот</b>: серверов {len(servers)}, стеков {len(stacks)}, {summary}"]
|
||||
for server in servers:
|
||||
if server.state == "NotOk":
|
||||
lines.append(f"🔴 {esc(server.name)} не отвечает")
|
||||
@@ -123,7 +123,7 @@ async def dashboard() -> tuple[str, types.InlineKeyboardMarkup]:
|
||||
try:
|
||||
fleet = await asyncio.wait_for(_fleet_block(), FLEET_TIMEOUT)
|
||||
except TimeoutError:
|
||||
fleet = ["<b>Флот</b>: Komodo не отвечает"]
|
||||
fleet = ["🛰 <b>Флот</b>: Komodo не отвечает"]
|
||||
lines += ["", *fleet]
|
||||
pins = await db.pins.all() if actions else []
|
||||
return "\n".join(lines), main_menu(pins, actions=bool(actions))
|
||||
|
||||
@@ -32,7 +32,7 @@ async def run_status(callback: types.CallbackQuery) -> None:
|
||||
await callback.answer()
|
||||
if not update.done:
|
||||
kb = InlineKeyboardBuilder()
|
||||
kb.button(text="Проверить", callback_data=f"run:st:{update_id}")
|
||||
kb.button(text="🔍 Проверить", callback_data=f"run:st:{update_id}")
|
||||
await show(
|
||||
callback,
|
||||
f"⏳ <b>{esc(title)}</b> идёт уже {clock(update.seconds)}",
|
||||
|
||||
@@ -54,7 +54,7 @@ async def list_view(page: int = 0) -> tuple[str, types.InlineKeyboardMarkup]:
|
||||
parts = [f"🟢 {up}", f"🔴 {len(watched) - up}"]
|
||||
if paused:
|
||||
parts.append(f"⏸ {paused}")
|
||||
return f"<b>Сервисы</b> {' '.join(parts)}", services_list(services, page)
|
||||
return f"📋 <b>Сервисы</b> {' '.join(parts)}", services_list(services, page)
|
||||
|
||||
|
||||
@router.message(Command("list"))
|
||||
@@ -311,7 +311,7 @@ async def svc_incidents(callback: types.CallbackQuery) -> None:
|
||||
total = downtime(incidents, parse(service.created_at), now())
|
||||
lines += ["", f"Всего {len(incidents)}, даунтайм {human_duration(total)}"]
|
||||
await show(
|
||||
callback, "\n".join(lines), back_to("К сервису", f"svc:open:{service.id}")
|
||||
callback, "\n".join(lines), back_to("⬅️ К сервису", f"svc:open:{service.id}")
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
@@ -326,7 +326,7 @@ async def svc_rename(callback: types.CallbackQuery, state: FSMContext) -> None:
|
||||
await show(
|
||||
callback,
|
||||
f"Пришли новое имя для <b>{esc(service.name)}</b>.",
|
||||
back_to("Отмена", f"svc:open:{service.id}"),
|
||||
back_to("✖️ Отмена", f"svc:open:{service.id}"),
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
@@ -378,7 +378,7 @@ async def stats_view() -> tuple[str, types.InlineKeyboardMarkup]:
|
||||
rows.append((uptime(service, incidents, month) or 0.0, service, incidents))
|
||||
rows.sort(key=lambda row: row[0])
|
||||
|
||||
lines = ["<b>Статистика</b>", ""]
|
||||
lines = ["📊 <b>Статистика</b>", ""]
|
||||
for _, service, incidents in rows:
|
||||
speed = (
|
||||
f" · {service.last_latency_ms} мс"
|
||||
|
||||
@@ -21,9 +21,9 @@ router.message.filter(Admin())
|
||||
router.callback_query.filter(Admin())
|
||||
|
||||
TOGGLES = {
|
||||
"actions": ("Действия Komodo", "включены", "выключены"),
|
||||
"commands": ("Действия как команды", "включены", "выключены"),
|
||||
"confirm": ("Подтверждение запуска", "включено", "выключено"),
|
||||
"actions": ("🎛 Действия Komodo", "включены", "выключены"),
|
||||
"commands": ("⌨️ Действия как команды", "включены", "выключены"),
|
||||
"confirm": ("🛡 Подтверждение запуска", "включено", "выключено"),
|
||||
}
|
||||
DIGEST_HOURS = (7, 8, 9, 10, 12, 18, 21)
|
||||
|
||||
@@ -43,7 +43,7 @@ async def _komodo_line() -> str:
|
||||
|
||||
async def settings_view() -> tuple[str, types.InlineKeyboardMarkup]:
|
||||
tz = db.settings.get("tz")
|
||||
lines = ["<b>Настройки</b>", await _komodo_line()]
|
||||
lines = ["⚙️ <b>Настройки</b>", await _komodo_line()]
|
||||
kb = InlineKeyboardBuilder()
|
||||
if env.komodo.enabled:
|
||||
for key, (title, on, off) in TOGGLES.items():
|
||||
@@ -59,14 +59,14 @@ async def settings_view() -> tuple[str, types.InlineKeyboardMarkup]:
|
||||
)
|
||||
if env.web.enabled:
|
||||
muted = len(db.settings.get("muted"))
|
||||
kb.button(text=f"Алерты Komodo, скрыто: {muted}", callback_data="set:alerts")
|
||||
kb.button(text=f"Часовой пояс: {tz}", callback_data="set:tz")
|
||||
kb.button(text=f"🔔 Алерты Komodo, скрыто: {muted}", callback_data="set:alerts")
|
||||
kb.button(text=f"🕒 Часовой пояс: {tz}", callback_data="set:tz")
|
||||
hour = db.settings.get("digest")
|
||||
digest = "выключена" if hour is None else f"в {hour:02d}:00"
|
||||
kb.button(text=f"Сводка за сутки: {digest}", callback_data="set:digest")
|
||||
kb.button(text=f"🗞 Сводка за сутки: {digest}", callback_data="set:digest")
|
||||
if env.komodo.enabled and db.settings.get("actions"):
|
||||
kb.button(text="Закреплённые", callback_data="set:pins")
|
||||
kb.button(text="Меню", callback_data="menu:main")
|
||||
kb.button(text="📌 Закреплённые", callback_data="set:pins")
|
||||
kb.button(text="🏠 Меню", callback_data="menu:main")
|
||||
kb.adjust(1)
|
||||
return "\n".join(lines), kb.as_markup()
|
||||
|
||||
@@ -106,9 +106,9 @@ def _alerts_view() -> tuple[str, types.InlineKeyboardMarkup]:
|
||||
for kind in kinds:
|
||||
mark = "🔕" if kind in muted else "🔔"
|
||||
kb.button(text=f"{mark} {kind}", callback_data=f"set:mute:{kind}")
|
||||
kb.button(text="К настройкам", callback_data="set:main")
|
||||
kb.button(text="⬅️ К настройкам", callback_data="set:main")
|
||||
kb.adjust(1)
|
||||
return "<b>Алерты Komodo</b>", kb.as_markup()
|
||||
return "🔔 <b>Алерты Komodo</b>", kb.as_markup()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "set:alerts")
|
||||
@@ -158,7 +158,7 @@ async def alert_mute(callback: types.CallbackQuery) -> None:
|
||||
kind = tail_arg(callback.data)
|
||||
if kind not in db.settings.get("muted"):
|
||||
await _flip_mute(kind)
|
||||
await _swap_alert_button(callback, f"{kind} скрыт, вернуть", f"al:show:{kind}")
|
||||
await _swap_alert_button(callback, f"🔔 {kind} скрыт, вернуть", f"al:show:{kind}")
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("al:show:"))
|
||||
@@ -166,7 +166,7 @@ async def alert_unmute(callback: types.CallbackQuery) -> None:
|
||||
kind = tail_arg(callback.data)
|
||||
if kind in db.settings.get("muted"):
|
||||
await _flip_mute(kind)
|
||||
await _swap_alert_button(callback, "Не присылать такие", f"al:mute:{kind}")
|
||||
await _swap_alert_button(callback, "🔕 Не присылать такие", f"al:mute:{kind}")
|
||||
|
||||
|
||||
@router.callback_query(F.data == "set:tz")
|
||||
@@ -178,7 +178,7 @@ async def settings_tz(callback: types.CallbackQuery, state: FSMContext) -> None:
|
||||
callback,
|
||||
"Пришли часовой пояс, например <code>Europe/Warsaw</code>.\n"
|
||||
f"Сейчас: {esc(tz)}, на часах {local}",
|
||||
back_to("Отмена", "set:main"),
|
||||
back_to("✖️ Отмена", "set:main"),
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
@@ -193,7 +193,7 @@ async def on_tz(message: types.Message, state: FSMContext) -> None:
|
||||
message,
|
||||
"Такого пояса нет. Нужно имя из базы tz, например "
|
||||
"<code>Europe/Warsaw</code> или <code>UTC</code>.",
|
||||
back_to("Отмена", "set:main"),
|
||||
back_to("✖️ Отмена", "set:main"),
|
||||
)
|
||||
return
|
||||
await state.clear()
|
||||
@@ -207,12 +207,12 @@ async def settings_digest(callback: types.CallbackQuery) -> None:
|
||||
kb = InlineKeyboardBuilder()
|
||||
for hour in DIGEST_HOURS:
|
||||
kb.button(text=f"{hour:02d}:00", callback_data=f"set:dg:{hour}")
|
||||
kb.button(text="Выключить", callback_data="set:dg:off")
|
||||
kb.button(text="К настройкам", callback_data="set:main")
|
||||
kb.button(text="🚫 Выключить", callback_data="set:dg:off")
|
||||
kb.button(text="⬅️ К настройкам", callback_data="set:main")
|
||||
kb.adjust(4, 3, 2)
|
||||
await show(
|
||||
callback,
|
||||
"<b>Сводка за сутки</b>\n"
|
||||
"🗞 <b>Сводка за сутки</b>\n"
|
||||
"Раз в день: аптайм и падения сервисов, операции и алерты флота.\n"
|
||||
f"Время по поясу {esc(db.settings.get('tz'))}.",
|
||||
kb.as_markup(),
|
||||
|
||||
@@ -1,38 +1,44 @@
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from healthbot.ui import DANGER, PRIMARY, label, pager, pages
|
||||
from utils.db.models import Pin, Service
|
||||
from healthbot.ui import DANGER, PRIMARY, label, pager, pages, with_icon
|
||||
from utils.db.models import Pin, PinMode, Service
|
||||
from utils.env import env
|
||||
|
||||
SERVICES_PAGE = 12
|
||||
|
||||
|
||||
def pin_text(pin: Pin) -> str:
|
||||
return with_icon(pin.label, "⚡" if pin.mode is PinMode.INSTANT else "📝")
|
||||
|
||||
|
||||
def main_menu(pins: list[Pin], *, actions: bool) -> InlineKeyboardMarkup:
|
||||
kb = InlineKeyboardBuilder()
|
||||
rows: list[int] = []
|
||||
for pin in pins:
|
||||
kb.button(
|
||||
text=label(pin.label, 30), callback_data=f"pin:run:{pin.id}", style=PRIMARY
|
||||
text=label(pin_text(pin), 30),
|
||||
callback_data=f"pin:run:{pin.id}",
|
||||
style=PRIMARY,
|
||||
)
|
||||
rows += [2] * (len(pins) // 2) + [1] * (len(pins) % 2)
|
||||
|
||||
kb.button(text="Сервисы", callback_data="menu:list:0")
|
||||
kb.button(text="📋 Сервисы", callback_data="menu:list:0")
|
||||
if env.komodo.enabled:
|
||||
kb.button(text="Флот", callback_data="fleet:main")
|
||||
kb.button(text="🛰 Флот", callback_data="fleet:main")
|
||||
if actions:
|
||||
kb.button(text="Действия", callback_data="act:list:a:0")
|
||||
kb.button(text="Статистика", callback_data="menu:stats")
|
||||
kb.button(text="🎛 Действия", callback_data="act:list:a:0")
|
||||
kb.button(text="📊 Статистика", callback_data="menu:stats")
|
||||
rows += [2, 2]
|
||||
else:
|
||||
kb.button(text="Статистика", callback_data="menu:stats")
|
||||
kb.button(text="📊 Статистика", callback_data="menu:stats")
|
||||
rows += [2, 1]
|
||||
else:
|
||||
kb.button(text="Статистика", callback_data="menu:stats")
|
||||
kb.button(text="📊 Статистика", callback_data="menu:stats")
|
||||
rows += [2]
|
||||
kb.button(text="Добавить сервис", callback_data="menu:add")
|
||||
kb.button(text="Настройки", callback_data="set:main")
|
||||
kb.button(text="Обновить", callback_data="menu:main")
|
||||
kb.button(text="➕ Добавить сервис", callback_data="menu:add")
|
||||
kb.button(text="⚙️ Настройки", callback_data="set:main")
|
||||
kb.button(text="🔄 Обновить", callback_data="menu:main")
|
||||
kb.adjust(*rows, 2, 1)
|
||||
return kb.as_markup()
|
||||
|
||||
@@ -60,33 +66,35 @@ def services_list(services: list[Service], page: int) -> InlineKeyboardMarkup:
|
||||
callback_data=f"svc:open:{svc.id}",
|
||||
)
|
||||
nav = pager(kb, "menu:list", page, total)
|
||||
kb.button(text="Добавить", callback_data="menu:add")
|
||||
kb.button(text="Меню", callback_data="menu:main")
|
||||
kb.button(text="➕ Добавить", callback_data="menu:add")
|
||||
kb.button(text="🏠 Меню", callback_data="menu:main")
|
||||
kb.adjust(*[1] * len(chunk), *([nav] if nav else []), 2)
|
||||
return kb.as_markup()
|
||||
|
||||
|
||||
def service_card(service: Service) -> InlineKeyboardMarkup:
|
||||
kb = InlineKeyboardBuilder()
|
||||
kb.button(text="Проверить", callback_data=f"svc:check:{service.id}", style=PRIMARY)
|
||||
kb.button(text="Инциденты", callback_data=f"svc:inc:{service.id}")
|
||||
kb.button(
|
||||
text="🔍 Проверить", callback_data=f"svc:check:{service.id}", style=PRIMARY
|
||||
)
|
||||
kb.button(text="🚨 Инциденты", callback_data=f"svc:inc:{service.id}")
|
||||
if service.is_paused:
|
||||
kb.button(text="Возобновить", callback_data=f"svc:resume:{service.id}")
|
||||
kb.button(text="▶️ Возобновить", callback_data=f"svc:resume:{service.id}")
|
||||
else:
|
||||
kb.button(text="Пауза", callback_data=f"svc:pause:{service.id}")
|
||||
kb.button(text="Правило", callback_data=f"svc:rule:{service.id}")
|
||||
kb.button(text="Имя", callback_data=f"svc:rename:{service.id}")
|
||||
kb.button(text="Адрес", callback_data=f"svc:url:{service.id}")
|
||||
kb.button(text="Удалить", callback_data=f"svc:del:{service.id}")
|
||||
kb.button(text="К списку", callback_data="menu:list:0")
|
||||
kb.button(text="⏸ Пауза", callback_data=f"svc:pause:{service.id}")
|
||||
kb.button(text="🎯 Правило", callback_data=f"svc:rule:{service.id}")
|
||||
kb.button(text="✏️ Имя", callback_data=f"svc:rename:{service.id}")
|
||||
kb.button(text="🔗 Адрес", callback_data=f"svc:url:{service.id}")
|
||||
kb.button(text="🗑 Удалить", callback_data=f"svc:del:{service.id}")
|
||||
kb.button(text="⬅️ К списку", callback_data="menu:list:0")
|
||||
kb.adjust(2)
|
||||
return kb.as_markup()
|
||||
|
||||
|
||||
def confirm_delete(service_id: int) -> InlineKeyboardMarkup:
|
||||
kb = InlineKeyboardBuilder()
|
||||
kb.button(text="Удалить", callback_data=f"svc:delyes:{service_id}", style=DANGER)
|
||||
kb.button(text="Отмена", callback_data=f"svc:open:{service_id}")
|
||||
kb.button(text="🗑 Удалить", callback_data=f"svc:delyes:{service_id}", style=DANGER)
|
||||
kb.button(text="✖️ Отмена", callback_data=f"svc:open:{service_id}")
|
||||
kb.adjust(2)
|
||||
return kb.as_markup()
|
||||
|
||||
@@ -94,9 +102,9 @@ def confirm_delete(service_id: int) -> InlineKeyboardMarkup:
|
||||
def after_check(service_id: int, *, offer_baseline: bool) -> InlineKeyboardMarkup:
|
||||
kb = InlineKeyboardBuilder()
|
||||
if offer_baseline:
|
||||
kb.button(text="Принять как эталон", callback_data=f"svc:base:{service_id}")
|
||||
kb.button(text="Ещё раз", callback_data=f"svc:check:{service_id}")
|
||||
kb.button(text="К сервису", callback_data=f"svc:open:{service_id}")
|
||||
kb.button(text="✅ Принять как эталон", callback_data=f"svc:base:{service_id}")
|
||||
kb.button(text="🔄 Ещё раз", callback_data=f"svc:check:{service_id}")
|
||||
kb.button(text="⬅️ К сервису", callback_data=f"svc:open:{service_id}")
|
||||
kb.adjust(*([1] if offer_baseline else []), 2)
|
||||
return kb.as_markup()
|
||||
|
||||
@@ -108,4 +116,4 @@ def back_to(text: str, data: str) -> InlineKeyboardMarkup:
|
||||
|
||||
|
||||
def back_to_menu() -> InlineKeyboardMarkup:
|
||||
return back_to("Меню", "menu:main")
|
||||
return back_to("🏠 Меню", "menu:main")
|
||||
@@ -30,17 +30,17 @@ def _head(service: Service, icon: str, tail: str) -> str:
|
||||
|
||||
def _down_kb(service: Service, incident_id: int | None) -> InlineKeyboardMarkup:
|
||||
kb = InlineKeyboardBuilder()
|
||||
kb.button(text="Проверить", callback_data=f"go:check:{service.id}")
|
||||
kb.button(text="Открыть", callback_data=f"go:svc:{service.id}")
|
||||
kb.button(text="🔍 Проверить", callback_data=f"go:check:{service.id}")
|
||||
kb.button(text="📂 Открыть", callback_data=f"go:svc:{service.id}")
|
||||
if incident_id is not None:
|
||||
kb.button(text="Не напоминать", callback_data=f"svc:mute:{incident_id}")
|
||||
kb.button(text="🔕 Не напоминать", callback_data=f"svc:mute:{incident_id}")
|
||||
kb.adjust(2, 1)
|
||||
return kb.as_markup()
|
||||
|
||||
|
||||
def _open_kb(service: Service) -> InlineKeyboardMarkup:
|
||||
kb = InlineKeyboardBuilder()
|
||||
kb.button(text="Открыть", callback_data=f"go:svc:{service.id}")
|
||||
kb.button(text="📂 Открыть", callback_data=f"go:svc:{service.id}")
|
||||
return kb.as_markup()
|
||||
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ def result_kb(update: Update | None, back: Back) -> InlineKeyboardMarkup:
|
||||
kb.button(text=text, callback_data=data)
|
||||
rows = [len(back)] if back else []
|
||||
if update is not None and len(output_of(update, only_failed=False)) > OUTPUT_LIMIT:
|
||||
kb.button(text="Весь вывод файлом", callback_data=f"run:file:{update.id}")
|
||||
kb.button(text="📎 Весь вывод файлом", callback_data=f"run:file:{update.id}")
|
||||
rows.append(1)
|
||||
kb.adjust(*rows or [1])
|
||||
return kb.as_markup()
|
||||
@@ -119,7 +119,7 @@ async def _track( # noqa: PLR0913
|
||||
return
|
||||
|
||||
check = InlineKeyboardBuilder()
|
||||
check.button(text="Проверить", callback_data=f"run:st:{started.id}")
|
||||
check.button(text="🔍 Проверить", callback_data=f"run:st:{started.id}")
|
||||
try:
|
||||
update = await komodo.wait(started.id, RUN_TIMEOUT)
|
||||
except KomodoError as exc:
|
||||
|
||||
+6
-2
@@ -88,6 +88,10 @@ def plural(count: int, one: str, few: str, many: str) -> str:
|
||||
return many
|
||||
|
||||
|
||||
def with_icon(text: str, icon: str) -> str:
|
||||
return f"{icon} {text}" if text[:1].isalnum() else text
|
||||
|
||||
|
||||
def pages(total: int, size: int) -> int:
|
||||
return max(1, (total + size - 1) // size)
|
||||
|
||||
@@ -97,10 +101,10 @@ def pager(kb: InlineKeyboardBuilder, prefix: str, page: int, total_pages: int) -
|
||||
return 0
|
||||
count = 0
|
||||
if page > 0:
|
||||
kb.button(text="‹ Назад", callback_data=f"{prefix}:{page - 1}")
|
||||
kb.button(text="◀️ Назад", callback_data=f"{prefix}:{page - 1}")
|
||||
count += 1
|
||||
if page < total_pages - 1:
|
||||
kb.button(text="Дальше ›", callback_data=f"{prefix}:{page + 1}")
|
||||
kb.button(text="Дальше ▶️", callback_data=f"{prefix}:{page + 1}")
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
Reference in new issue
Block a user