Add Arcane project details
This commit is contained in:
@@ -5,7 +5,7 @@ from aiogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton, C
|
|||||||
from app import dp, cfg
|
from app import dp, cfg
|
||||||
from auth import is_admin_msg
|
from auth import is_admin_msg
|
||||||
from keyboards import docker_kb, arcane_kb
|
from keyboards import docker_kb, arcane_kb
|
||||||
from services.arcane import list_projects, restart_project, set_project_state
|
from services.arcane import list_projects, restart_project, set_project_state, get_project_details
|
||||||
from state import ARCANE_CACHE
|
from state import ARCANE_CACHE
|
||||||
|
|
||||||
|
|
||||||
@@ -26,6 +26,7 @@ def _arcane_kb(page: int, total_pages: int, items: list[dict]) -> InlineKeyboard
|
|||||||
action_text = "⏹" if action == "down" else "▶️"
|
action_text = "⏹" if action == "down" else "▶️"
|
||||||
rows.append([
|
rows.append([
|
||||||
InlineKeyboardButton(text=f"🔄 {name}", callback_data=f"arcane:restart:{pid}"),
|
InlineKeyboardButton(text=f"🔄 {name}", callback_data=f"arcane:restart:{pid}"),
|
||||||
|
InlineKeyboardButton(text="ℹ️", callback_data=f"arcane:details:{pid}"),
|
||||||
InlineKeyboardButton(text=action_text, callback_data=f"arcane:{action}:{pid}"),
|
InlineKeyboardButton(text=action_text, callback_data=f"arcane:{action}:{pid}"),
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -156,6 +157,54 @@ async def arcane_restart(cb: CallbackQuery):
|
|||||||
await cb.message.answer(f"❌ Arcane restart failed: {info}", reply_markup=arcane_kb)
|
await cb.message.answer(f"❌ Arcane restart failed: {info}", reply_markup=arcane_kb)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.callback_query(F.data.startswith("arcane:details:"))
|
||||||
|
async def arcane_details(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("Loading…")
|
||||||
|
ok, info, data = await asyncio.to_thread(get_project_details, base_url, api_key, env_id, pid)
|
||||||
|
if not ok:
|
||||||
|
await cb.message.answer(f"❌ Arcane details failed: {info}", reply_markup=arcane_kb)
|
||||||
|
return
|
||||||
|
|
||||||
|
name = data.get("name", "?")
|
||||||
|
status = data.get("status", "unknown")
|
||||||
|
running = data.get("runningCount", 0)
|
||||||
|
total = data.get("serviceCount", 0)
|
||||||
|
status_reason = data.get("statusReason")
|
||||||
|
icon = "🟢" if status == "running" else "🟡"
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
f"🧰 **{name}**",
|
||||||
|
f"{icon} Status: {status} ({running}/{total})",
|
||||||
|
]
|
||||||
|
if status_reason:
|
||||||
|
lines.append(f"⚠️ {status_reason}")
|
||||||
|
|
||||||
|
services = data.get("runtimeServices", [])
|
||||||
|
if services:
|
||||||
|
lines.append("")
|
||||||
|
lines.append("🧩 Services:")
|
||||||
|
for s in services:
|
||||||
|
s_name = s.get("name", "?")
|
||||||
|
s_status = s.get("status", "unknown")
|
||||||
|
s_health = s.get("health")
|
||||||
|
s_icon = "🟢" if s_status == "running" else "🟡"
|
||||||
|
line = f"{s_icon} {s_name}: {s_status}"
|
||||||
|
if s_health:
|
||||||
|
line += f" ({s_health})"
|
||||||
|
lines.append(line)
|
||||||
|
|
||||||
|
await cb.message.answer("\n".join(lines), parse_mode="Markdown", reply_markup=arcane_kb)
|
||||||
|
|
||||||
|
|
||||||
@dp.callback_query(F.data.startswith("arcane:up:"))
|
@dp.callback_query(F.data.startswith("arcane:up:"))
|
||||||
async def arcane_up(cb: CallbackQuery):
|
async def arcane_up(cb: CallbackQuery):
|
||||||
if cb.from_user.id != cfg["telegram"]["admin_id"]:
|
if cb.from_user.id != cfg["telegram"]["admin_id"]:
|
||||||
|
|||||||
@@ -78,3 +78,27 @@ def set_project_state(
|
|||||||
if payload and not payload.get("success", True):
|
if payload and not payload.get("success", True):
|
||||||
return False, "API returned success=false"
|
return False, "API returned success=false"
|
||||||
return True, "OK"
|
return True, "OK"
|
||||||
|
|
||||||
|
|
||||||
|
def get_project_details(base_url: str, api_key: str, env_id: int, project_id: str, timeout: int = 10) -> tuple[bool, str, dict]:
|
||||||
|
url = f"{base_url.rstrip('/')}/api/environments/{env_id}/projects/{project_id}"
|
||||||
|
req = Request(url, 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)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return False, "Invalid JSON", {}
|
||||||
|
|
||||||
|
if not payload.get("success"):
|
||||||
|
return False, "API returned success=false", {}
|
||||||
|
|
||||||
|
return True, "OK", payload.get("data", {})
|
||||||
|
|||||||
Reference in New Issue
Block a user