Add NPMplus proxy hosts controls
This commit is contained in:
@@ -10,7 +10,7 @@ from services.http_checks import get_url_checks, check_url
|
|||||||
from services.queue import enqueue
|
from services.queue import enqueue
|
||||||
from services.updates import list_updates, apply_updates
|
from services.updates import list_updates, apply_updates
|
||||||
from services.runner import run_cmd
|
from services.runner import run_cmd
|
||||||
from services.npmplus import fetch_certificates, format_certificates
|
from services.npmplus import fetch_certificates, format_certificates, list_proxy_hosts, set_proxy_host
|
||||||
import state
|
import state
|
||||||
from state import UPDATES_CACHE, REBOOT_PENDING
|
from state import UPDATES_CACHE, REBOOT_PENDING
|
||||||
from services.metrics import summarize
|
from services.metrics import summarize
|
||||||
@@ -246,6 +246,49 @@ async def ssl_certs(msg: Message):
|
|||||||
asyncio.create_task(worker())
|
asyncio.create_task(worker())
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message(F.text == "🧩 NPMplus")
|
||||||
|
async def npmplus_hosts(msg: Message):
|
||||||
|
if not is_admin_msg(msg):
|
||||||
|
return
|
||||||
|
|
||||||
|
await msg.answer("⏳ Loading NPMplus hosts…", reply_markup=system_logs_kb)
|
||||||
|
|
||||||
|
async def worker():
|
||||||
|
try:
|
||||||
|
hosts = await asyncio.to_thread(list_proxy_hosts, cfg)
|
||||||
|
except Exception as e:
|
||||||
|
await msg.answer(f"⚠️ NPMplus error: {e}", reply_markup=system_logs_kb)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not hosts:
|
||||||
|
await msg.answer("🧩 NPMplus\n\n(no proxy hosts)", reply_markup=system_logs_kb)
|
||||||
|
return
|
||||||
|
|
||||||
|
lines = ["🧩 NPMplus proxy hosts\n"]
|
||||||
|
rows = []
|
||||||
|
for h in hosts[:10]:
|
||||||
|
hid = h.get("id")
|
||||||
|
domains = h.get("domain_names") or []
|
||||||
|
name = ", ".join(domains) if isinstance(domains, list) else str(domains)
|
||||||
|
forward = f"{h.get('forward_host')}:{h.get('forward_port')}"
|
||||||
|
enabled = h.get("enabled", True)
|
||||||
|
icon = "🟢" if enabled else "🔴"
|
||||||
|
lines.append(f"{icon} {name} → {forward}")
|
||||||
|
if hid is not None:
|
||||||
|
action = "disable" if enabled else "enable"
|
||||||
|
rows.append([
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=f\"{('⛔' if enabled else '✅')} {name[:12]}\",
|
||||||
|
callback_data=f\"npmplus:{action}:{hid}\"
|
||||||
|
)
|
||||||
|
])
|
||||||
|
|
||||||
|
kb = InlineKeyboardMarkup(inline_keyboard=rows)
|
||||||
|
await msg.answer("\n".join(lines), reply_markup=kb)
|
||||||
|
|
||||||
|
asyncio.create_task(worker())
|
||||||
|
|
||||||
|
|
||||||
@dp.message(F.text == "📦 Updates")
|
@dp.message(F.text == "📦 Updates")
|
||||||
async def updates_list(msg: Message):
|
async def updates_list(msg: Message):
|
||||||
if not is_admin_msg(msg):
|
if not is_admin_msg(msg):
|
||||||
@@ -381,6 +424,30 @@ async def reboot_cancel(cb: CallbackQuery):
|
|||||||
await cb.message.delete()
|
await cb.message.delete()
|
||||||
|
|
||||||
|
|
||||||
|
@dp.callback_query(F.data.startswith("npmplus:"))
|
||||||
|
async def npmplus_toggle(cb: CallbackQuery):
|
||||||
|
if cb.from_user.id != cfg["telegram"]["admin_id"]:
|
||||||
|
return
|
||||||
|
parts = cb.data.split(":")
|
||||||
|
if len(parts) != 3:
|
||||||
|
await cb.answer("Bad request")
|
||||||
|
return
|
||||||
|
action, raw_id = parts[1], parts[2]
|
||||||
|
try:
|
||||||
|
host_id = int(raw_id)
|
||||||
|
except ValueError:
|
||||||
|
await cb.answer("Bad id")
|
||||||
|
return
|
||||||
|
|
||||||
|
enable = action == "enable"
|
||||||
|
await cb.answer("Working…")
|
||||||
|
ok, info = await asyncio.to_thread(set_proxy_host, cfg, host_id, enable)
|
||||||
|
if ok:
|
||||||
|
await cb.message.answer("✅ Updated", reply_markup=system_logs_kb)
|
||||||
|
else:
|
||||||
|
await cb.message.answer(f"❌ NPMplus error: {info}", reply_markup=system_logs_kb)
|
||||||
|
|
||||||
|
|
||||||
@dp.message(F.text, F.func(lambda msg: msg.from_user and msg.from_user.id in REBOOT_PENDING))
|
@dp.message(F.text, F.func(lambda msg: msg.from_user and msg.from_user.id in REBOOT_PENDING))
|
||||||
async def reboot_password(msg: Message):
|
async def reboot_password(msg: Message):
|
||||||
if not is_admin_msg(msg):
|
if not is_admin_msg(msg):
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ system_logs_kb = ReplyKeyboardMarkup(
|
|||||||
keyboard=[
|
keyboard=[
|
||||||
[KeyboardButton(text="🧾 Audit"), KeyboardButton(text="📣 Incidents")],
|
[KeyboardButton(text="🧾 Audit"), KeyboardButton(text="📣 Incidents")],
|
||||||
[KeyboardButton(text="🧰 Processes"), KeyboardButton(text="🔒 SSL")],
|
[KeyboardButton(text="🧰 Processes"), KeyboardButton(text="🔒 SSL")],
|
||||||
[KeyboardButton(text="🌐 URLs"), KeyboardButton(text="🔑 SSH log")],
|
[KeyboardButton(text="🔑 SSH log"), KeyboardButton(text="🧩 NPMplus")],
|
||||||
[KeyboardButton(text="🌍 External")],
|
[KeyboardButton(text="🌍 External"), KeyboardButton(text="🌐 URLs")],
|
||||||
[KeyboardButton(text="⬅️ System")],
|
[KeyboardButton(text="⬅️ System")],
|
||||||
],
|
],
|
||||||
resize_keyboard=True,
|
resize_keyboard=True,
|
||||||
|
|||||||
@@ -132,6 +132,48 @@ def fetch_certificates(cfg: dict[str, Any]) -> list[dict[str, Any]]:
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def list_proxy_hosts(cfg: dict[str, Any]) -> list[dict[str, Any]]:
|
||||||
|
npm_cfg = cfg.get("npmplus", {})
|
||||||
|
base_url = (npm_cfg.get("base_url") or "").rstrip("/")
|
||||||
|
verify_tls = npm_cfg.get("verify_tls", True)
|
||||||
|
if not base_url:
|
||||||
|
raise ValueError("NPMplus base_url not configured")
|
||||||
|
|
||||||
|
token = _get_token(cfg)
|
||||||
|
url = f"{base_url}/nginx/proxy-hosts"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
"User-Agent": "tg-admin-bot",
|
||||||
|
}
|
||||||
|
data = _request_json(url, headers, None, verify_tls)
|
||||||
|
if not isinstance(data, list):
|
||||||
|
raise RuntimeError("Unexpected API response")
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def set_proxy_host(cfg: dict[str, Any], host_id: int, enabled: bool) -> tuple[bool, str]:
|
||||||
|
npm_cfg = cfg.get("npmplus", {})
|
||||||
|
base_url = (npm_cfg.get("base_url") or "").rstrip("/")
|
||||||
|
verify_tls = npm_cfg.get("verify_tls", True)
|
||||||
|
if not base_url:
|
||||||
|
return False, "NPMplus base_url not configured"
|
||||||
|
|
||||||
|
token = _get_token(cfg)
|
||||||
|
action = "enable" if enabled else "disable"
|
||||||
|
url = f"{base_url}/nginx/proxy-hosts/{host_id}/{action}"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
"User-Agent": "tg-admin-bot",
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
payload = _request_json(url, headers, None, verify_tls)
|
||||||
|
except Exception as e:
|
||||||
|
return False, str(e)
|
||||||
|
if payload is True or (isinstance(payload, dict) and payload.get("success", True)):
|
||||||
|
return True, "OK"
|
||||||
|
return False, "API returned error"
|
||||||
|
|
||||||
|
|
||||||
def format_certificates(certs: list[dict[str, Any]]) -> str:
|
def format_certificates(certs: list[dict[str, Any]]) -> str:
|
||||||
if not certs:
|
if not certs:
|
||||||
return "🔒 SSL certificates\n\nℹ️ No certificates found"
|
return "🔒 SSL certificates\n\nℹ️ No certificates found"
|
||||||
|
|||||||
Reference in New Issue
Block a user