Implementar tema claro/oscuro con Bootstrap 5
- Agregar atributo data-bs-theme al HTML - Implementar botón toggle con íconos sol/luna en navegación - Agregar estilos CSS para modo oscuro (variables, componentes, tablas) - Implementar JavaScript para funcionalidad toggle con persistencia localStorage - Agregar detección automática del tema del sistema - Fix específico para columna "Contenido (Previo)" en sent_messages.php - Mejorar Content Security Policy para archivos .map de Bootstrap - Configuración de entorno automática para .env.pruebas Características: - Toggle claro/oscuro con persistencia - Detección automática de preferencias del sistema - Estilos personalizados para componentes en modo oscuro - Compatibilidad con todas las páginas del sistema
This commit is contained in:
422
assets/css/style.css
Executable file
422
assets/css/style.css
Executable file
@@ -0,0 +1,422 @@
|
||||
/* Theme Toggle Styles */
|
||||
.theme-toggle-btn {
|
||||
color: #f8f9fa !important; /* Light color for the icon */
|
||||
border: 1px solid #f8f9fa !important; /* Light color for the border */
|
||||
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.theme-toggle-btn:hover {
|
||||
background-color: #f8f9fa;
|
||||
color: #212529 !important; /* Dark color for icon on hover */
|
||||
}
|
||||
|
||||
/* General Styles */
|
||||
body {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Dark Mode Variables */
|
||||
[data-bs-theme="dark"] {
|
||||
--bs-body-bg: #1a1a1a;
|
||||
--bs-body-color: #e9ecef;
|
||||
--bs-border-color: #495057;
|
||||
}
|
||||
|
||||
|
||||
|
||||
[data-bs-theme="dark"] #theme-toggle:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
/* Custom dark mode adjustments */
|
||||
[data-bs-theme="dark"] .message.in {
|
||||
background-color: #2d3748;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .message.out {
|
||||
background-color: #2563eb;
|
||||
color: white;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .card {
|
||||
background-color: #2d3748;
|
||||
border-color: #4a5568;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .table {
|
||||
color: #e9ecef;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .form-control {
|
||||
background-color: #2d3748;
|
||||
border-color: #4a5568;
|
||||
color: #e9ecef;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .form-control:focus {
|
||||
background-color: #2d3748;
|
||||
border-color: #2563eb;
|
||||
color: #e9ecef;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .navbar {
|
||||
background-color: #1a1a1a !important;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .dropdown-menu {
|
||||
background-color: #2d3748;
|
||||
border-color: #4a5568;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .dropdown-item {
|
||||
color: #e9ecef;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .dropdown-item:hover {
|
||||
background-color: #4a5568;
|
||||
color: #e9ecef;
|
||||
}
|
||||
|
||||
/* Fix for message preview in dark mode */
|
||||
[data-bs-theme="dark"] .message-preview {
|
||||
color: #e9ecef !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* More specific fix for table cells in dark mode */
|
||||
[data-bs-theme="dark"] .table td {
|
||||
color: #e9ecef !important;
|
||||
border-color: #4a5568 !important;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .table tbody tr {
|
||||
background-color: #2d3748 !important;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .table tbody tr:nth-child(odd) {
|
||||
background-color: #1a202c !important;
|
||||
}
|
||||
|
||||
/* Universal fix for all text in dark mode tables */
|
||||
[data-bs-theme="dark"] .table * {
|
||||
color: #e9ecef !important;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .table .text-muted {
|
||||
color: #a0aec0 !important;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .table .badge {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
/* Fix for card container in dark mode */
|
||||
[data-bs-theme="dark"] .card-body {
|
||||
background-color: #2d3748 !important;
|
||||
color: #e9ecef !important;
|
||||
}
|
||||
|
||||
/* Table container fix */
|
||||
[data-bs-theme="dark"] .table-responsive {
|
||||
background-color: #2d3748 !important;
|
||||
}
|
||||
|
||||
/* Specific fix for text content in dark mode */
|
||||
[data-bs-theme="dark"] .text-content {
|
||||
color: #212529 !important;
|
||||
background-color: #f8f9fa !important;
|
||||
padding: 8px !important;
|
||||
border-radius: 4px !important;
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] td.text-break {
|
||||
color: #212529 !important;
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
/* Alternative: Force dark theme for all table cells */
|
||||
[data-bs-theme="dark"] .table td:nth-child(3) {
|
||||
color: #212529 !important;
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .table td:nth-child(3) div {
|
||||
color: #212529 !important;
|
||||
background-color: #f8f9fa !important;
|
||||
}
|
||||
|
||||
/* FIX FOR MESSAGE PREVIEW - MOST SPECIFIC SELECTORS */
|
||||
html[data-bs-theme="dark"] body .container-fluid .card .card-body .table-responsive .table tbody tr td:nth-child(3),
|
||||
html[data-bs-theme="dark"] .table td:nth-child(3),
|
||||
body[data-bs-theme="dark"] .table td:nth-child(3) {
|
||||
background-color: #2d3748 !important;
|
||||
border-color: #4a5568 !important;
|
||||
color: #e9ecef !important;
|
||||
}
|
||||
|
||||
html[data-bs-theme="dark"] body .container-fluid .card .card-body .table-responsive .table tbody tr td:nth-child(3) .message-preview,
|
||||
html[data-bs-theme="dark"] .table td:nth-child(3) .message-preview {
|
||||
background-color: #374151 !important;
|
||||
color: #f3f4f6 !important;
|
||||
padding: 8px !important;
|
||||
border-radius: 4px !important;
|
||||
display: inline-block !important;
|
||||
border: 1px solid #4a5568 !important;
|
||||
}
|
||||
|
||||
/* Override any conflicting styles */
|
||||
html[data-bs-theme="dark"] td:nth-child(3) *,
|
||||
html[data-bs-theme="dark"] td:nth-child(3) div,
|
||||
html[data-bs-theme="dark"] td:nth-child(3) span {
|
||||
color: #f3f4f6 !important;
|
||||
}
|
||||
|
||||
/* ULTRA SPECIFIC - Target the exact table */
|
||||
#sent-messages-table[data-bs-theme="dark"] .table td:nth-child(3),
|
||||
#sent-messages-table .table td:nth-child(3)[data-bs-theme="dark"],
|
||||
.card[data-bs-theme="dark"] .table td:nth-child(3) {
|
||||
background: #2d3748 !important;
|
||||
color: #f3f4f6 !important;
|
||||
}
|
||||
|
||||
#sent-messages-table[data-bs-theme="dark"] .table td:nth-child(3) .message-preview,
|
||||
#sent-messages-table .table td:nth-child(3)[data-bs-theme="dark"] .message-preview {
|
||||
background: #374151 !important;
|
||||
color: #ffffff !important;
|
||||
padding: 8px !important;
|
||||
border-radius: 4px !important;
|
||||
border: 1px solid #4a5568 !important;
|
||||
}
|
||||
|
||||
/* Additional dark mode fixes */
|
||||
[data-bs-theme="dark"] .table-light {
|
||||
background-color: #2d3748 !important;
|
||||
color: #e9ecef !important;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .table-hover tbody tr:hover {
|
||||
background-color: #4a5568 !important;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .text-muted {
|
||||
color: #a0aec0 !important;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .badge {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .badge.bg-info {
|
||||
background-color: #3182ce !important;
|
||||
}
|
||||
|
||||
/* Estilos para el chat */
|
||||
#chat-history {
|
||||
height: 70vh;
|
||||
overflow-y: auto;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.message {
|
||||
max-width: 80%;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 1rem;
|
||||
word-wrap: break-word;
|
||||
position: relative;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Mensajes entrantes */
|
||||
.message.in {
|
||||
background-color: #f0f0f0;
|
||||
color: #333;
|
||||
align-self: flex-start;
|
||||
border-bottom-left-radius: 0.25rem;
|
||||
}
|
||||
|
||||
/* Mensajes salientes */
|
||||
.message.out {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
align-self: flex-end;
|
||||
border-bottom-right-radius: 0.25rem;
|
||||
}
|
||||
|
||||
/* Estilo para el nombre del remitente en grupos */
|
||||
.message-sender {
|
||||
font-weight: bold;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
/* Estilo para el texto del mensaje */
|
||||
.message-text {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Estilo para el contenedor del formulario de mensajes */
|
||||
#message-form-container {
|
||||
padding: 1rem;
|
||||
border-top: 1px solid #dee2e6;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
/* Estilos para la lista de usuarios */
|
||||
.user-list {
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.user-list .list-group-item {
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.user-list .list-group-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.user-list .list-group-item.active {
|
||||
background-color: #007bff;
|
||||
border-color: #007bff;
|
||||
}
|
||||
|
||||
#wrapper {
|
||||
display: flex;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
#sidebar-wrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#page-content-wrapper {
|
||||
min-width: 100vw;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
/* Navbar styles */
|
||||
.navbar-brand {
|
||||
font-weight: bold;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.navbar-dark .navbar-nav .nav-link {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.navbar-dark .navbar-nav .nav-link:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.navbar-dark .navbar-nav .nav-link.active {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
animation: slideDown 0.2s ease;
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.dropdown-item:hover {.theme-toggle-btn
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.dropdown-item.active, .dropdown-item:active {
|
||||
background-color: #0d6efd;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Responsive navbar */
|
||||
@media (max-width: 991px) {
|
||||
.navbar-collapse {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.d-flex.align-items-center.gap-2 {
|
||||
flex-direction: column;
|
||||
align-items: flex-start !important;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
#wrapper.toggled #page-content-wrapper {
|
||||
min-width: calc(100vw - 250px);
|
||||
}
|
||||
|
||||
.sidebar-heading {
|
||||
padding: 0.875rem 1.25rem;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
#sidebar-wrapper {
|
||||
margin-left: 0;
|
||||
position: relative; /* Changed for desktop */
|
||||
z-index: 1; /* Changed for desktop */
|
||||
}
|
||||
|
||||
#page-content-wrapper {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#wrapper.toggled #sidebar-wrapper {
|
||||
margin-left: -250px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Overlay for mobile when sidebar is open */
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 999;
|
||||
display: none;
|
||||
opacity: 0; /* Initial opacity */
|
||||
transition: opacity 0.3s ease-in-out; /* Smooth transition */
|
||||
}
|
||||
|
||||
.overlay.show {
|
||||
display: block;
|
||||
opacity: 1; /* Full opacity when shown */
|
||||
}
|
||||
|
||||
/* Estilos para el logo en el sidebar */
|
||||
#sidebar-logo {
|
||||
width: 40px; /* Tamaño fijo para el ancho */
|
||||
height: auto; /* Mantiene la proporción de aspecto */
|
||||
vertical-align: middle; /* Alinea verticalmente con el texto */
|
||||
margin-left: 8px; /* Espaciado a la izquierda del logo */
|
||||
}
|
||||
|
||||
.sidebar-heading {
|
||||
display: flex;
|
||||
align-items: center; /* Centra verticalmente el texto y el logo */
|
||||
padding: 0.875rem 1.25rem;
|
||||
font-size: 1.2rem;
|
||||
white-space: nowrap; /* Evita que el texto se rompa en varias líneas */
|
||||
}
|
||||
46
assets/js/main.js
Executable file
46
assets/js/main.js
Executable file
@@ -0,0 +1,46 @@
|
||||
$(document).ready(function(){
|
||||
$("#menu-toggle").click(function(e) {
|
||||
e.preventDefault();
|
||||
$("#wrapper").toggleClass("toggled");
|
||||
});
|
||||
|
||||
$(".overlay").click(function() {
|
||||
$("#wrapper").removeClass("toggled");
|
||||
});
|
||||
|
||||
// Theme Toggle Functionality
|
||||
const themeToggle = $('#theme-toggle');
|
||||
const themeIcon = $('#theme-icon');
|
||||
const html = $('html');
|
||||
|
||||
// Load saved theme or default to light
|
||||
const savedTheme = localStorage.getItem('theme') || 'light';
|
||||
setTheme(savedTheme);
|
||||
|
||||
themeToggle.on('click', function() {
|
||||
const currentTheme = html.attr('data-bs-theme') || 'light';
|
||||
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
|
||||
setTheme(newTheme);
|
||||
localStorage.setItem('theme', newTheme);
|
||||
});
|
||||
|
||||
function setTheme(theme) {
|
||||
html.attr('data-bs-theme', theme);
|
||||
|
||||
// Update icon
|
||||
if (theme === 'dark') {
|
||||
themeIcon.removeClass('bi-sun-fill').addClass('bi-moon-fill');
|
||||
themeToggle.attr('title', 'Cambiar a tema claro');
|
||||
} else {
|
||||
themeIcon.removeClass('bi-moon-fill').addClass('bi-sun-fill');
|
||||
themeToggle.attr('title', 'Cambiar a tema oscuro');
|
||||
}
|
||||
}
|
||||
|
||||
// Check system preference on first load
|
||||
if (!localStorage.getItem('theme')) {
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
setTheme(prefersDark ? 'dark' : 'light');
|
||||
localStorage.setItem('theme', prefersDark ? 'dark' : 'light');
|
||||
}
|
||||
});
|
||||
75
common/helpers/url_helper.php
Executable file
75
common/helpers/url_helper.php
Executable file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
// Incluir el archivo de configuración
|
||||
require_once __DIR__ . '/../../config/config.php';
|
||||
|
||||
/**
|
||||
* Establece las cabeceras de seguridad, incluida la Content-Security-Policy.
|
||||
* Debe llamarse antes de cualquier salida HTML.
|
||||
*/
|
||||
function set_security_headers() {
|
||||
// CSP para permitir recursos solo del propio dominio y CDNs de confianza
|
||||
$csp = "default-src 'self'; "
|
||||
. "script-src 'self' https://cdn.jsdelivr.net https://code.jquery.com 'unsafe-inline'; " // unsafe-inline para scripts de bootstrap si es necesario
|
||||
. "style-src 'self' https://cdn.jsdelivr.net 'unsafe-inline'; " // unsafe-inline para estilos de bootstrap
|
||||
. "img-src 'self' data: " . BOT_BASE_URL . "; " // permitir imágenes del propio dominio, data URIs y el dominio externo
|
||||
. "font-src 'self' https://cdn.jsdelivr.net; "
|
||||
. "connect-src 'self' https://cdn.jsdelivr.net;"; // Para archivos .map de Bootstrap
|
||||
header("Content-Security-Policy: " . $csp);
|
||||
header("X-Content-Type-Options: nosniff");
|
||||
header("X-Frame-Options: DENY");
|
||||
header("X-XSS-Protection: 1; mode=block");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Genera una URL absoluta para el sitio
|
||||
*
|
||||
* @param string $path Ruta relativa
|
||||
* @return string URL completa
|
||||
*/
|
||||
function site_url($path = '') {
|
||||
return rtrim(BOT_BASE_URL, '/') . '/' . ltrim($path, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Genera una URL para los assets (CSS, JS, imágenes, etc.)
|
||||
*
|
||||
* @param string $path Ruta relativa al archivo de asset
|
||||
* @return string URL completa al asset
|
||||
*/
|
||||
function asset($path) {
|
||||
return site_url('assets/' . ltrim($path, '/'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirige al usuario a una URL específica
|
||||
*
|
||||
* @param string $url URL a la que redirigir
|
||||
* @param int $statusCode Código de estado HTTP (por defecto 302)
|
||||
*/
|
||||
function redirect($url, $statusCode = 302) {
|
||||
header('Location: ' . $url, true, $statusCode);
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene la URL actual completa
|
||||
*
|
||||
* @param string $path Ruta relativa
|
||||
* @return string URL completa
|
||||
*/
|
||||
function current_url() {
|
||||
$protocol = "http://";
|
||||
return $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica si la URL actual coincide con el patrón dado
|
||||
*
|
||||
* @param string $pattern Patrón a verificar
|
||||
* @return bool Verdadero si coincide, falso en caso contrario
|
||||
*/
|
||||
function is_current_url($pattern) {
|
||||
$current = current_url();
|
||||
return (strpos($current, $pattern) !== false);
|
||||
}
|
||||
148
config/config.php
Executable file
148
config/config.php
Executable file
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
// config/config.php
|
||||
|
||||
// Cargar variables de entorno
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
// Verificar si estamos en un contenedor Docker
|
||||
$is_docker = (getenv('DOCKER_CONTAINER') === '1') || ($_SERVER['DOCKER_CONTAINER'] ?? null) === '1';
|
||||
|
||||
if ($is_docker) {
|
||||
// Docker: cargar archivo .env creado por el entrypoint
|
||||
$dotenv = null;
|
||||
if (file_exists(dirname(__DIR__) . '/.env')) {
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||
try {
|
||||
$dotenv->load();
|
||||
} catch (Exception $e) {
|
||||
die('Error al cargar el archivo de entorno en Docker: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Entorno local: cargar archivo .env según APP_ENVIRONMENT
|
||||
$env = getenv('APP_ENVIRONMENT') ?: ($_SERVER['APP_ENVIRONMENT'] ?? 'pruebas');
|
||||
$envFile = '.env';
|
||||
if ($env) {
|
||||
$envFile = '.env.' . $env;
|
||||
}
|
||||
|
||||
$dotenv = null;
|
||||
if (file_exists(dirname(__DIR__) . '/' . $envFile)) {
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__), $envFile);
|
||||
} elseif (file_exists(dirname(__DIR__) . '/.env')) {
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||
}
|
||||
|
||||
if ($dotenv) {
|
||||
try {
|
||||
$dotenv->load();
|
||||
} catch (Exception $e) {
|
||||
die('Error al cargar el archivo de entorno: ' . $e->getMessage());
|
||||
}
|
||||
$dotenv->required([
|
||||
'DB_HOST', 'DB_NAME', 'DB_USER', 'DB_PASS',
|
||||
'JWT_SECRET', 'APP_URL'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Environment Configuration
|
||||
define('ENVIRONMENT', $_ENV['APP_ENV'] ?? $_SERVER['APP_ENV'] ?? 'production');
|
||||
|
||||
// Detectar si se ejecuta desde la línea de comandos
|
||||
$is_cli = (php_sapi_name() === 'cli' || defined('STDIN'));
|
||||
|
||||
// Helper function to get env vars
|
||||
function getEnvVar($name, $default = null) {
|
||||
return $_ENV[$name] ?? $_SERVER[$name] ?? getenv($name) ?? $default;
|
||||
}
|
||||
|
||||
// Configurar la URL base y el protocolo
|
||||
if ($is_cli) {
|
||||
define('BOT_BASE_URL', getEnvVar('APP_URL'));
|
||||
$protocol = 'http';
|
||||
} else {
|
||||
$protocol = 'http';
|
||||
if ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ||
|
||||
(!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') ||
|
||||
(!empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] === 'on') ||
|
||||
(!empty($_SERVER['HTTP_CF_VISITOR']) && strpos($_SERVER['HTTP_CF_VISITOR'], 'https') !== false)) {
|
||||
$protocol = 'https';
|
||||
$_SERVER['HTTPS'] = 'on';
|
||||
$_SERVER['SERVER_PORT'] = 443;
|
||||
}
|
||||
$app_url = getEnvVar('APP_URL');
|
||||
if ($app_url) {
|
||||
define('BOT_BASE_URL', $app_url);
|
||||
} else {
|
||||
define('BOT_BASE_URL', $protocol . '://' . $_SERVER['HTTP_HOST']);
|
||||
}
|
||||
$_SERVER['REQUEST_SCHEME'] = $protocol;
|
||||
}
|
||||
define('BASE_PATH', dirname(__DIR__));
|
||||
|
||||
// Database Configuration
|
||||
define('DB_HOST', getEnvVar('DB_HOST'));
|
||||
define('DB_USER', getEnvVar('DB_USER'));
|
||||
define('DB_PASS', getEnvVar('DB_PASS'));
|
||||
define('DB_NAME', getEnvVar('DB_NAME'));
|
||||
define('DB_DIALECT', getEnvVar('DB_DIALECT'));
|
||||
define('DB_PORT', getEnvVar('DB_PORT'));
|
||||
|
||||
// Session Configuration
|
||||
define('SESSION_SECRET', getEnvVar('JWT_SECRET'));
|
||||
|
||||
// Discord API Configuration
|
||||
define('DISCORD_GUILD_ID', getEnvVar('DISCORD_GUILD_ID'));
|
||||
define('DISCORD_CLIENT_ID', getEnvVar('DISCORD_CLIENT_ID'));
|
||||
define('DISCORD_CLIENT_SECRET', getEnvVar('DISCORD_CLIENT_SECRET'));
|
||||
define('DISCORD_BOT_TOKEN', getEnvVar('DISCORD_BOT_TOKEN'));
|
||||
|
||||
// Telegram API Configuration
|
||||
define('TELEGRAM_BOT_TOKEN', getEnvVar('TELEGRAM_BOT_TOKEN'));
|
||||
define('TELEGRAM_WEBHOOK_TOKEN', getEnvVar('TELEGRAM_WEBHOOK_TOKEN'));
|
||||
|
||||
// LibreTranslate Configuration
|
||||
define('LIBRETRANSLATE_URL', getEnvVar('LIBRETRANSLATE_URL'));
|
||||
|
||||
// N8N Configuration
|
||||
define('N8N_URL', getEnvVar('N8N_URL'));
|
||||
define('N8N_TOKEN', getEnvVar('N8N_TOKEN'));
|
||||
define('N8N_PROCESS_QUEUE_WEBHOOK_URL', getEnvVar('N8N_PROCESS_QUEUE_WEBHOOK_URL'));
|
||||
define('N8N_IA_WEBHOOK_URL', getEnvVar('N8N_IA_WEBHOOK_URL'));
|
||||
define('N8N_IA_WEBHOOK_URL_DISCORD', getEnvVar('N8N_IA_WEBHOOK_URL_DISCORD'));
|
||||
define('INTERNAL_API_KEY', getEnvVar('INTERNAL_API_KEY'));
|
||||
|
||||
// Error Reporting
|
||||
switch (ENVIRONMENT) {
|
||||
case 'development':
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '1');
|
||||
ini_set('log_errors', '1');
|
||||
ini_set('error_log', dirname(__DIR__) . '/logs/php_errors.log');
|
||||
break;
|
||||
case 'production':
|
||||
error_reporting(E_ALL & ~E_DEPRECATED);
|
||||
ini_set('display_errors', '0');
|
||||
ini_set('log_errors', '1');
|
||||
ini_set('error_log', dirname(__DIR__) . '/logs/php_errors.log');
|
||||
break;
|
||||
default:
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '1');
|
||||
ini_set('log_errors', '1');
|
||||
ini_set('error_log', dirname(__DIR__) . '/logs/php_errors.log');
|
||||
break;
|
||||
}
|
||||
|
||||
// Helper function to get full URL
|
||||
function url($path = '') {
|
||||
$path = ltrim($path, '/');
|
||||
return BOT_BASE_URL . '/' . $path;
|
||||
}
|
||||
|
||||
// Helper function to get full asset URL
|
||||
function asset_url($path = '') {
|
||||
$path = ltrim($path, '/');
|
||||
return BOT_BASE_URL . '/assets/' . $path;
|
||||
}
|
||||
171
sent_messages.php
Executable file
171
sent_messages.php
Executable file
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/session_check.php';
|
||||
require_once __DIR__ . '/includes/db.php';
|
||||
|
||||
try {
|
||||
$query = "
|
||||
SELECT
|
||||
sm.id as sent_message_id,
|
||||
sm.platform_message_id,
|
||||
s.sent_at,
|
||||
m.content,
|
||||
u.username as creator_username,
|
||||
r.name as recipient_name,
|
||||
r.type as recipient_type,
|
||||
r.platform,
|
||||
r.platform_id
|
||||
FROM sent_messages sm
|
||||
JOIN schedules s ON sm.schedule_id = s.id
|
||||
JOIN messages m ON s.message_id = m.id
|
||||
JOIN users u ON m.user_id = u.id
|
||||
JOIN recipients r ON sm.recipient_id = r.id
|
||||
ORDER BY s.sent_at DESC
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($query);
|
||||
$stmt->execute();
|
||||
$sentMessages = $stmt->fetchAll();
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error al consultar mensajes enviados: " . $e->getMessage());
|
||||
$sentMessages = [];
|
||||
$db_error = "Error de base de datos: " . $e->getMessage();
|
||||
}
|
||||
|
||||
$pageTitle = 'Mensajes Enviados';
|
||||
require_once __DIR__ . '/templates/header.php';
|
||||
?>
|
||||
|
||||
<div class="container-fluid">
|
||||
<h1 class="mt-4" data-translate="true">Mensajes Enviados</h1>
|
||||
|
||||
<?php if (isset($db_error)): ?>
|
||||
<div class="alert alert-danger"><?= htmlspecialchars($db_error) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
// Obtener la plataforma del mensaje que se está eliminando (si está presente en la URL)
|
||||
$platform = $_GET['platform'] ?? 'Discord'; // Por defecto a Discord para mantener compatibilidad
|
||||
$platform = ucfirst(strtolower($platform)); // Asegurar que la primera letra sea mayúscula
|
||||
?>
|
||||
|
||||
<?php if (isset($_GET['success']) && $_GET['success'] === 'deleted'): ?>
|
||||
<div class="alert alert-success"><span data-translate="true">Mensaje eliminado de</span> <?= htmlspecialchars($platform) ?> <span data-translate="true">con éxito</span>.</div>
|
||||
<?php elseif (isset($_GET['error'])): ?>
|
||||
<div class="alert alert-danger">
|
||||
<?php
|
||||
$errorMessage = '<span data-translate="true">Ocurrió un error desconocido.</span>';
|
||||
if ($_GET['error'] === 'delete_failed') {
|
||||
$errorMessage = "<span data-translate='true'>No se pudo eliminar el mensaje de</span> {$platform}.";
|
||||
if (isset($_GET['message'])) {
|
||||
$errorMessage .= ' ' . htmlspecialchars($_GET['message']);
|
||||
}
|
||||
}
|
||||
echo htmlspecialchars($errorMessage);
|
||||
?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card shadow-sm" id="sent-messages-table">
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th data-translate="true">Plataforma</th>
|
||||
<th data-translate="true">Destinatario</th>
|
||||
<th data-translate="true">Contenido (Previo)</th>
|
||||
<th data-translate="true">Fecha de Envío</th>
|
||||
<th data-translate="true">Creado por</th>
|
||||
<th class="text-center" data-translate="true">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($sentMessages)): ?>
|
||||
<tr>
|
||||
<td colspan="6" class="text-center text-muted" data-translate="true">No se han enviado mensajes todavía.</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($sentMessages as $msg): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="badge <?= $msg['platform'] === 'discord' ? 'bg-primary' : 'bg-info text-dark' ?>">
|
||||
<i class="bi bi-<?= $msg['platform'] === 'discord' ? 'discord' : 'telegram' ?>"></i>
|
||||
<?= htmlspecialchars(ucfirst($msg['platform'])) ?>
|
||||
</span>
|
||||
</td>
|
||||
<td><?= htmlspecialchars($msg['recipient_name']) . ' <span class="text-muted">(' . $msg['recipient_type'] . ')</span>' ?></td>
|
||||
<td class="text-break">
|
||||
<div class="message-preview text-content">
|
||||
<?= substr(strip_tags($msg['content']), 0, 100) ?>...
|
||||
</div>
|
||||
</td>
|
||||
<td><?= date('d/m/Y H:i', strtotime($msg['sent_at'])) ?></td>
|
||||
<td><?= htmlspecialchars($msg['creator_username']) ?></td>
|
||||
<td class="text-center">
|
||||
<div class="d-flex gap-2 justify-content-center">
|
||||
<form action="create_message.php" method="POST" class="d-inline">
|
||||
<input type="hidden" name="action" value="reuse">
|
||||
<input type="hidden" name="messageContent" value="<?= htmlspecialchars($msg['content'], ENT_QUOTES, 'UTF-8') ?>">
|
||||
<button type="submit" class="btn btn-sm btn-secondary" title="Reutilizar este mensaje" data-translate-title="true">
|
||||
<i class="bi bi-recycle"></i>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<?php if (!empty($msg['platform_message_id'])): ?>
|
||||
<?php if ($msg['platform'] === 'discord'): ?>
|
||||
<form action="includes/discord_actions.php" method="POST" onsubmit="return confirm(this.querySelector('[data-translate-confirm]').getAttribute('data-translate-confirm'));" class="d-inline">
|
||||
<input type="hidden" name="action" value="delete_message">
|
||||
<input type="hidden" name="sent_message_id" value="<?= $msg['sent_message_id'] ?>">
|
||||
<input type="hidden" name="platform_message_id" value="<?= htmlspecialchars($msg['platform_message_id'], ENT_QUOTES, 'UTF-8') ?>">
|
||||
<input type="hidden" name="channel_id" value="<?= htmlspecialchars($msg['platform_id'], ENT_QUOTES, 'UTF-8') ?>">
|
||||
<input type="hidden" name="confirm_message" value="¿Estás seguro de que quieres ELIMINAR este mensaje de Discord?" data-translate-confirm="¿Estás seguro de que quieres ELIMINAR este mensaje de Discord?">
|
||||
<button type="submit" class="btn btn-sm btn-danger" title="Eliminar de Discord" data-translate-title="true">
|
||||
<i class="bi bi-trash-fill"></i>
|
||||
</button>
|
||||
</form>
|
||||
<?php elseif ($msg['platform'] === 'telegram'): ?>
|
||||
<form action="includes/telegram_actions.php" method="POST" onsubmit="return confirm(this.querySelector('[data-translate-confirm]').getAttribute('data-translate-confirm'));" class="d-inline">
|
||||
<input type="hidden" name="action" value="delete_message">
|
||||
<input type="hidden" name="sent_message_id" value="<?= $msg['sent_message_id'] ?>">
|
||||
<input type="hidden" name="platform_message_id" value="<?= htmlspecialchars($msg['platform_message_id'], ENT_QUOTES, 'UTF-8') ?>">
|
||||
<input type="hidden" name="chat_id" value="<?= htmlspecialchars($msg['platform_id'], ENT_QUOTES, 'UTF-8') ?>">
|
||||
<input type="hidden" name="confirm_message" value="¿Estás seguro de que quieres ELIMINAR este mensaje de Telegram?" data-translate-confirm="¿Estás seguro de que quieres ELIMINAR este mensaje de Telegram?">
|
||||
<button type="submit" class="btn btn-sm btn-danger" title="Eliminar de Telegram" data-translate-title="true">
|
||||
<i class="bi bi-trash-fill"></i>
|
||||
</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.message-preview {
|
||||
max-height: 80px;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
background-color: var(--bs-light);
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: 5px;
|
||||
font-size: 0.9em;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .message-preview {
|
||||
background-color: var(--bs-dark-bg-subtle);
|
||||
border-color: var(--bs-border-color-translucent);
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php require_once __DIR__ . '/templates/footer.php'; ?>
|
||||
151
templates/header.php
Executable file
151
templates/header.php
Executable file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
// Incluir los helpers necesarios
|
||||
require_once __DIR__ . '/../includes/url_helper.php';
|
||||
require_once __DIR__ . '/../includes/translation_helper.php';
|
||||
|
||||
// Inicializar el idioma de la sesión si no está definido
|
||||
if (!isset($_SESSION['language'])) {
|
||||
$_SESSION['language'] = 'es'; // Idioma por defecto
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="es" data-bs-theme="light">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Bot Discord</title>
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
||||
<!-- Bootstrap Icons -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
|
||||
<!-- Estilos personalizados -->
|
||||
<link rel="stylesheet" href="<?php echo asset('css/style.css'); ?>" type="text/css">
|
||||
<!-- Contenido extra del head (opcional) -->
|
||||
<?php if (isset($extraHead)) echo $extraHead; ?>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Top Navigation Bar -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark border-bottom">
|
||||
<div class="container-fluid">
|
||||
<!-- Logo y nombre -->
|
||||
<a class="navbar-brand d-flex align-items-center" href="<?php echo site_url('index.php'); ?>">
|
||||
<img src="<?php echo asset('images/logo.png'); ?>" alt="Logo" style="height: 30px; margin-right: 10px;">
|
||||
<strong>Bot Discord</strong>
|
||||
</a>
|
||||
|
||||
<!-- Toggle button para móvil -->
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarMenu" aria-controls="navbarMenu" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<!-- Menu items -->
|
||||
<div class="collapse navbar-collapse" id="navbarMenu">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<!-- Inicio -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="<?php echo site_url('index.php'); ?>" data-translate="true">
|
||||
<i class="bi bi-house-door-fill me-1"></i>Inicio
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Mensajes -->
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navMensajes" role="button" data-bs-toggle="dropdown" aria-expanded="false" data-translate="true">
|
||||
<i class="bi bi-chat-dots-fill me-1"></i>Mensajes
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="navMensajes">
|
||||
<li><a class="dropdown-item" href="<?php echo site_url('create_message.php'); ?>" data-translate="true"><i class="bi bi-plus-square-fill me-2"></i>Crear Mensaje</a></li>
|
||||
<li><a class="dropdown-item" href="<?php echo site_url('scheduled_messages.php'); ?>" data-translate="true"><i class="bi bi-clock-fill me-2"></i>Programados</a></li>
|
||||
<li><a class="dropdown-item" href="<?php echo site_url('recurrentes.php'); ?>" data-translate="true"><i class="bi bi-arrow-repeat me-2"></i>Plantillas</a></li>
|
||||
<li><a class="dropdown-item" href="<?php echo site_url('sent_messages.php'); ?>" data-translate="true"><i class="bi bi-send-fill me-2"></i>Enviados</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<!-- Recursos -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="<?php echo site_url('gallery.php'); ?>" data-translate="true">
|
||||
<i class="bi bi-images me-1"></i>Galería
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Admin (solo si es admin) -->
|
||||
<?php if ($_SESSION['role'] === 'admin'): ?>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navAdmin" role="button" data-bs-toggle="dropdown" aria-expanded="false" data-translate="true">
|
||||
<i class="bi bi-gear-fill me-1"></i>Admin
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="navAdmin">
|
||||
<li><h6 class="dropdown-header">Gestión</h6></li>
|
||||
<li><a class="dropdown-item" href="<?php echo site_url('admin/users.php'); ?>" data-translate="true"><i class="bi bi-people-fill me-2"></i>Usuarios</a></li>
|
||||
<li><a class="dropdown-item" href="<?php echo site_url('admin/recipients.php'); ?>" data-translate="true"><i class="bi bi-person-rolodex me-2"></i>Destinatarios</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><h6 class="dropdown-header">Configuración</h6></li>
|
||||
<li><a class="dropdown-item" href="<?php echo site_url('admin/languages.php'); ?>" data-translate="true"><i class="bi bi-translate me-2"></i>Idiomas</a></li>
|
||||
<li><a class="dropdown-item" href="<?php echo site_url('admin/comandos.php'); ?>" data-translate="true"><i class="bi bi-terminal-fill me-2"></i>Comandos</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><h6 class="dropdown-header">Bots</h6></li>
|
||||
<li><a class="dropdown-item" href="<?php echo site_url('telegram/admin/telegram_welcome.php'); ?>" data-translate="true"><i class="bi bi-telegram me-2"></i>Telegram Config</a></li>
|
||||
<li><a class="dropdown-item" href="<?php echo site_url('telegram/admin/telegram_bot_interactions.php'); ?>" data-translate="true"><i class="bi bi-robot me-2"></i>Interacciones Bot</a></li>
|
||||
<li><a class="dropdown-item" href="<?php echo site_url('telegram/admin/chat_telegram.php'); ?>" data-translate="true"><i class="bi bi-chat-dots-fill me-2"></i>Chat Telegram</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><h6 class="dropdown-header">Monitoreo</h6></li>
|
||||
<li><a class="dropdown-item" href="<?php echo site_url('admin/activity.php'); ?>" data-translate="true"><i class="bi bi-clipboard-data-fill me-2"></i>Actividad</a></li>
|
||||
<li><a class="dropdown-item" href="<?php echo site_url('admin/test_discord_connection.php'); ?>" data-translate="true"><i class="bi bi-bug-fill me-2"></i>Test</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- User menu -->
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navUser" role="button" data-bs-toggle="dropdown" aria-expanded="false" style="cursor: pointer;">
|
||||
<i class="bi bi-person-circle me-1"></i><?php echo htmlspecialchars($_SESSION['username']); ?>
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="navUser">
|
||||
<li><a class="dropdown-item" href="<?php echo site_url('profile.php'); ?>" data-translate="true"><i class="bi bi-person-circle me-2"></i>Perfil</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="<?php echo site_url('logout.php'); ?>" data-translate="true"><i class="bi bi-box-arrow-right me-2"></i>Cerrar Sesión</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Right side: Theme toggle, Language selector, DB info -->
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap ms-auto">
|
||||
<!-- Theme Toggle -->
|
||||
<button class="btn btn-sm theme-toggle-btn" id="theme-toggle" title="Cambiar tema" aria-label="Cambiar tema claro/oscuro">
|
||||
<i class="bi bi-sun-fill" id="theme-icon"></i>
|
||||
</button>
|
||||
|
||||
<select class="form-select form-select-sm w-auto" id="language-selector" style="max-width: 120px;">
|
||||
<!-- Options will be populated by translate_frontend.js -->
|
||||
</select>
|
||||
|
||||
<?php if ($_SESSION['role'] === 'admin'): ?>
|
||||
<div class="card border-secondary bg-dark text-light" style="width: 150px; box-shadow: 0 2px 6px rgba(0,0,0,0.3); margin: 0; flex-shrink: 0;">
|
||||
<div class="card-body p-1 text-center" style="font-size: 0.8rem;">
|
||||
<div class="mb-1">
|
||||
<i class="bi bi-database text-info" style="font-size: 14px;"></i>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<small class="text-muted d-block" style="font-size: 8px;">BASE DE DATOS</small>
|
||||
<strong class="d-block text-light" style="font-size: 11px;"><?php echo htmlspecialchars(DB_NAME); ?></strong>
|
||||
</div>
|
||||
<hr class="my-1" style="margin: 0.3rem 0;">
|
||||
<div>
|
||||
<small class="text-muted d-block" style="font-size: 8px;">SERVIDOR</small>
|
||||
<code class="text-info" style="font-size: 10px;"><?php echo htmlspecialchars(DB_HOST); ?>:<?php echo htmlspecialchars(DB_PORT); ?></code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div id="page-content-wrapper">
|
||||
|
||||
<main class="container-fluid p-4">
|
||||
|
||||
<!-- Conflicting language script removed -->
|
||||
Reference in New Issue
Block a user