Añadiendo todos los archivos del proyecto (incluidos secretos y venv)
This commit is contained in:
0
panel/__init__.py
Normal file
0
panel/__init__.py
Normal file
BIN
panel/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
panel/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
panel/__pycache__/main.cpython-312.pyc
Normal file
BIN
panel/__pycache__/main.cpython-312.pyc
Normal file
Binary file not shown.
182
panel/main.py
Normal file
182
panel/main.py
Normal file
@@ -0,0 +1,182 @@
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from fastapi import FastAPI, HTTPException, status
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from fastapi import Request
|
||||
from fastapi.responses import RedirectResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
from botdiscord.config import load_config, get_web_config, get_libretranslate_url, get_db_type
|
||||
|
||||
app = FastAPI(title="Panel de Configuración - Bots de Traducción")
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
templates = Jinja2Templates(directory=os.path.join(SCRIPT_DIR, "panel", "templates"))
|
||||
|
||||
class LoginForm(BaseModel):
|
||||
username: str
|
||||
password: str
|
||||
|
||||
def get_config():
|
||||
cfg = load_config()
|
||||
return {
|
||||
"discord": {"token": cfg.get("discord", {}).get("token", "")},
|
||||
"telegram": {"token": cfg.get("telegram", {}).get("token", "")},
|
||||
"libretranslate": {"url": cfg.get("libretranslate", {}).get("url", "")},
|
||||
"web": cfg.get("web", {}),
|
||||
"database": cfg.get("database", {}),
|
||||
"languages": cfg.get("languages", {})
|
||||
}
|
||||
|
||||
def verify_admin(username: str, password: str) -> bool:
|
||||
web_config = get_web_config()
|
||||
return (username == web_config.get("admin_username", "") and
|
||||
password == web_config.get("admin_password", ""))
|
||||
|
||||
@app.get("/")
|
||||
async def root(request: Request):
|
||||
return templates.TemplateResponse("index.html", {"request": request})
|
||||
|
||||
@app.get("/login")
|
||||
async def login_page(request: Request):
|
||||
return templates.TemplateResponse("login.html", {"request": request})
|
||||
|
||||
@app.post("/login")
|
||||
async def login(request: Request):
|
||||
form = await request.form()
|
||||
username = form.get("username", "")
|
||||
password = form.get("password", "")
|
||||
|
||||
if verify_admin(username, password):
|
||||
response = RedirectResponse(url="/dashboard", status_code=status.HTTP_303_SEE_OTHER)
|
||||
response.set_cookie(key="auth", value="ok", httponly=True)
|
||||
return response
|
||||
|
||||
return templates.TemplateResponse("login.html", {
|
||||
"request": request,
|
||||
"error": "Credenciales incorrectas"
|
||||
})
|
||||
|
||||
@app.get("/dashboard")
|
||||
async def dashboard(request: Request):
|
||||
if request.cookies.get("auth") != "ok":
|
||||
return RedirectResponse(url="/login")
|
||||
|
||||
config = get_config()
|
||||
return templates.TemplateResponse("dashboard.html", {
|
||||
"request": request,
|
||||
"config": config
|
||||
})
|
||||
|
||||
@app.get("/config")
|
||||
async def config_page(request: Request):
|
||||
if request.cookies.get("auth") != "ok":
|
||||
return RedirectResponse(url="/login")
|
||||
|
||||
config = get_config()
|
||||
return templates.TemplateResponse("config.html", {
|
||||
"request": request,
|
||||
"config": config
|
||||
})
|
||||
|
||||
@app.get("/languages")
|
||||
async def languages_page(request: Request):
|
||||
if request.cookies.get("auth") != "ok":
|
||||
return RedirectResponse(url="/login")
|
||||
|
||||
from botdiscord.database import get_available_languages, get_bot_languages
|
||||
|
||||
available = get_available_languages()
|
||||
discord_langs = get_bot_languages("discord")
|
||||
telegram_langs = get_bot_languages("telegram")
|
||||
|
||||
return templates.TemplateResponse("languages.html", {
|
||||
"request": request,
|
||||
"available_languages": available,
|
||||
"discord_languages": discord_langs,
|
||||
"telegram_languages": telegram_langs,
|
||||
"libretranslate_url": get_libretranslate_url()
|
||||
})
|
||||
|
||||
@app.post("/languages/sync")
|
||||
async def sync_languages(request: Request):
|
||||
if request.cookies.get("auth") != "ok":
|
||||
raise HTTPException(status_code=401)
|
||||
|
||||
import aiohttp
|
||||
url = get_libretranslate_url()
|
||||
|
||||
if not url:
|
||||
return RedirectResponse(url="/languages?error=1", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
base_url = url.rstrip("/translate").rstrip("/translate/").rstrip("/")
|
||||
if base_url.endswith("/translate"):
|
||||
base_url = base_url[:-10]
|
||||
base_url = base_url.rstrip("/")
|
||||
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(f"{base_url}/languages", timeout=aiohttp.ClientTimeout(total=10)) as resp:
|
||||
if resp.status == 200:
|
||||
languages = await resp.json()
|
||||
from botdiscord.database import get_available_languages, set_available_languages
|
||||
|
||||
existing = {lang["code"]: lang.get("flag", "") for lang in get_available_languages()}
|
||||
|
||||
for lang in languages:
|
||||
lang["flag"] = existing.get(lang["code"], "")
|
||||
|
||||
set_available_languages(languages)
|
||||
return RedirectResponse(url="/languages?synced=1", status_code=status.HTTP_303_SEE_OTHER)
|
||||
else:
|
||||
return RedirectResponse(url="/languages?error=1", status_code=status.HTTP_303_SEE_OTHER)
|
||||
except Exception as e:
|
||||
print(f"Error syncing languages: {e}")
|
||||
return RedirectResponse(url="/languages?error=1", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
@app.post("/languages/bot")
|
||||
async def update_bot_languages(request: Request):
|
||||
if request.cookies.get("auth") != "ok":
|
||||
raise HTTPException(status_code=401)
|
||||
|
||||
form = await request.form()
|
||||
bot_type = form.get("bot_type", "discord")
|
||||
lang_codes = form.getlist("lang_codes")
|
||||
|
||||
from botdiscord.database import set_bot_languages
|
||||
set_bot_languages(bot_type, lang_codes)
|
||||
|
||||
return RedirectResponse(url="/languages?success=1", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
@app.post("/languages/flags")
|
||||
async def update_language_flags(request: Request):
|
||||
if request.cookies.get("auth") != "ok":
|
||||
raise HTTPException(status_code=401)
|
||||
|
||||
from botdiscord.database import get_available_languages, set_available_languages
|
||||
|
||||
available = get_available_languages()
|
||||
form = await request.form()
|
||||
|
||||
for lang in available:
|
||||
flag_key = f"flag_{lang['code']}"
|
||||
lang['flag'] = form.get(flag_key, "")
|
||||
|
||||
set_available_languages(available)
|
||||
|
||||
return RedirectResponse(url="/languages?success=1", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
@app.get("/logout")
|
||||
async def logout():
|
||||
response = RedirectResponse(url="/login", status_code=status.HTTP_303_SEE_OTHER)
|
||||
response.delete_cookie("auth")
|
||||
return response
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
web_config = get_web_config()
|
||||
uvicorn.run(app, host=web_config.get("host", "0.0.0.0"), port=web_config.get("port", 8000))
|
||||
129
panel/templates/config.html
Normal file
129
panel/templates/config.html
Normal file
@@ -0,0 +1,129 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Configuración - Bots de Traducción</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">
|
||||
</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
|
||||
</a>
|
||||
<div class="d-flex">
|
||||
<a href="/dashboard" class="btn btn-outline-light btn-sm me-2">Dashboard</a>
|
||||
<a href="/logout" class="btn btn-outline-light btn-sm">Cerrar Sesión</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-5">
|
||||
<h2 class="mb-4">⚙️ Configuración</h2>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i> La configuración se lee desde las variables de entorno de Docker.
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-discord"></i> Tokens de Bots</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Token de Discord</label>
|
||||
<input type="text" class="form-control"
|
||||
value="{{ config.discord.token }}" readonly>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Token de Telegram</label>
|
||||
<input type="text" class="form-control"
|
||||
value="{{ config.telegram.token }}" readonly>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">URL de LibreTranslate</label>
|
||||
<input type="text" class="form-control"
|
||||
value="{{ config.libretranslate.url }}" readonly>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-globe"></i> Panel Web</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Usuario admin</label>
|
||||
<input type="text" class="form-control"
|
||||
value="{{ config.web.admin_username }}" readonly>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Host</label>
|
||||
<input type="text" class="form-control"
|
||||
value="{{ config.web.host }}" readonly>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Puerto</label>
|
||||
<input type="number" class="form-control"
|
||||
value="{{ config.web.port }}" readonly>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-database"></i> Base de Datos</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Tipo de Base de Datos</label>
|
||||
<input type="text" class="form-control"
|
||||
value="{{ config.database.type|upper }}" readonly>
|
||||
</div>
|
||||
|
||||
{% if config.database.type == 'mysql' %}
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Host MySQL</label>
|
||||
<input type="text" class="form-control"
|
||||
value="{{ config.database.host }}" readonly>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Puerto MySQL</label>
|
||||
<input type="number" class="form-control"
|
||||
value="{{ config.database.port }}" readonly>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Usuario MySQL</label>
|
||||
<input type="text" class="form-control"
|
||||
value="{{ config.database.user }}" readonly>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Nombre de Base de Datos</label>
|
||||
<input type="text" class="form-control"
|
||||
value="{{ config.database.name }}" readonly>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Ruta de la base de datos</label>
|
||||
<input type="text" class="form-control"
|
||||
value="{{ config.database.path }}" readonly>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
107
panel/templates/dashboard.html
Normal file
107
panel/templates/dashboard.html
Normal file
@@ -0,0 +1,107 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Dashboard - Bots de Traducción</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">
|
||||
</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
|
||||
</a>
|
||||
<div class="d-flex">
|
||||
<a href="/logout" class="btn btn-outline-light btn-sm">Cerrar Sesión</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-5">
|
||||
<h2 class="mb-4">📊 Dashboard</h2>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="card text-white bg-primary mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="bi bi-discord"></i> Discord</h5>
|
||||
<p class="card-text">Bot de traducción para servidores de Discord</p>
|
||||
<a href="/config" class="btn btn-light btn-sm">Configurar</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card text-white bg-info mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="bi bi-telegram"></i> Telegram</h5>
|
||||
<p class="card-text">Bot de traducción para grupos de Telegram</p>
|
||||
<a href="/config" class="btn btn-light btn-sm">Configurar</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card text-white bg-success mb-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><i class="bi bi-globe"></i> Idiomas</h5>
|
||||
<p class="card-text">Administrar idiomas disponibles</p>
|
||||
<a href="/languages" class="btn btn-light btn-sm">Administrar</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">📋 Estado de Configuración</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>Token Discord</th>
|
||||
<td>{{ '✅ Configurado' if config.discord.token and config.discord.token != 'TU_DISCORD_BOT_TOKEN' else '❌ No configurado' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Token Telegram</th>
|
||||
<td>{{ '✅ Configurado' if config.telegram.token and config.telegram.token != 'TU_TELEGRAM_BOT_TOKEN' else '❌ No configurado' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>LibreTranslate URL</th>
|
||||
<td>{{ config.libretranslate.url if config.libretranslate.url else '❌ No configurado' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Idiomas activos</th>
|
||||
<td>{{ config.languages.enabled|length }} idiomas</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Tipo de Base de Datos</th>
|
||||
<td>{{ config.database.type|upper }}</td>
|
||||
</tr>
|
||||
{% if config.database.type == 'mysql' %}
|
||||
<tr>
|
||||
<th>Host MySQL</th>
|
||||
<td>{{ config.database.host }}:{{ config.database.port }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Base de Datos</th>
|
||||
<td>{{ config.database.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Usuario MySQL</th>
|
||||
<td>{{ config.database.user }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<th>Ruta SQLite</th>
|
||||
<td>{{ config.database.path }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
19
panel/templates/index.html
Normal file
19
panel/templates/index.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Bots de Traducción</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container text-center text-white" style="padding-top: 100px;">
|
||||
<h1 class="display-4">🤖 Bots de Traducción</h1>
|
||||
<p class="lead">Panel de configuración para Discord y Telegram</p>
|
||||
<a href="/login" class="btn btn-light btn-lg mt-3">Iniciar Sesión</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
248
panel/templates/languages.html
Normal file
248
panel/templates/languages.html
Normal file
@@ -0,0 +1,248 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Idiomas - Bots de Traducción</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">
|
||||
</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
|
||||
</a>
|
||||
<div class="d-flex">
|
||||
<a href="/dashboard" class="btn btn-outline-light btn-sm me-2">Dashboard</a>
|
||||
<a href="/logout" class="btn btn-outline-light btn-sm">Cerrar Sesión</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="modal fade" id="flagPicker" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Seleccionar Bandera</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row g-1" id="flagGrid"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container mt-5">
|
||||
<h2 class="mb-4">🌍 Gestión de Idiomas</h2>
|
||||
|
||||
{% if request.query_params.get('success') %}
|
||||
<div class="alert alert-success">Configuración guardada correctamente.</div>
|
||||
{% endif %}
|
||||
{% if request.query_params.get('synced') %}
|
||||
<div class="alert alert-success">Idiomas sincronizados desde LibreTranslate.</div>
|
||||
{% endif %}
|
||||
{% if request.query_params.get('error') %}
|
||||
<div class="alert alert-danger">Error al sincronizar. Verifica la URL de LibreTranslate.</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0"><i class="bi bi-cloud-download"></i> Sincronizar con LibreTranslate</h5>
|
||||
<form method="post" action="/languages/sync">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-arrow-repeat"></i> Sincronizar idiomas
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted">URL de LibreTranslate: <code>{{ libretranslate_url }}</code></p>
|
||||
<p class="text-muted"> Idiomas disponibles: {{ available_languages|length }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if available_languages|length > 0 %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">🚩 Editar Banderas</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" action="/languages/flags">
|
||||
<p class="text-muted">Haz clic en el botón de bandera para seleccionar una, o escribe directamente el emoji.</p>
|
||||
<div class="row">
|
||||
{% for lang in available_languages %}
|
||||
<div class="col-md-4 col-sm-6 mb-3">
|
||||
<div class="input-group">
|
||||
<button type="button" class="btn btn-outline-secondary flag-btn"
|
||||
data-target="flag_{{ lang.code }}"
|
||||
style="font-size: 1.5rem;">
|
||||
{{ lang.flag if lang.flag else '🏳️' }}
|
||||
</button>
|
||||
<input type="text" class="form-control"
|
||||
name="flag_{{ lang.code }}"
|
||||
id="flag_{{ lang.code }}"
|
||||
value="{{ lang.flag }}"
|
||||
placeholder="🇪🇸"
|
||||
maxlength="10">
|
||||
<span class="input-group-text">{{ lang.code }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<button type="submit" class="btn btn-warning mt-2">
|
||||
<i class="bi bi-flag"></i> Guardar Banderas
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0"><i class="bi bi-discord"></i> Idiomas para Discord</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" action="/languages/bot">
|
||||
<input type="hidden" name="bot_type" value="discord">
|
||||
{% if available_languages|length == 0 %}
|
||||
<p class="text-muted">No hay idiomas disponibles. Sincroniza primero.</p>
|
||||
{% else %}
|
||||
{% for lang in available_languages %}
|
||||
<div class="form-check d-flex align-items-center">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
name="lang_codes" value="{{ lang.code }}"
|
||||
id="discord_{{ lang.code }}"
|
||||
{% if lang.code in discord_languages %}checked{% endif %}>
|
||||
<label class="form-check-label ms-2" for="discord_{{ lang.code }}" style="font-size: 1.5rem;">
|
||||
{{ lang.flag if lang.flag else '🏳️' }}
|
||||
</label>
|
||||
<span class="ms-2">{{ lang.name }} ({{ lang.code }})</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<button type="submit" class="btn btn-primary mt-3">Guardar para Discord</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h5 class="mb-0"><i class="bi bi-telegram"></i> Idiomas para Telegram</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" action="/languages/bot">
|
||||
<input type="hidden" name="bot_type" value="telegram">
|
||||
{% if available_languages|length == 0 %}
|
||||
<p class="text-muted">No hay idiomas disponibles. Sincroniza primero.</p>
|
||||
{% else %}
|
||||
{% for lang in available_languages %}
|
||||
<div class="form-check d-flex align-items-center">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
name="lang_codes" value="{{ lang.code }}"
|
||||
id="telegram_{{ lang.code }}"
|
||||
{% if lang.code in telegram_languages %}checked{% endif %}>
|
||||
<label class="form-check-label ms-2" for="telegram_{{ lang.code }}" style="font-size: 1.5rem;">
|
||||
{{ lang.flag if lang.flag else '🏳️' }}
|
||||
</label>
|
||||
<span class="ms-2">{{ lang.name }} ({{ lang.code }})</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<button type="submit" class="btn btn-info mt-3 text-white">Guardar para Telegram</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if available_languages|length > 0 %}
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">📋 Idiomas disponibles en LibreTranslate</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Bandera</th>
|
||||
<th>Código</th>
|
||||
<th>Nombre</th>
|
||||
<th>Discord</th>
|
||||
<th>Telegram</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for lang in available_languages %}
|
||||
<tr>
|
||||
<td style="font-size: 1.5rem;">{{ lang.flag if lang.flag else '🏳️' }}</td>
|
||||
<td><code>{{ lang.code }}</code></td>
|
||||
<td>{{ lang.name }}</td>
|
||||
<td>
|
||||
{% if lang.code in discord_languages %}
|
||||
<i class="bi bi-check-circle-fill text-success"></i>
|
||||
{% else %}
|
||||
<i class="bi bi-x-circle text-secondary"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if lang.code in telegram_languages %}
|
||||
<i class="bi bi-check-circle-fill text-success"></i>
|
||||
{% else %}
|
||||
<i class="bi bi-x-circle text-secondary"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
const flags = [
|
||||
'🇬🇧','🇺🇸','🇪🇸','🇫🇷','🇩🇪','🇮🇹','🇵🇹','🇧🇷','🇷🇺','🇯🇵','🇰🇷','🇨🇳',
|
||||
'🇦🇪','🇸🇦','🇮🇳','🇹🇭','🇻🇳','🇮🇩','🇵🇭','🇲🇾','🇸🇬','🇦🇺','🇳🇿','🇨🇦',
|
||||
'🇲🇽','🇦🇷','🇨🇴','🇨🇱','🇵🇪','🇻🇪','🇵🇾','🇺🇾','🇧🇴','🇪🇨','🇵🇦','🇨🇷',
|
||||
'🇨🇺','🇩🇴','🇭🇳','🇸🇻','🇬🇹','🇳🇮','🇱🇧','🇵🇸','🇯🇲','🇹🇹','🇧🇧','🇩🇲',
|
||||
'🇪🇪','🇱🇻','🇱🇹','🇺🇦','🇧🇾','🇰🇿','🇦🇲','🇬🇪','🇦🇿','🇰🇬','🇹🇯','🇹🇲',
|
||||
'🇭🇷','🇷🇸','🇸🇮','🇲🇪','🇦🇱','🇲🇰','🇧🇦','🇲🇷','🇱🇾','🇹🇳','🇸🇳','🇲🇱'
|
||||
];
|
||||
|
||||
let currentTarget = null;
|
||||
const modal = new bootstrap.Modal(document.getElementById('flagPicker'));
|
||||
const flagGrid = document.getElementById('flagGrid');
|
||||
|
||||
flags.forEach(flag => {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'btn btn-outline-secondary col-2';
|
||||
btn.style.fontSize = '1.5rem';
|
||||
btn.textContent = flag;
|
||||
btn.onclick = () => {
|
||||
if (currentTarget) {
|
||||
document.getElementById(currentTarget).value = flag;
|
||||
const btn = document.querySelector(`button[data-target="${currentTarget}"]`);
|
||||
btn.textContent = flag;
|
||||
}
|
||||
modal.hide();
|
||||
};
|
||||
flagGrid.appendChild(btn);
|
||||
});
|
||||
|
||||
document.querySelectorAll('.flag-btn').forEach(btn => {
|
||||
btn.onclick = () => {
|
||||
currentTarget = btn.getAttribute('data-target');
|
||||
modal.show();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
36
panel/templates/login.html
Normal file
36
panel/templates/login.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Bots de Traducción - Login</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; }
|
||||
.login-card { max-width: 400px; margin: 100px auto; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="card login-card shadow">
|
||||
<div class="card-body">
|
||||
<h3 class="text-center mb-4">🔐 Panel de Configuración</h3>
|
||||
{% if error %}
|
||||
<div class="alert alert-danger">{{ error }}</div>
|
||||
{% endif %}
|
||||
<form method="post">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Usuario</label>
|
||||
<input type="text" name="username" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Contraseña</label>
|
||||
<input type="password" name="password" class="form-control" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">Iniciar Sesión</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user