From 5d169c0b815ac82fe166503a39e48aa9caa8503d Mon Sep 17 00:00:00 2001 From: benya Date: Sat, 7 Feb 2026 23:30:50 +0300 Subject: [PATCH] Add Arcane start/stop actions --- handlers/arcane.py | 46 ++++++++++++++++++++++++++++++++++++++++++++-- services/arcane.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/handlers/arcane.py b/handlers/arcane.py index 525b9cc..037c183 100644 --- a/handlers/arcane.py +++ b/handlers/arcane.py @@ -4,7 +4,7 @@ from aiogram.types import Message from app import dp, cfg from auth import is_admin_msg from keyboards import docker_kb, arcane_kb -from services.arcane import list_projects, restart_project +from services.arcane import list_projects, restart_project, set_project_state from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery @@ -38,7 +38,11 @@ async def cmd_arcane_projects(msg: Message): icon = "🟢" if status == "running" else "🟡" lines.append(f"{icon} {name}: {status} ({running}/{total})") if pid: - rows.append([InlineKeyboardButton(text=f"🔄 {name}", callback_data=f"arcane:restart:{pid}")]) + rows.append([ + InlineKeyboardButton(text=f"🔄 {name}", callback_data=f"arcane:restart:{pid}"), + InlineKeyboardButton(text="▶️", callback_data=f"arcane:start:{pid}"), + InlineKeyboardButton(text="⏹", callback_data=f"arcane:stop:{pid}"), + ]) kb = InlineKeyboardMarkup(inline_keyboard=rows) if rows else arcane_kb await msg.answer("\n".join(lines), reply_markup=kb) @@ -75,3 +79,41 @@ async def arcane_restart(cb: CallbackQuery): await cb.message.answer("✅ Arcane restart triggered", reply_markup=arcane_kb) else: await cb.message.answer(f"❌ Arcane restart failed: {info}", reply_markup=arcane_kb) + + +@dp.callback_query(F.data.startswith("arcane:start:")) +async def arcane_start(cb: CallbackQuery): + if cb.from_user.id != cfg["telegram"]["admin_id"]: + return + + _, _, pid = cb.data.split(":", 2) + base_url, api_key, env_id = _arcane_cfg() + if not base_url or not api_key: + await cb.answer("Arcane config missing") + return + + await cb.answer("Starting…") + ok, info = await asyncio.to_thread(set_project_state, base_url, api_key, env_id, pid, "start") + if ok: + await cb.message.answer("✅ Arcane start triggered", reply_markup=arcane_kb) + else: + await cb.message.answer(f"❌ Arcane start failed: {info}", reply_markup=arcane_kb) + + +@dp.callback_query(F.data.startswith("arcane:stop:")) +async def arcane_stop(cb: CallbackQuery): + if cb.from_user.id != cfg["telegram"]["admin_id"]: + return + + _, _, pid = cb.data.split(":", 2) + base_url, api_key, env_id = _arcane_cfg() + if not base_url or not api_key: + await cb.answer("Arcane config missing") + return + + await cb.answer("Stopping…") + ok, info = await asyncio.to_thread(set_project_state, base_url, api_key, env_id, pid, "stop") + if ok: + await cb.message.answer("✅ Arcane stop triggered", reply_markup=arcane_kb) + else: + await cb.message.answer(f"❌ Arcane stop failed: {info}", reply_markup=arcane_kb) diff --git a/services/arcane.py b/services/arcane.py index 4b2fc47..e54b3dd 100644 --- a/services/arcane.py +++ b/services/arcane.py @@ -48,3 +48,33 @@ def restart_project(base_url: str, api_key: str, env_id: int, project_id: str, t if payload and not payload.get("success", True): return False, "API returned success=false" return True, "OK" + + +def set_project_state( + base_url: str, + api_key: str, + env_id: int, + project_id: str, + action: str, + timeout: int = 20, +) -> tuple[bool, str]: + url = f"{base_url.rstrip('/')}/api/environments/{env_id}/projects/{project_id}/{action}" + req = Request(url, method="POST", headers={"X-Api-Key": api_key}) + try: + with urlopen(req, timeout=timeout) as resp: + raw = resp.read().decode("utf-8", errors="ignore") + except HTTPError as e: + return False, f"HTTP {e.code}" + except URLError as e: + return False, f"URL error: {e.reason}" + except Exception as e: + return False, f"Error: {e}" + + try: + payload = json.loads(raw) if raw else {} + except json.JSONDecodeError: + payload = {} + + if payload and not payload.get("success", True): + return False, "API returned success=false" + return True, "OK"