Add Arcane project status
This commit is contained in:
@@ -22,6 +22,11 @@ alerts:
|
|||||||
smart_cooldown_sec: 21600
|
smart_cooldown_sec: 21600
|
||||||
smart_temp_warn: 50
|
smart_temp_warn: 50
|
||||||
|
|
||||||
|
arcane:
|
||||||
|
base_url: "http://localhost:3552"
|
||||||
|
api_key: "arc_..."
|
||||||
|
env_id: 0
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
# If true, discover containers by name/label
|
# If true, discover containers by name/label
|
||||||
autodiscovery: true
|
autodiscovery: true
|
||||||
|
|||||||
52
handlers/arcane.py
Normal file
52
handlers/arcane.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import asyncio
|
||||||
|
from aiogram import F
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def _arcane_cfg():
|
||||||
|
arc = cfg.get("arcane", {})
|
||||||
|
return arc.get("base_url"), arc.get("api_key"), int(arc.get("env_id", 0))
|
||||||
|
|
||||||
|
|
||||||
|
async def cmd_arcane_projects(msg: Message):
|
||||||
|
base_url, api_key, env_id = _arcane_cfg()
|
||||||
|
if not base_url or not api_key:
|
||||||
|
await msg.answer("⚠️ Arcane config missing", reply_markup=docker_kb)
|
||||||
|
return
|
||||||
|
|
||||||
|
await msg.answer("⏳ Arcane projects…", reply_markup=arcane_kb)
|
||||||
|
|
||||||
|
async def worker():
|
||||||
|
ok, info, items = await asyncio.to_thread(list_projects, base_url, api_key, env_id)
|
||||||
|
if not ok:
|
||||||
|
await msg.answer(f"❌ Arcane error: {info}", reply_markup=arcane_kb)
|
||||||
|
return
|
||||||
|
|
||||||
|
lines = ["🧰 Arcane projects\n"]
|
||||||
|
for p in items:
|
||||||
|
status = p.get("status", "unknown")
|
||||||
|
name = p.get("name", "?")
|
||||||
|
running = p.get("runningCount", 0)
|
||||||
|
total = p.get("serviceCount", 0)
|
||||||
|
icon = "🟢" if status == "running" else "🟡"
|
||||||
|
lines.append(f"{icon} {name}: {status} ({running}/{total})")
|
||||||
|
|
||||||
|
await msg.answer("\n".join(lines), reply_markup=arcane_kb)
|
||||||
|
|
||||||
|
asyncio.create_task(worker())
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message(F.text == "🧰 Arcane")
|
||||||
|
async def arcane_menu(msg: Message):
|
||||||
|
if is_admin_msg(msg):
|
||||||
|
await cmd_arcane_projects(msg)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message(F.text == "🔄 Refresh")
|
||||||
|
async def arcane_refresh(msg: Message):
|
||||||
|
if is_admin_msg(msg):
|
||||||
|
await cmd_arcane_projects(msg)
|
||||||
@@ -19,12 +19,21 @@ menu_kb = ReplyKeyboardMarkup(
|
|||||||
docker_kb = ReplyKeyboardMarkup(
|
docker_kb = ReplyKeyboardMarkup(
|
||||||
keyboard=[
|
keyboard=[
|
||||||
[KeyboardButton(text="🐳 Status")],
|
[KeyboardButton(text="🐳 Status")],
|
||||||
|
[KeyboardButton(text="🧰 Arcane")],
|
||||||
[KeyboardButton(text="🔄 Restart"), KeyboardButton(text="📜 Logs")],
|
[KeyboardButton(text="🔄 Restart"), KeyboardButton(text="📜 Logs")],
|
||||||
[KeyboardButton(text="⬅️ Назад")],
|
[KeyboardButton(text="⬅️ Назад")],
|
||||||
],
|
],
|
||||||
resize_keyboard=True,
|
resize_keyboard=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
arcane_kb = ReplyKeyboardMarkup(
|
||||||
|
keyboard=[
|
||||||
|
[KeyboardButton(text="🔄 Refresh")],
|
||||||
|
[KeyboardButton(text="⬅️ Назад")],
|
||||||
|
],
|
||||||
|
resize_keyboard=True,
|
||||||
|
)
|
||||||
|
|
||||||
backup_kb = ReplyKeyboardMarkup(
|
backup_kb = ReplyKeyboardMarkup(
|
||||||
keyboard=[
|
keyboard=[
|
||||||
[KeyboardButton(text="📦 Status"), KeyboardButton(text="📦 Last snapshot")],
|
[KeyboardButton(text="📦 Status"), KeyboardButton(text="📦 Last snapshot")],
|
||||||
|
|||||||
1
main.py
1
main.py
@@ -16,6 +16,7 @@ import handlers.artifacts
|
|||||||
import handlers.system
|
import handlers.system
|
||||||
import handlers.help
|
import handlers.help
|
||||||
import handlers.callbacks
|
import handlers.callbacks
|
||||||
|
import handlers.arcane
|
||||||
|
|
||||||
|
|
||||||
async def notify_start():
|
async def notify_start():
|
||||||
|
|||||||
27
services/arcane.py
Normal file
27
services/arcane.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import json
|
||||||
|
from urllib.request import Request, urlopen
|
||||||
|
from urllib.error import URLError, HTTPError
|
||||||
|
|
||||||
|
|
||||||
|
def list_projects(base_url: str, api_key: str, env_id: int, timeout: int = 10) -> tuple[bool, str, list[dict]]:
|
||||||
|
url = f"{base_url.rstrip('/')}/api/environments/{env_id}/projects"
|
||||||
|
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