From 45fd8fe2215dd38afaae205de2b2a7ab46b383ec Mon Sep 17 00:00:00 2001 From: Vladless Date: Thu, 17 Apr 2025 15:02:54 +0300 Subject: [PATCH] on/off server in cluster --- assets/schema.sql | 3 ++ database.py | 17 +++++---- handlers/admin/servers/keyboard.py | 14 +++++--- handlers/admin/servers/servers_handler.py | 44 +++++++++++++++++++++-- handlers/keys/key_utils.py | 11 ++++-- 5 files changed, 73 insertions(+), 16 deletions(-) diff --git a/assets/schema.sql b/assets/schema.sql index 0f84a171..f7885de4 100644 --- a/assets/schema.sql +++ b/assets/schema.sql @@ -133,6 +133,9 @@ CREATE TABLE IF NOT EXISTS servers ALTER TABLE servers ADD COLUMN IF NOT EXISTS panel_type TEXT NOT NULL DEFAULT '3x-ui'; ALTER TABLE servers ALTER COLUMN subscription_url DROP NOT NULL; +ALTER TABLE servers +ADD COLUMN IF NOT EXISTS enabled BOOLEAN NOT NULL DEFAULT TRUE; + CREATE TABLE IF NOT EXISTS gifts diff --git a/database.py b/database.py index 7f219fe2..ccc6ea54 100644 --- a/database.py +++ b/database.py @@ -1341,17 +1341,21 @@ async def get_last_notification_time(tg_id: int, notification_type: str, session await conn.close() -async def get_servers(session: Any = None): +async def get_servers(session: Any = None, include_enabled: bool = False): conn = None try: conn = session if session is not None else await asyncpg.connect(DATABASE_URL) - result = await conn.fetch( - """ + query = """ SELECT cluster_name, server_name, api_url, subscription_url, inbound_id, panel_type - FROM servers - """ - ) + """ + if include_enabled: + query += ", enabled" + + query += " FROM servers" + + result = await conn.fetch(query) + servers = {} for row in result: cluster_name = row["cluster_name"] @@ -1364,6 +1368,7 @@ async def get_servers(session: Any = None): "subscription_url": row["subscription_url"], "inbound_id": row["inbound_id"], "panel_type": row["panel_type"], + "enabled": row.get("enabled", True), }) return servers diff --git a/handlers/admin/servers/keyboard.py b/handlers/admin/servers/keyboard.py index 31d4c7d2..b9b568cb 100644 --- a/handlers/admin/servers/keyboard.py +++ b/handlers/admin/servers/keyboard.py @@ -10,14 +10,20 @@ class AdminServerCallback(CallbackData, prefix="admin_server"): data: str -def build_manage_server_kb(server_name: str, cluster_name: str) -> InlineKeyboardMarkup: +def build_manage_server_kb(server_name: str, cluster_name: str, enabled: bool) -> InlineKeyboardMarkup: from ..clusters.keyboard import AdminClusterCallback builder = InlineKeyboardBuilder() - builder.button(text="🗑️ Удалить", callback_data=AdminServerCallback(action="delete", data=server_name).pack()) + + toggle_text = "🔴 Отключить" if enabled else "🟢 Включить" + toggle_action = "disable" if enabled else "enable" + builder.button( - text="✏️ Сменить название", callback_data=AdminServerCallback(action="rename", data=server_name).pack() + text=toggle_text, callback_data=AdminServerCallback(action=toggle_action, data=server_name).pack() ) + builder.button(text="🗑️ Удалить", callback_data=AdminServerCallback(action="delete", data=server_name).pack()) + builder.button(text="✏️ Сменить название", callback_data=AdminServerCallback(action="rename", data=server_name).pack()) builder.button(text=BACK, callback_data=AdminClusterCallback(action="manage", data=cluster_name).pack()) + builder.adjust(1) - return builder.as_markup() + return builder.as_markup() \ No newline at end of file diff --git a/handlers/admin/servers/servers_handler.py b/handlers/admin/servers/servers_handler.py index 816404fb..cd1ace22 100644 --- a/handlers/admin/servers/servers_handler.py +++ b/handlers/admin/servers/servers_handler.py @@ -5,7 +5,7 @@ from aiogram.fsm.context import FSMContext from aiogram.types import CallbackQuery, InlineKeyboardButton from aiogram.utils.keyboard import InlineKeyboardBuilder -from database import delete_server, get_servers +from database import get_servers from filters.admin import IsAdminFilter from handlers.buttons import BACK @@ -22,7 +22,7 @@ router = Router() @router.callback_query(AdminServerCallback.filter(F.action == "manage"), IsAdminFilter()) async def handle_server_manage(callback_query: CallbackQuery, callback_data: AdminServerCallback): server_name = callback_data.data - servers = await get_servers() + servers = await get_servers(include_enabled=True) cluster_name, server = next( ((c, s) for c, cs in servers.items() for s in cs if s["server_name"] == server_name), (None, None) @@ -42,7 +42,7 @@ async def handle_server_manage(callback_query: CallbackQuery, callback_data: Adm await callback_query.message.edit_text( text=text, - reply_markup=build_manage_server_kb(server_name, cluster_name), + reply_markup=build_manage_server_kb(server_name, cluster_name, enabled=server.get("enabled", True)), ) else: await callback_query.message.edit_text(text="❌ Сервер не найден.") @@ -170,3 +170,41 @@ async def process_callback_delete_server( text=f"✅ Сервер '{server_name}' удален.", reply_markup=build_admin_back_kb("clusters"), ) + + +@router.callback_query( + AdminServerCallback.filter(F.action.in_(["enable", "disable"])), IsAdminFilter() +) +async def toggle_server_enabled(callback_query: CallbackQuery, callback_data: AdminServerCallback, session: Any): + server_name = callback_data.data + action = callback_data.action + + new_status = action == "enable" + + await session.execute( + "UPDATE servers SET enabled = $1 WHERE server_name = $2", + new_status, + server_name + ) + + servers = await get_servers(include_enabled=True) + + cluster_name, server = next( + ((c, s) for c, cs in servers.items() for s in cs if s["server_name"] == server_name), (None, None) + ) + + if not server: + await callback_query.message.edit_text("❌ Сервер не найден.") + return + + text = ( + f"🔧 Информация о сервере {server_name}:\n\n" + f"📡 API URL: {server['api_url']}\n" + f"🌐 Subscription URL: {server['subscription_url']}\n" + f"🔑 Inbound ID: {server['inbound_id']}" + ) + + await callback_query.message.edit_text( + text=text, + reply_markup=build_manage_server_kb(server_name, cluster_name, enabled=new_status) + ) diff --git a/handlers/keys/key_utils.py b/handlers/keys/key_utils.py index ae512e83..8d13cb8d 100644 --- a/handlers/keys/key_utils.py +++ b/handlers/keys/key_utils.py @@ -43,7 +43,7 @@ async def create_key_on_cluster( remnawave_link: str = None, ): try: - servers = await get_servers() + servers = await get_servers(include_enabled=True) cluster = servers.get(cluster_id) server_id_to_store = cluster_id @@ -59,10 +59,15 @@ async def create_key_on_cluster( else: raise ValueError(f"Кластер или сервер с ID/именем {cluster_id} не найден.") + enabled_servers = [s for s in cluster if s.get("enabled", True)] + if not enabled_servers: + logger.warning(f"[Key Creation] Нет доступных серверов в кластере {cluster_id}") + return + semaphore = asyncio.Semaphore(2) - remnawave_servers = [s for s in cluster if s.get("panel_type", "3x-ui").lower() == "remnawave"] - xui_servers = [s for s in cluster if s.get("panel_type", "3x-ui").lower() == "3x-ui"] + remnawave_servers = [s for s in enabled_servers if s.get("panel_type", "3x-ui").lower() == "remnawave"] + xui_servers = [s for s in enabled_servers if s.get("panel_type", "3x-ui").lower() == "3x-ui"] remnawave_created = False remnawave_key = None