diff --git a/src/healthbot/alerts.py b/src/healthbot/alerts.py
index 9e5f561..2383c95 100644
--- a/src/healthbot/alerts.py
+++ b/src/healthbot/alerts.py
@@ -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
diff --git a/src/healthbot/digest.py b/src/healthbot/digest.py
index c9dc289..01f160a 100644
--- a/src/healthbot/digest.py
+++ b/src/healthbot/digest.py
@@ -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"Сервисы: аптайм {pct(uptime)}, падений не было"]
+ return [f"📋 Сервисы: аптайм {pct(uptime)}, падений не было"]
word = plural(falls, "падение", "падения", "падений")
lines = [
- f"Сервисы: аптайм {pct(uptime)}, {falls} {word}, "
+ f"📋 Сервисы: аптайм {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"Флот: {esc(exc)}"]
+ return [f"🛰 Флот: {esc(exc)}"]
tz = db.settings.get("tz")
failed = [u for u in updates if u.done and not u.success]
- lines = [f"Флот: операций {len(updates)}, неудачных {len(failed)}"]
+ lines = [f"🛰 Флот: операций {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 = ["Сводка за сутки"]
+ lines = ["🗞 Сводка за сутки"]
for block in blocks:
if block:
lines += ["", *block]
diff --git a/src/healthbot/handlers/actions.py b/src/healthbot/handlers/actions.py
index 10eb4b6..4c88d42 100644
--- a/src/healthbot/handlers/actions.py
+++ b/src/healthbot/handlers/actions.py
@@ -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"{KIND_TITLE[kind]}: {len(items)}"
+ text = f"{KIND_ICON[kind]} {KIND_TITLE[kind]}: {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"{esc(detail.name)}"]
+ lines = [f"{KIND_ICON[kind]} {esc(detail.name)}"]
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"{esc(key)}: {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 = "Закреплённые"
+ text = "📌 Закреплённые"
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"Пришли новое название для кнопки {esc(pin.label)}.",
- 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")):
diff --git a/src/healthbot/handlers/add.py b/src/healthbot/handlers/add.py
index 2ff1a2a..ef91515 100644
--- a/src/healthbot/handlers/add.py
+++ b/src/healthbot/handlers/add.py
@@ -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"Пришли новый адрес для {esc(service.name)}.\n"
f"Сейчас: {esc(service.url)}",
- 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)
diff --git a/src/healthbot/handlers/fleet.py b/src/healthbot/handlers/fleet.py
index b57dba9..f0bce69 100644
--- a/src/healthbot/handlers/fleet.py
+++ b/src/healthbot/handlers/fleet.py
@@ -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"Флот: серверов {len(servers)}, стеков {len(stacks)}", ""]
+ lines = [f"🛰 Флот: серверов {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} {esc(server.name)}"]
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} {esc(stack.name)}",
@@ -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 = ["Не работают", ""]
+ lines = ["⚠️ Не работают", ""]
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 = ["Открытые алерты"]
+ lines = ["🔔 Открытые алерты"]
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 "лог пуст"
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"{esc(stack.name)}{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"{esc(stack.name)}: чьи логи?", 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"{title}: операции\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"Журнал\n\n{body}", kb.as_markup()
+ return f"🕘 Журнал\n\n{body}", kb.as_markup()
@router.callback_query(F.data == "fleet:journal")
diff --git a/src/healthbot/handlers/menu.py b/src/healthbot/handlers/menu.py
index d265213..5311732 100644
--- a/src/healthbot/handlers/menu.py
+++ b/src/healthbot/handlers/menu.py
@@ -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"Сервисы: {every}{working} {works}"]
+ return [f"📋 Сервисы: {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"Сервисы: {', '.join(parts)}"]
+ lines = [f"📋 Сервисы: {', '.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"Флот: {esc(exc)}"]
+ return [f"🛰 Флот: {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"Флот: серверов {len(servers)}, стеков {len(stacks)}, {summary}"]
+ lines = [f"🛰 Флот: серверов {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 = ["Флот: Komodo не отвечает"]
+ fleet = ["🛰 Флот: Komodo не отвечает"]
lines += ["", *fleet]
pins = await db.pins.all() if actions else []
return "\n".join(lines), main_menu(pins, actions=bool(actions))
diff --git a/src/healthbot/handlers/runs.py b/src/healthbot/handlers/runs.py
index 24cf60d..16916c6 100644
--- a/src/healthbot/handlers/runs.py
+++ b/src/healthbot/handlers/runs.py
@@ -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"⏳ {esc(title)} идёт уже {clock(update.seconds)}",
diff --git a/src/healthbot/handlers/services.py b/src/healthbot/handlers/services.py
index ba611d0..593c502 100644
--- a/src/healthbot/handlers/services.py
+++ b/src/healthbot/handlers/services.py
@@ -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"Сервисы {' '.join(parts)}", services_list(services, page)
+ return f"📋 Сервисы {' '.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"Пришли новое имя для {esc(service.name)}.",
- 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 = ["Статистика", ""]
+ lines = ["📊 Статистика", ""]
for _, service, incidents in rows:
speed = (
f" · {service.last_latency_ms} мс"
diff --git a/src/healthbot/handlers/settings.py b/src/healthbot/handlers/settings.py
index 2f5c691..85c28e9 100644
--- a/src/healthbot/handlers/settings.py
+++ b/src/healthbot/handlers/settings.py
@@ -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 = ["Настройки", await _komodo_line()]
+ lines = ["⚙️ Настройки", 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 "Алерты Komodo", kb.as_markup()
+ return "🔔 Алерты Komodo", 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,
"Пришли часовой пояс, например Europe/Warsaw.\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, например "
"Europe/Warsaw или UTC.",
- 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,
- "Сводка за сутки\n"
+ "🗞 Сводка за сутки\n"
"Раз в день: аптайм и падения сервисов, операции и алерты флота.\n"
f"Время по поясу {esc(db.settings.get('tz'))}.",
kb.as_markup(),
diff --git a/src/healthbot/keyboards/__init__.py b/src/healthbot/keyboards/__init__.py
index 3816729..befadf6 100644
--- a/src/healthbot/keyboards/__init__.py
+++ b/src/healthbot/keyboards/__init__.py
@@ -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")
diff --git a/src/healthbot/monitor.py b/src/healthbot/monitor.py
index 3cee02a..d1e0025 100644
--- a/src/healthbot/monitor.py
+++ b/src/healthbot/monitor.py
@@ -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()
diff --git a/src/healthbot/runs.py b/src/healthbot/runs.py
index 4bbb5e4..fa51bb7 100644
--- a/src/healthbot/runs.py
+++ b/src/healthbot/runs.py
@@ -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:
diff --git a/src/healthbot/ui.py b/src/healthbot/ui.py
index 7ae7bf9..fbbe3d4 100644
--- a/src/healthbot/ui.py
+++ b/src/healthbot/ui.py
@@ -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