Fix Discord channel activation system
- Fix MySQL boolean conversion in toggle_channel_status - Improve cache management with 5-second timeout - Add bulk channel selection and toggle functionality - Fix Jinja2 template syntax errors - Add comprehensive debugging for channel status queries - Implement real-time channel activation without container restart
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import signal
|
||||||
|
import time
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
@@ -9,7 +11,7 @@ import re
|
|||||||
import html
|
import html
|
||||||
|
|
||||||
from botdiscord.config import get_discord_token, load_config, get_languages
|
from botdiscord.config import get_discord_token, load_config, get_languages
|
||||||
from botdiscord.database import init_db, get_active_languages, get_bot_languages, save_message, get_welcome_message
|
from botdiscord.database import init_db, get_active_languages, get_bot_languages, save_message, get_welcome_message, is_channel_enabled
|
||||||
from botdiscord.ui import PersistentTranslationView, ConfigView, WelcomeTranslationView, TranslationButton
|
from botdiscord.ui import PersistentTranslationView, ConfigView, WelcomeTranslationView, TranslationButton
|
||||||
from botdiscord.translate import get_reverse_mapping, load_lang_mappings, get_name_to_code, get_flag_mapping
|
from botdiscord.translate import get_reverse_mapping, load_lang_mappings, get_name_to_code, get_flag_mapping
|
||||||
|
|
||||||
@@ -115,6 +117,14 @@ def get_active_langs_for_guild(guild_id):
|
|||||||
async def on_message(message):
|
async def on_message(message):
|
||||||
if message.author.bot: return
|
if message.author.bot: return
|
||||||
|
|
||||||
|
# Verificar si el canal está habilitado para traducción
|
||||||
|
channel_enabled = is_channel_enabled(message.channel.id)
|
||||||
|
print(f"[Bot] Mensaje en canal {message.channel.id} ({message.channel.name}) - Habilitado: {channel_enabled} - Timestamp: {time.strftime('%H:%M:%S')}")
|
||||||
|
|
||||||
|
if not channel_enabled:
|
||||||
|
print(f"[Bot] Ignorando mensaje en canal desactivado: {message.channel.name}")
|
||||||
|
return
|
||||||
|
|
||||||
text_content = message.content.strip()
|
text_content = message.content.strip()
|
||||||
if not text_content: return
|
if not text_content: return
|
||||||
|
|
||||||
|
|||||||
@@ -190,6 +190,19 @@ def init_db():
|
|||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4''')
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4''')
|
||||||
|
|
||||||
|
cursor.execute('''CREATE TABLE IF NOT EXISTS discord_servers
|
||||||
|
(server_id BIGINT NOT NULL PRIMARY KEY,
|
||||||
|
server_name VARCHAR(255) NOT NULL,
|
||||||
|
added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4''')
|
||||||
|
|
||||||
|
cursor.execute('''CREATE TABLE IF NOT EXISTS discord_channels
|
||||||
|
(channel_id BIGINT NOT NULL PRIMARY KEY,
|
||||||
|
channel_name VARCHAR(255) NOT NULL,
|
||||||
|
server_id BIGINT NOT NULL,
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (server_id) REFERENCES discord_servers(server_id) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4''')
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
cursor.close()
|
cursor.close()
|
||||||
else:
|
else:
|
||||||
@@ -243,6 +256,20 @@ def init_db():
|
|||||||
channel_id INTEGER NOT NULL,
|
channel_id INTEGER NOT NULL,
|
||||||
message_content TEXT NOT NULL,
|
message_content TEXT NOT NULL,
|
||||||
enabled INTEGER DEFAULT 1)''')
|
enabled INTEGER DEFAULT 1)''')
|
||||||
|
|
||||||
|
c.execute('''CREATE TABLE IF NOT EXISTS discord_servers
|
||||||
|
(server_id INTEGER PRIMARY KEY,
|
||||||
|
server_name TEXT NOT NULL,
|
||||||
|
added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)''')
|
||||||
|
|
||||||
|
c.execute('''CREATE TABLE IF NOT EXISTS discord_channels
|
||||||
|
(channel_id INTEGER PRIMARY KEY,
|
||||||
|
channel_name TEXT NOT NULL,
|
||||||
|
server_id INTEGER NOT NULL,
|
||||||
|
is_active INTEGER DEFAULT 1,
|
||||||
|
added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (server_id) REFERENCES discord_servers(server_id))''')
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
@@ -716,3 +743,232 @@ def delete_welcome_message(guild_id: int):
|
|||||||
c.execute("DELETE FROM welcome_messages WHERE guild_id = ?", (guild_id,))
|
c.execute("DELETE FROM welcome_messages WHERE guild_id = ?", (guild_id,))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
# Funciones para gestión de servidores y canales de Discord
|
||||||
|
def sync_discord_servers(servers: list):
|
||||||
|
db_type = get_db_type()
|
||||||
|
|
||||||
|
if db_type == "mysql":
|
||||||
|
conn = get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
for server in servers:
|
||||||
|
server_id = server.get('id')
|
||||||
|
server_name = server.get('name', '')
|
||||||
|
cursor.execute("""INSERT INTO discord_servers (server_id, server_name)
|
||||||
|
VALUES (%s, %s)
|
||||||
|
ON DUPLICATE KEY UPDATE server_name = %s""",
|
||||||
|
(server_id, server_name, server_name))
|
||||||
|
conn.commit()
|
||||||
|
cursor.close()
|
||||||
|
else:
|
||||||
|
conn = get_connection()
|
||||||
|
c = conn.cursor()
|
||||||
|
for server in servers:
|
||||||
|
server_id = server.get('id')
|
||||||
|
server_name = server.get('name', '')
|
||||||
|
c.execute("""INSERT OR REPLACE INTO discord_servers (server_id, server_name)
|
||||||
|
VALUES (?, ?)""", (server_id, server_name))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def get_discord_servers() -> list:
|
||||||
|
db_type = get_db_type()
|
||||||
|
|
||||||
|
if db_type == "mysql":
|
||||||
|
conn = get_connection()
|
||||||
|
cursor = conn.cursor(dictionary=True)
|
||||||
|
cursor.execute("SELECT server_id, server_name, added_at FROM discord_servers ORDER BY server_name")
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
cursor.close()
|
||||||
|
return rows
|
||||||
|
else:
|
||||||
|
conn = get_connection()
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute("SELECT server_id, server_name, added_at FROM discord_servers ORDER BY server_name")
|
||||||
|
rows = [{"server_id": r[0], "server_name": r[1], "added_at": r[2]} for r in c.fetchall()]
|
||||||
|
conn.close()
|
||||||
|
return rows
|
||||||
|
|
||||||
|
def sync_discord_channels(server_id: int, channels: list):
|
||||||
|
db_type = get_db_type()
|
||||||
|
|
||||||
|
try:
|
||||||
|
if db_type == "mysql":
|
||||||
|
conn = get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
for channel in channels:
|
||||||
|
channel_id = channel.get('id')
|
||||||
|
channel_name = channel.get('name', '')
|
||||||
|
cursor.execute("""INSERT INTO discord_channels (channel_id, channel_name, server_id)
|
||||||
|
VALUES (%s, %s, %s)
|
||||||
|
ON DUPLICATE KEY UPDATE channel_name = %s, server_id = %s""",
|
||||||
|
(channel_id, channel_name, server_id, channel_name, server_id))
|
||||||
|
conn.commit()
|
||||||
|
cursor.close()
|
||||||
|
else:
|
||||||
|
conn = get_connection()
|
||||||
|
c = conn.cursor()
|
||||||
|
for channel in channels:
|
||||||
|
channel_id = channel.get('id')
|
||||||
|
channel_name = channel.get('name', '')
|
||||||
|
c.execute("""INSERT OR REPLACE INTO discord_channels (channel_id, channel_name, server_id)
|
||||||
|
VALUES (?, ?, ?)""", (channel_id, channel_name, server_id))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
# Limpiar todo el cache de canales después de sincronizar
|
||||||
|
clear_channel_cache()
|
||||||
|
print(f"[DB] Canales sincronizados para servidor {server_id}, cache limpiado")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[DB] Error sincronizando canales del servidor {server_id}: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def get_discord_channels(server_id: int = None) -> list:
|
||||||
|
db_type = get_db_type()
|
||||||
|
|
||||||
|
if db_type == "mysql":
|
||||||
|
conn = get_connection()
|
||||||
|
cursor = conn.cursor(dictionary=True)
|
||||||
|
if server_id:
|
||||||
|
cursor.execute("SELECT channel_id, channel_name, server_id, is_active, added_at FROM discord_channels WHERE server_id = %s ORDER BY channel_name", (server_id,))
|
||||||
|
else:
|
||||||
|
cursor.execute("SELECT channel_id, channel_name, server_id, is_active, added_at FROM discord_channels ORDER BY server_id, channel_name")
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
cursor.close()
|
||||||
|
return rows
|
||||||
|
else:
|
||||||
|
conn = get_connection()
|
||||||
|
c = conn.cursor()
|
||||||
|
if server_id:
|
||||||
|
c.execute("SELECT channel_id, channel_name, server_id, is_active, added_at FROM discord_channels WHERE server_id = ? ORDER BY channel_name", (server_id,))
|
||||||
|
else:
|
||||||
|
c.execute("SELECT channel_id, channel_name, server_id, is_active, added_at FROM discord_channels ORDER BY server_id, channel_name")
|
||||||
|
rows = [{"channel_id": r[0], "channel_name": r[1], "server_id": r[2], "is_active": bool(r[3]), "added_at": r[4]} for r in c.fetchall()]
|
||||||
|
conn.close()
|
||||||
|
return rows
|
||||||
|
|
||||||
|
def toggle_channel_status(channel_id: int, is_active: bool):
|
||||||
|
db_type = get_db_type()
|
||||||
|
|
||||||
|
try:
|
||||||
|
if db_type == "mysql":
|
||||||
|
conn = get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("UPDATE discord_channels SET is_active = %s WHERE channel_id = %s",
|
||||||
|
(1 if is_active else 0, channel_id))
|
||||||
|
conn.commit()
|
||||||
|
cursor.close()
|
||||||
|
else:
|
||||||
|
conn = get_connection()
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute("UPDATE discord_channels SET is_active = ? WHERE channel_id = ?",
|
||||||
|
(1 if is_active else 0, channel_id))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
# Limpiar el cache para forzar re-lectura
|
||||||
|
clear_channel_cache(channel_id)
|
||||||
|
print(f"[DB] Canal {channel_id} actualizado a {is_active}, cache limpiado")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[DB] Error actualizando canal {channel_id}: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Cache simple para estados de canales (evita consultas excesivas)
|
||||||
|
_channel_status_cache = {}
|
||||||
|
_cache_timeout = 5 # segundos
|
||||||
|
_last_cache_update = {}
|
||||||
|
|
||||||
|
def is_channel_enabled(channel_id: int) -> bool:
|
||||||
|
global _channel_status_cache, _last_cache_update
|
||||||
|
|
||||||
|
db_type = get_db_type()
|
||||||
|
current_time = time.time()
|
||||||
|
|
||||||
|
# Verificar si tenemos un cache válido
|
||||||
|
if (channel_id in _channel_status_cache and
|
||||||
|
channel_id in _last_cache_update and
|
||||||
|
current_time - _last_cache_update[channel_id] < _cache_timeout):
|
||||||
|
result = _channel_status_cache[channel_id]
|
||||||
|
print(f"[DB Cache] Canal {channel_id} estado (cache): {result}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
try:
|
||||||
|
if db_type == "mysql":
|
||||||
|
conn = get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("SELECT is_active FROM discord_channels WHERE channel_id = %s", (channel_id,))
|
||||||
|
row = cursor.fetchone()
|
||||||
|
cursor.close()
|
||||||
|
print(f"[DB] MySQL Query: SELECT is_active FROM discord_channels WHERE channel_id = {channel_id}")
|
||||||
|
print(f"[DB] MySQL Row: {row}")
|
||||||
|
result = bool(row[0]) if row else False
|
||||||
|
else:
|
||||||
|
conn = get_connection()
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute("SELECT is_active FROM discord_channels WHERE channel_id = ?", (channel_id,))
|
||||||
|
row = c.fetchone()
|
||||||
|
conn.close()
|
||||||
|
print(f"[DB] SQLite Query: SELECT is_active FROM discord_channels WHERE channel_id = {channel_id}")
|
||||||
|
print(f"[DB] SQLite Row: {row}")
|
||||||
|
result = bool(row[0]) if row else False
|
||||||
|
|
||||||
|
# Actualizar cache
|
||||||
|
_channel_status_cache[channel_id] = result
|
||||||
|
_last_cache_update[channel_id] = current_time
|
||||||
|
|
||||||
|
print(f"[DB] Canal {channel_id} estado (DB): {result} - Timestamp: {time.strftime('%H:%M:%S')}")
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[DB] Error verificando canal {channel_id}: {e}")
|
||||||
|
# En caso de error, devolver False por seguridad
|
||||||
|
return False
|
||||||
|
|
||||||
|
def clear_channel_cache(channel_id: int = None):
|
||||||
|
"""Limpia el cache de un canal específico o de todos"""
|
||||||
|
global _channel_status_cache, _last_cache_update
|
||||||
|
if channel_id:
|
||||||
|
_channel_status_cache.pop(channel_id, None)
|
||||||
|
_last_cache_update.pop(channel_id, None)
|
||||||
|
print(f"[DB] Cache limpiado para canal {channel_id}")
|
||||||
|
# Forzar expiración inmediata poniendo un timestamp antiguo
|
||||||
|
_last_cache_update[channel_id] = 0
|
||||||
|
else:
|
||||||
|
_channel_status_cache.clear()
|
||||||
|
_last_cache_update.clear()
|
||||||
|
print(f"[DB] Todo el cache de canales limpiado")
|
||||||
|
|
||||||
|
def delete_server_channels(server_id: int):
|
||||||
|
"""Elimina todos los canales de un servidor específico"""
|
||||||
|
db_type = get_db_type()
|
||||||
|
|
||||||
|
if db_type == "mysql":
|
||||||
|
conn = get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("DELETE FROM discord_channels WHERE server_id = %s", (server_id,))
|
||||||
|
conn.commit()
|
||||||
|
cursor.close()
|
||||||
|
else:
|
||||||
|
conn = get_connection()
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute("DELETE FROM discord_channels WHERE server_id = ?", (server_id,))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def delete_discord_server(server_id: int):
|
||||||
|
"""Elimina un servidor y todos sus canales"""
|
||||||
|
db_type = get_db_type()
|
||||||
|
|
||||||
|
if db_type == "mysql":
|
||||||
|
conn = get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("DELETE FROM discord_servers WHERE server_id = %s", (server_id,))
|
||||||
|
conn.commit()
|
||||||
|
cursor.close()
|
||||||
|
else:
|
||||||
|
conn = get_connection()
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute("DELETE FROM discord_servers WHERE server_id = ?", (server_id,))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|||||||
187
panel/main.py
187
panel/main.py
@@ -8,9 +8,14 @@ from fastapi.templating import Jinja2Templates
|
|||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
from fastapi.responses import RedirectResponse
|
from fastapi.responses import RedirectResponse
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
from passlib.hash import pbkdf2_sha256 as hasher
|
from passlib.hash import pbkdf2_sha256 as hasher
|
||||||
from botdiscord.config import load_config, get_web_config, get_libretranslate_url, get_db_type
|
from botdiscord.config import load_config, get_web_config, get_libretranslate_url, get_db_type
|
||||||
|
|
||||||
|
# Asegurar que las variables de entorno se carguen correctamente
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
load_config() # Cargamos configuración inmediatamente
|
load_config() # Cargamos configuración inmediatamente
|
||||||
|
|
||||||
from botdiscord.database import (
|
from botdiscord.database import (
|
||||||
@@ -397,6 +402,188 @@ async def delete_welcome(request: Request):
|
|||||||
|
|
||||||
return RedirectResponse(url="/welcome", status_code=status.HTTP_303_SEE_OTHER)
|
return RedirectResponse(url="/welcome", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
|
|
||||||
|
@app.get("/discord-channels")
|
||||||
|
async def discord_channels_page(request: Request):
|
||||||
|
if request.cookies.get("auth") != "ok":
|
||||||
|
return RedirectResponse(url="/login")
|
||||||
|
|
||||||
|
from botdiscord.database import get_discord_servers, get_discord_channels
|
||||||
|
|
||||||
|
servers = get_discord_servers()
|
||||||
|
channels = get_discord_channels()
|
||||||
|
|
||||||
|
# Agrupar canales por servidor
|
||||||
|
servers_with_channels = {}
|
||||||
|
for server in servers:
|
||||||
|
server_id = server['server_id']
|
||||||
|
servers_with_channels[server_id] = {
|
||||||
|
'server_info': server,
|
||||||
|
'channels': [ch for ch in channels if ch['server_id'] == server_id]
|
||||||
|
}
|
||||||
|
|
||||||
|
success = request.query_params.get("success") == "1"
|
||||||
|
error = request.query_params.get("error") == "1"
|
||||||
|
synced = request.query_params.get("synced") == "1"
|
||||||
|
|
||||||
|
return templates.TemplateResponse("discord_channels.html", {
|
||||||
|
"request": request,
|
||||||
|
"servers_with_channels": servers_with_channels,
|
||||||
|
"success": success,
|
||||||
|
"error": error,
|
||||||
|
"synced": synced
|
||||||
|
})
|
||||||
|
|
||||||
|
@app.post("/discord-channels/sync")
|
||||||
|
async def sync_discord_servers(request: Request):
|
||||||
|
if request.cookies.get("auth") != "ok":
|
||||||
|
raise HTTPException(status_code=401)
|
||||||
|
|
||||||
|
try:
|
||||||
|
print("[Panel] Iniciando sincronización de Discord...")
|
||||||
|
|
||||||
|
# Diagnóstico de variables de entorno
|
||||||
|
print("[Panel] Variables de entorno disponibles:")
|
||||||
|
discord_env_vars = {k: v for k, v in os.environ.items() if 'DISCORD' in k or 'TOKEN' in k}
|
||||||
|
for key, value in discord_env_vars.items():
|
||||||
|
print(f" {key}: {'***' if 'TOKEN' in key else value}")
|
||||||
|
|
||||||
|
# Importar y usar el módulo de sincronización
|
||||||
|
from botdiscord.server_sync import sync_discord_servers_from_api
|
||||||
|
result = await sync_discord_servers_from_api()
|
||||||
|
|
||||||
|
print(f"[Panel] Resultado de sincronización: {result}")
|
||||||
|
if result:
|
||||||
|
return RedirectResponse(url="/discord-channels?synced=1", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
|
else:
|
||||||
|
return RedirectResponse(url="/discord-channels?error=1", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[Panel] Error en sincronización: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return RedirectResponse(url="/discord-channels?error=1", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
|
|
||||||
|
@app.post("/discord-channels/toggle")
|
||||||
|
async def toggle_discord_channel(request: Request):
|
||||||
|
if request.cookies.get("auth") != "ok":
|
||||||
|
raise HTTPException(status_code=401)
|
||||||
|
|
||||||
|
form = await request.form()
|
||||||
|
channel_id = form.get("channel_id")
|
||||||
|
is_active = form.get("is_active") == "true"
|
||||||
|
|
||||||
|
if not channel_id:
|
||||||
|
return RedirectResponse(url="/discord-channels?error=1", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from botdiscord.database import toggle_channel_status
|
||||||
|
toggle_channel_status(int(channel_id), is_active)
|
||||||
|
return RedirectResponse(url="/discord-channels?success=1", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error toggling channel: {e}")
|
||||||
|
return RedirectResponse(url="/discord-channels?error=1", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
|
|
||||||
|
@app.post("/discord-channels/delete-server")
|
||||||
|
async def delete_discord_server(request: Request):
|
||||||
|
if request.cookies.get("auth") != "ok":
|
||||||
|
raise HTTPException(status_code=401)
|
||||||
|
|
||||||
|
form = await request.form()
|
||||||
|
server_id = form.get("server_id")
|
||||||
|
|
||||||
|
if not server_id:
|
||||||
|
return RedirectResponse(url="/discord-channels?error=1", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from botdiscord.database import delete_discord_server
|
||||||
|
delete_discord_server(int(server_id))
|
||||||
|
return RedirectResponse(url="/discord-channels?success=1", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error deleting server: {e}")
|
||||||
|
return RedirectResponse(url="/discord-channels?error=1", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
|
|
||||||
|
@app.post("/discord-channels/reload-bot")
|
||||||
|
async def reload_discord_bot(request: Request):
|
||||||
|
if request.cookies.get("auth") != "ok":
|
||||||
|
raise HTTPException(status_code=401)
|
||||||
|
|
||||||
|
try:
|
||||||
|
print("[Panel] Enviando señal de recarga al bot de Discord...")
|
||||||
|
|
||||||
|
# Importar y ejecutar la función de recarga
|
||||||
|
from botdiscord.bot_reload import reload_bot_config
|
||||||
|
result = await reload_bot_config()
|
||||||
|
|
||||||
|
if result:
|
||||||
|
return RedirectResponse(url="/discord-channels?success=1", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
|
else:
|
||||||
|
return RedirectResponse(url="/discord-channels?error=1", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[Panel] Error recargando bot: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return RedirectResponse(url="/discord-channels?error=1", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
|
|
||||||
|
@app.post("/discord-channels/bulk-toggle")
|
||||||
|
async def bulk_toggle_discord_channels(request: Request):
|
||||||
|
if request.cookies.get("auth") != "ok":
|
||||||
|
raise HTTPException(status_code=401)
|
||||||
|
|
||||||
|
form = await request.form()
|
||||||
|
channel_ids = form.getlist("channel_ids")
|
||||||
|
bulk_action = form.get("bulk_action")
|
||||||
|
|
||||||
|
if not channel_ids or not bulk_action:
|
||||||
|
return RedirectResponse(url="/discord-channels?error=1", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from botdiscord.database import toggle_channel_status
|
||||||
|
|
||||||
|
is_active = (bulk_action == "activate")
|
||||||
|
success_count = 0
|
||||||
|
|
||||||
|
for channel_id_str in channel_ids:
|
||||||
|
try:
|
||||||
|
channel_id = int(channel_id_str)
|
||||||
|
toggle_channel_status(channel_id, is_active)
|
||||||
|
success_count += 1
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f"[Panel] Bulk toggle: {success_count} canales {'activados' if is_active else 'desactivados'}")
|
||||||
|
return RedirectResponse(url="/discord-channels?success=1", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[Panel] Error en bulk toggle: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
return RedirectResponse(url="/discord-channels?error=1", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
|
|
||||||
|
@app.get("/diagnosis")
|
||||||
|
async def diagnosis_page(request: Request):
|
||||||
|
if request.cookies.get("auth") != "ok":
|
||||||
|
return RedirectResponse(url="/login")
|
||||||
|
|
||||||
|
# Obtener variables de entorno relevantes
|
||||||
|
env_vars = {
|
||||||
|
'DISCORD_TOKEN': os.getenv('DISCORD_TOKEN'),
|
||||||
|
'TELEGRAM_TOKEN': os.getenv('TELEGRAM_TOKEN'),
|
||||||
|
'LIBRETRANSLATE_URL': os.getenv('LIBRETRANSLATE_URL'),
|
||||||
|
'DB_TYPE': os.getenv('DB_TYPE'),
|
||||||
|
'DB_HOST': os.getenv('DB_HOST'),
|
||||||
|
'DB_PORT': os.getenv('DB_PORT'),
|
||||||
|
'DB_NAME': os.getenv('DB_NAME'),
|
||||||
|
'DB_USER': os.getenv('DB_USER')
|
||||||
|
}
|
||||||
|
|
||||||
|
# Obtener configuración cargada
|
||||||
|
config = get_config()
|
||||||
|
|
||||||
|
return templates.TemplateResponse("diagnosis.html", {
|
||||||
|
"request": request,
|
||||||
|
"env_vars": env_vars,
|
||||||
|
"config": config
|
||||||
|
})
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
web_config = get_web_config()
|
web_config = get_web_config()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{% set lang = request.cookies.get('panel_lang', 'es') %}
|
{% set lang = request.cookies.get('panel_lang', 'es') %}
|
||||||
|
{% set is_admin = username == 'nickpons666' %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{ lang }}">
|
<html lang="{{ lang }}">
|
||||||
<head>
|
<head>
|
||||||
@@ -48,17 +49,17 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">{{ "Token de Discord" | translate(lang) }}</label>
|
<label class="form-label">{{ "Token de Discord" | translate(lang) }}</label>
|
||||||
<input type="text" class="form-control"
|
<input type="text" class="form-control"
|
||||||
value="{{ config.discord.token }}" readonly>
|
value="{{ config.discord.token if is_admin else '****************************************' }}" readonly>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">{{ "Token de Telegram" | translate(lang) }}</label>
|
<label class="form-label">{{ "Token de Telegram" | translate(lang) }}</label>
|
||||||
<input type="text" class="form-control"
|
<input type="text" class="form-control"
|
||||||
value="{{ config.telegram.token }}" readonly>
|
value="{{ config.telegram.token if is_admin else '****************************************' }}" readonly>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">{{ "URL de LibreTranslate" | translate(lang) }}</label>
|
<label class="form-label">{{ "URL de LibreTranslate" | translate(lang) }}</label>
|
||||||
<input type="text" class="form-control"
|
<input type="text" class="form-control"
|
||||||
value="{{ config.libretranslate.url }}" readonly>
|
value="{{ config.libretranslate.url if is_admin else '****************************************' }}" readonly>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -107,28 +108,28 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">{{ "Host MySQL" | translate(lang) }}</label>
|
<label class="form-label">{{ "Host MySQL" | translate(lang) }}</label>
|
||||||
<input type="text" class="form-control"
|
<input type="text" class="form-control"
|
||||||
value="{{ config.database.host }}" readonly>
|
value="{{ config.database.host if is_admin else '****************************************' }}" readonly>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">{{ "Puerto MySQL" | translate(lang) }}</label>
|
<label class="form-label">{{ "Puerto MySQL" | translate(lang) }}</label>
|
||||||
<input type="number" class="form-control"
|
<input type="number" class="form-control"
|
||||||
value="{{ config.database.port }}" readonly>
|
value="{{ config.database.port if is_admin else '****************************************' }}" readonly>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">{{ "Usuario MySQL" | translate(lang) }}</label>
|
<label class="form-label">{{ "Usuario MySQL" | translate(lang) }}</label>
|
||||||
<input type="text" class="form-control"
|
<input type="text" class="form-control"
|
||||||
value="{{ config.database.user }}" readonly>
|
value="{{ config.database.user if is_admin else '****************************************' }}" readonly>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">{{ "Nombre de Base de Datos" | translate(lang) }}</label>
|
<label class="form-label">{{ "Nombre de Base de Datos" | translate(lang) }}</label>
|
||||||
<input type="text" class="form-control"
|
<input type="text" class="form-control"
|
||||||
value="{{ config.database.name }}" readonly>
|
value="{{ config.database.name if is_admin else '****************************************' }}" readonly>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">{{ "Ruta de la base de datos" | translate(lang) }}</label>
|
<label class="form-label">{{ "Ruta de la base de datos" | translate(lang) }}</label>
|
||||||
<input type="text" class="form-control"
|
<input type="text" class="form-control"
|
||||||
value="{{ config.database.path }}" readonly>
|
value="{{ config.database.path if is_admin else '****************************************' }}" readonly>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -77,6 +77,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card text-white bg-warning mb-3">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title"><i class="bi bi-hash"></i> {{ "Canales de Discord" | translate(lang) }}</h5>
|
||||||
|
<p class="card-text">{{ "Administrar canales habilitados para traducción" | translate(lang) }}</p>
|
||||||
|
<a href="/discord-channels" class="btn btn-dark btn-sm">{{ "Administrar" | translate(lang) }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card mt-4">
|
<div class="card mt-4">
|
||||||
|
|||||||
270
panel/templates/discord_channels.html
Normal file
270
panel/templates/discord_channels.html
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
{% set lang = request.cookies.get('panel_lang', 'es') %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="{{ lang }}">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{ "Canales de Discord - Bots de Traducción" | translate(lang) }}</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
|
||||||
|
<style>
|
||||||
|
.channel-item {
|
||||||
|
border-left: 4px solid #6f42c1;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
.channel-item:hover {
|
||||||
|
border-left-color: #563d7c;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
.channel-inactive {
|
||||||
|
border-left-color: #dc3545;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
.server-card {
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.server-header {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
}
|
||||||
|
.toggle-switch {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 50px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.toggle-switch input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
.slider {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #ccc;
|
||||||
|
transition: .4s;
|
||||||
|
border-radius: 24px;
|
||||||
|
}
|
||||||
|
.slider:before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
left: 3px;
|
||||||
|
bottom: 3px;
|
||||||
|
background-color: white;
|
||||||
|
transition: .4s;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
input:checked + .slider {
|
||||||
|
background-color: #28a745;
|
||||||
|
}
|
||||||
|
input:checked + .slider:before {
|
||||||
|
transform: translateX(26px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="/dashboard">
|
||||||
|
<i class="bi bi-translate"></i> {{ "Bots de Traducción" | translate(lang) }}
|
||||||
|
</a>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="dropdown me-3">
|
||||||
|
<button class="btn btn-outline-light btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
||||||
|
<i class="bi bi-translate"></i>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
|
<li><a class="dropdown-item {{ 'active' if lang == 'es' }}" href="/set-lang/es">Español</a></li>
|
||||||
|
<li><a class="dropdown-item {{ 'active' if lang == 'en' }}" href="/set-lang/en">English</a></li>
|
||||||
|
<li><a class="dropdown-item {{ 'active' if lang == 'pt' }}" href="/set-lang/pt">Português</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<a href="/dashboard" class="btn btn-outline-light btn-sm me-2">{{ "Dashboard" | translate(lang) }}</a>
|
||||||
|
<a href="/logout" class="btn btn-outline-light btn-sm">{{ "Cerrar Sesión" | translate(lang) }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2>📱 {{ "Canales de Discord" | translate(lang) }}</h2>
|
||||||
|
<div>
|
||||||
|
<a href="/diagnosis" class="btn btn-outline-secondary me-2">
|
||||||
|
<i class="bi bi-bug"></i> {{ "Diagnóstico" | translate(lang) }}
|
||||||
|
</a>
|
||||||
|
<form method="post" action="/discord-channels/sync" class="d-inline">
|
||||||
|
<button type="submit" class="btn btn-primary" onclick="return confirm('{{ '¿Sincronizar servidores y canales desde Discord?' | translate(lang) }}')">
|
||||||
|
<i class="bi bi-arrow-repeat"></i> {{ "Sincronizar" | translate(lang) }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if success %}
|
||||||
|
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||||
|
<i class="bi bi-check-circle"></i> {{ "Configuración actualizada correctamente." | translate(lang) }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if synced %}
|
||||||
|
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||||
|
<i class="bi bi-check-circle"></i> {{ "Servidores y canales sincronizados desde Discord." | translate(lang) }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if error %}
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
|
<i class="bi bi-exclamation-circle"></i> {{ "Error al realizar la operación. Verifica la configuración del bot." | translate(lang) }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if servers_with_channels|length == 0 %}
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="bi bi-info-circle"></i> {{ "No hay servidores sincronizados. Usa el botón 'Sincronizar' para obtener los servidores y canales de Discord." | translate(lang) }}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{% for server_id, server_data in servers_with_channels.items() %}
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header bg-dark text-white">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="bi bi-server"></i> {{ server_data.server_info.server_name }}
|
||||||
|
<small class="text-muted">({{ server_data.server_info.server_id }})</small>
|
||||||
|
</h5>
|
||||||
|
<form method="post" action="/discord-channels/delete-server" class="d-inline">
|
||||||
|
<input type="hidden" name="server_id" value="{{ server_id }}">
|
||||||
|
<button type="submit" class="btn btn-sm btn-outline-danger"
|
||||||
|
onclick="return confirm('{{ '¿Eliminar este servidor y todos sus canales?' | translate(lang) }}')">
|
||||||
|
<i class="bi bi-trash"></i> {{ "Eliminar" | translate(lang) }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
{% if server_data.channels|length == 0 %}
|
||||||
|
<p class="text-muted">{{ "No hay canales en este servidor." | translate(lang) }}</p>
|
||||||
|
{% else %}
|
||||||
|
<form method="post" action="/discord-channels/bulk-toggle">
|
||||||
|
<input type="hidden" name="server_id" value="{{ server_id }}">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="50px">
|
||||||
|
<input type="checkbox" class="form-check-input" id="selectAll_{{ server_id }}"
|
||||||
|
onchange="toggleAllChannels({{ server_id }})">
|
||||||
|
</th>
|
||||||
|
<th>{{ "Canal" | translate(lang) }}</th>
|
||||||
|
<th>{{ "ID" | translate(lang) }}</th>
|
||||||
|
<th width="100px">{{ "Estado" | translate(lang) }}</th>
|
||||||
|
<th width="120px">{{ "Acciones" | translate(lang) }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for channel in server_data.channels %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" class="form-check-input channel-checkbox"
|
||||||
|
name="channel_ids" value="{{ channel.channel_id }}"
|
||||||
|
data-server="{{ server_id }}">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<i class="bi bi-hash"></i> {{ channel.channel_name }}
|
||||||
|
</td>
|
||||||
|
<td><code>{{ channel.channel_id }}</code></td>
|
||||||
|
<td>
|
||||||
|
{% if channel.is_active %}
|
||||||
|
<span class="badge bg-success">{{ "Activo" | translate(lang) }}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-secondary">{{ "Inactivo" | translate(lang) }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="btn-group btn-group-sm" role="group">
|
||||||
|
<form method="post" action="/discord-channels/toggle" class="d-inline">
|
||||||
|
<input type="hidden" name="channel_id" value="{{ channel.channel_id }}">
|
||||||
|
<input type="hidden" name="is_active" value="{{ 'false' if channel.is_active else 'true' }}">
|
||||||
|
<button type="submit" class="btn {% if channel.is_active %}btn-warning{% else %}btn-success{% endif %} btn-sm"
|
||||||
|
title="{{ 'Desactivar' if channel.is_active else 'Activar' | translate(lang) }}">
|
||||||
|
<i class="bi bi-{% if channel.is_active %}pause{% else %}play{% endif %}"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3">
|
||||||
|
<button type="submit" name="bulk_action" value="activate"
|
||||||
|
class="btn btn-success btn-sm me-2" disabled>
|
||||||
|
<i class="bi bi-play"></i> {{ "Activar seleccionados" | translate(lang) }}
|
||||||
|
</button>
|
||||||
|
<button type="submit" name="bulk_action" value="deactivate"
|
||||||
|
class="btn btn-warning btn-sm" disabled>
|
||||||
|
<i class="bi bi-pause"></i> {{ "Desactivar seleccionados" | translate(lang) }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
function toggleAllChannels(serverId) {
|
||||||
|
const selectAll = document.getElementById('selectAll_' + serverId);
|
||||||
|
const checkboxes = document.querySelectorAll('.channel-checkbox[data-server="' + serverId + '"]');
|
||||||
|
const bulkButtons = selectAll.closest('form').querySelectorAll('button[type="submit"][name="bulk_action"]');
|
||||||
|
|
||||||
|
checkboxes.forEach(checkbox => {
|
||||||
|
checkbox.checked = selectAll.checked;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Habilitar/deshabilitar botones bulk
|
||||||
|
const anyChecked = document.querySelectorAll('.channel-checkbox:checked').length > 0;
|
||||||
|
bulkButtons.forEach(btn => btn.disabled = !anyChecked);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBulkButtons() {
|
||||||
|
const anyChecked = document.querySelectorAll('.channel-checkbox:checked').length > 0;
|
||||||
|
document.querySelectorAll('button[type="submit"][name="bulk_action"]').forEach(btn => {
|
||||||
|
btn.disabled = !anyChecked;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Actualizar checkboxes de "seleccionar todo"
|
||||||
|
document.querySelectorAll('[id^="selectAll_"]').forEach(selectAll => {
|
||||||
|
const serverId = selectAll.id.replace('selectAll_', '');
|
||||||
|
const checkboxes = document.querySelectorAll('.channel-checkbox[data-server="' + serverId + '"]');
|
||||||
|
const allChecked = Array.from(checkboxes).every(cb => cb.checked);
|
||||||
|
const anyChecked = Array.from(checkboxes).some(cb => cb.checked);
|
||||||
|
|
||||||
|
selectAll.checked = allChecked;
|
||||||
|
selectAll.indeterminate = anyChecked && !allChecked;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Agregar event listeners a todos los checkboxes
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
document.querySelectorAll('.channel-checkbox').forEach(checkbox => {
|
||||||
|
checkbox.addEventListener('change', updateBulkButtons);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user