{"id":3353,"date":"2026-02-09T01:34:47","date_gmt":"2026-02-08T23:34:47","guid":{"rendered":"https:\/\/www.sinetiqueta.com\/?p=3353"},"modified":"2026-02-09T01:38:20","modified_gmt":"2026-02-08T23:38:20","slug":"mini-cpanel-mejora-dashboard-red-y-accesos-profesionales-en-desarrollo","status":"publish","type":"post","link":"https:\/\/www.sinetiqueta.com\/?p=3353","title":{"rendered":"&#x1f7e2; Mini CPanel \u2013 Mejora: Dashboard, Red y Accesos Profesionales (EN DESARROLLO)"},"content":{"rendered":"\n<p><strong>Continuaci\u00f3n de: Mini CPanel Profesional \u2013 Procedimiento Completo para Raspberry Pi 3B<\/strong><\/p>\n\n\n\n<p>En este art\u00edculo vamos a mejorar nuestro Mini CPanel, haci\u00e9ndolo <strong>m\u00e1s profesional, visual y funcional<\/strong>. Esto incluye la configuraci\u00f3n de red, accesos r\u00e1pidos a herramientas, logs, y un dashboard con estad\u00edsticas de servidor.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">&#x1f539; \u00cdndice de mejoras de este art\u00edculo<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Dashboard principal<\/strong>\n<ul class=\"wp-block-list\">\n<li>Visualizaci\u00f3n de IP, MAC, estado de la red<\/li>\n\n\n\n<li>Estad\u00edsticas de servidor (CPU, memoria, almacenamiento, n\u00famero de archivos, conexiones activas)<\/li>\n\n\n\n<li>Graficar tr\u00e1fico de red (solo al abrir el dashboard)<\/li>\n\n\n\n<li><strong>Dashboard secundario<\/strong>\n<ul class=\"wp-block-list\">\n<li>Registro de LOGs de conexiones descargable por fechas y auto borrado.<\/li>\n\n\n\n<li>Geolocalizaci\u00f3n de IPs<\/li>\n\n\n\n<li>Lista de puertos abiertos<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Botones de acceso r\u00e1pido<\/strong>\n<ul class=\"wp-block-list\">\n<li>FileBrowser<\/li>\n\n\n\n<li>phpMyAdmin<\/li>\n\n\n\n<li>Personalizaci\u00f3n con iconos adaptativos (logo de la app)<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Logs de actividad<\/strong>\n<ul class=\"wp-block-list\">\n<li>Inicio de sesi\u00f3n<\/li>\n\n\n\n<li>Acciones del panel (reinicio PHP, ajustes, backups)<\/li>\n\n\n\n<li>Auto-borrado a 60 d\u00edas<\/li>\n\n\n\n<li>Opci\u00f3n de borrado manual con confirmaci\u00f3n<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Configuraci\u00f3n de red<\/strong>\n<ul class=\"wp-block-list\">\n<li>Consultar IP, MAC, DNS y Gateway<\/li>\n\n\n\n<li>Test de conectividad (ping y nslookup)<\/li>\n\n\n\n<li>Interfaces activas y estad\u00edsticas de tr\u00e1fico<\/li>\n\n\n\n<li>N\u00famero de conexiones activas y por IP<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Acciones r\u00e1pidas de red<\/strong>\n<ul class=\"wp-block-list\">\n<li>Reinicio de interfaces<\/li>\n\n\n\n<li>Cambio de IP est\u00e1tica\/din\u00e1mica<\/li>\n\n\n\n<li>Configuraci\u00f3n WiFi<\/li>\n\n\n\n<li>Escaneo r\u00e1pido de LAN<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Seguridad y buenas pr\u00e1cticas<\/strong>\n<ul class=\"wp-block-list\">\n<li>Acceso solo LAN<\/li>\n\n\n\n<li>HTTPS opcional con certificado self-signed<\/li>\n\n\n\n<li>Ejecuci\u00f3n de scripts con sudo seguro<\/li>\n\n\n\n<li>Usuario admin separado<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Est\u00e9tica y UX<\/strong>\n<ul class=\"wp-block-list\">\n<li>Tipograf\u00eda profesional y colores claros<\/li>\n\n\n\n<li>Panel responsivo con tarjetas, tablas e iconos<\/li>\n\n\n\n<li>Logo adaptable al tama\u00f1o para no interferir en la visualizaci\u00f3n<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">&#x1f7e2; Mini CPanel \u2013 Punto 1: Dashboard Principal<\/h1>\n\n\n\n<p><strong>Objetivo:<\/strong> Crear un dashboard visual que muestre solo cuando se accede, informaci\u00f3n relevante como:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>IP y MAC de la Raspberry Pi<\/li>\n\n\n\n<li>Gateway y DNS<\/li>\n\n\n\n<li>Uso de CPU, RAM y almacenamiento<\/li>\n\n\n\n<li>N\u00famero de archivos en <code>\/var\/www\/apps<\/code><\/li>\n\n\n\n<li>Conexiones activas por IP<\/li>\n\n\n\n<li>Gr\u00e1fico de tr\u00e1fico de red (opcional con Chart.js)<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">1&#xfe0f;&#x20e3; Crear directorios para el dashboard<\/h2>\n\n\n\n<p>Primero asegur\u00e9monos de que existan los directorios donde guardaremos los assets (CSS, JS, im\u00e1genes):<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>sudo mkdir -p \/var\/www\/html\/assets\/css<br>\nsudo mkdir -p \/var\/www\/html\/assets\/js<br>\nsudo mkdir -p \/var\/www\/html\/assets\/img<br>\nsudo chown -R www-data:www-data \/var\/www\/html\/assets<br>\nsudo chmod -R 755 \/var\/www\/html\/assets<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"has-large-font-size\">1&#xfe0f;&#x20e3; Modificar el ADMIN.php<\/p>\n\n\n\n<pre class=\"wp-block-preformatted has-small-font-size\">&lt;?php<br>session_start();<br>$secret_pass = \"PASSWORD\";<br><br>\/* ========= LOGIN ========= *\/<br>if (isset($_POST['password'])) {<br>    if (hash_equals($secret_pass, $_POST['password'])) {<br>        $_SESSION['auth'] = true;<br>    } else { $error = \"Contrase\u00f1a incorrecta\"; }<br>}<br><br>if (!isset($_SESSION['auth'])):<br>?&gt;<br>&lt;!DOCTYPE html&gt;<br>&lt;html lang=\"es\"&gt;<br>&lt;head&gt;<br>&lt;meta charset=\"utf-8\"&gt;<br>&lt;title&gt;Login&lt;\/title&gt;<br>&lt;link rel=\"icon\" type=\"image\/png\" href=\"assets\/img\/logo.png\"&gt;<br>&lt;style&gt;<br>body{background:#0f172a;color:#e5e7eb;font-family:sans-serif;display:flex;justify-content:center;align-items:center;height:100vh;margin:0}<br>.login{background:#1e293b;padding:30px;border-radius:12px;width:90%;max-width:400px;text-align:center}<br>\/* Estilo para el logo en login *\/<br>.login-logo{width:80px;height:auto;margin-bottom:20px}<br>input,button{width:100%;padding:12px;margin-top:10px;border-radius:8px;border:none;box-sizing:border-box}<br>button{background:#3b82f6;color:#fff;font-weight:bold;cursor:pointer}<br>&lt;\/style&gt;<br>&lt;\/head&gt;<br>&lt;body&gt;<br>&lt;div class=\"login\"&gt;<br>    &lt;img src=\"assets\/img\/logo.png\" alt=\"Logo\" class=\"login-logo\"&gt;<br>    &lt;form method=\"post\"&gt;<br>        &lt;input type=\"password\" name=\"password\" placeholder=\"Contrase\u00f1a\" autofocus&gt;<br>        &lt;button type=\"submit\"&gt;Entrar&lt;\/button&gt;<br>        &lt;?php if(isset($error)) echo \"&lt;p style='color:#ef4444;margin-top:15px'&gt;$error&lt;\/p&gt;\"; ?&gt;<br>    &lt;\/form&gt;<br>&lt;\/div&gt;<br>&lt;\/body&gt;<br>&lt;\/html&gt;<br>&lt;?php exit; endif; ?&gt;<br><br>&lt;?php<br>\/* ========= CONFIG &amp; INTERFACE ========= *\/<br>if(isset($_GET['set_iface'])) $_SESSION['iface'] = $_GET['set_iface'];<br>$active_iface = $_SESSION['iface'] ?? 'eth0';<br><br>$netFile = \"\/tmp\/admin_net_prev.json\";<br>$scriptPath = \"\/var\/www\/apps\/admin-scripts\/\";<br><br>\/* ========= TERMINAL ========= *\/<br>if (!isset($_SESSION['terminal'])) $_SESSION['terminal'] = [];<br>function logTerm($type,$msg){<br>    $_SESSION['terminal'][]=['t'=&gt;date(\"Y-m-d H:i:s\"),'type'=&gt;$type,'msg'=&gt;trim($msg)];<br>    if(count($_SESSION['terminal'])&gt;300) $_SESSION['terminal']=array_slice($_SESSION['terminal'],-300);<br>}<br><br>\/* ========= ACTION AJAX ========= *\/<br>if(isset($_GET['action'])){<br>    header(\"Content-Type: application\/json\");<br>    if($_GET['action']==='run'){<br>        $cmd = $_GET['cmd'] ?? '';<br>        $map = ['reiniciar_php'=&gt;'reiniciar-php.sh','permisos'=&gt;'ajustar-permisos.sh','backup'=&gt;'backup.sh'];<br>        if(isset($map[$cmd])){<br>            $fullPath = $scriptPath . $map[$cmd];<br>            if(file_exists($fullPath)){<br>                $out = shell_exec(\"sudo -n $fullPath 2&gt;&amp;1 &lt; \/dev\/null\");<br>                logTerm('ok', $out ?: 'OK');<br>            } else { logTerm('err', \"No existe: $fullPath\"); }<br>            echo json_encode(['ok'=&gt;true]);<br>        }<br>        exit;<br>    }<br>    if($_GET['action']==='clear_term'){ $_SESSION['terminal']=[]; echo json_encode(['ok'=&gt;true]); exit; }<br>}<br><br>\/* ========= SISTEMA ========= *\/<br>function cpu(){ return round(floatval(shell_exec(\"top -bn1 | awk '\/Cpu\/ {print 100-$8}'\")),1); }<br>function mem(){ return round(floatval(shell_exec(\"free | awk '\/Mem:\/ {print $3\/$2*100}'\")),1); }<br>function disk(){ return intval(shell_exec(\"df \/ | awk 'NR==2{gsub(\/%\/,\\\"\\\",$5);print $5}'\")); }<br>function temp(){ return round(@file_get_contents(\"\/sys\/class\/thermal\/thermal_zone0\/temp\")\/1000,1); }<br><br>\/* ========= RED AVANZADA ========= *\/<br>function get_network_details($i) {<br>    $ip = trim(shell_exec(\"ip -4 addr show $i | awk '\/inet \/{print $2}' | cut -d\/ -f1\"));<br>    $mask = trim(shell_exec(\"ip -4 addr show $i | awk '\/inet \/{print $2}' | cut -d\/ -f2\"));<br>    $mac = trim(@file_get_contents(\"\/sys\/class\/net\/$i\/address\"));<br>    $gateway = trim(shell_exec(\"ip route | grep default | awk '{print $3}'\"));<br>    $dns = trim(shell_exec(\"grep nameserver \/etc\/resolv.conf | awk '{print $2}' | head -n 1\"));<br>    $state = trim(@file_get_contents(\"\/sys\/class\/net\/$i\/operstate\"));<br>    $public_ip = trim(@shell_exec(\"curl -s --max-time 2 https:\/\/api.ipify.org\") ?: '-');<br><br>    return [<br>        'interface' =&gt; $i,<br>        'ip' =&gt; $ip ?: 'Desconectado',<br>        'mask' =&gt; $mask ?: '-',<br>        'mac' =&gt; $mac ?: '-',<br>        'gateway' =&gt; $gateway ?: '-',<br>        'dns' =&gt; $dns ?: '-',<br>        'state' =&gt; $state ?: 'unknown',<br>        'public' =&gt; $public_ip ?: '-',<br>        'rx' =&gt; intval(@file_get_contents(\"\/sys\/class\/net\/$i\/statistics\/rx_bytes\")),<br>        'tx' =&gt; intval(@file_get_contents(\"\/sys\/class\/net\/$i\/statistics\/tx_bytes\"))<br>    ];<br>}<br><br>\/* ========= JSON ========= *\/<br>if(isset($_GET['json'])){<br>    header(\"Content-Type: application\/json\");<br>    $prev = file_exists($netFile) ? json_decode(file_get_contents($netFile),true) : [];<br>    $net = get_network_details($active_iface);<br>    <br>    $rx_speed = isset($prev[$active_iface.'_rx']) ? ($net['rx']-$prev[$active_iface.'_rx'])\/1024 : 0;<br>    $tx_speed = isset($prev[$active_iface.'_tx']) ? ($net['tx']-$prev[$active_iface.'_tx'])\/1024 : 0;<br>    <br>    $raw_conns = shell_exec(\"ss -tun | awk 'NR&gt;1 {print $1, $6}'\");<br>    $ip_counts = [];<br>    if($raw_conns){<br>        foreach(explode(\"\\n\", trim($raw_conns)) as $line){<br>            $cols = preg_split('\/\\s+\/', trim($line)); if(count($cols) &lt; 2) continue;<br>            $remote_full = $cols[1];<br>            $parts = explode(':', $remote_full); array_pop($parts); <br>            $ip = str_replace(['[',']'], '', implode(':', $parts));<br>            if($ip == \"127.0.0.1\" || $ip == \"::1\" || empty($ip) || $ip == \"*\" || $ip == \"0.0.0.0\") continue;<br>            $state = ($cols[0] == \"ESTAB\" ? 'ok' : 'err');<br>            if(!isset($ip_counts[$ip])) $ip_counts[$ip] = ['count'=&gt;0, 'state'=&gt;$state];<br>            $ip_counts[$ip]['count']++;<br>        }<br>    }<br><br>    $prev[$active_iface.'_rx'] = $net['rx']; $prev[$active_iface.'_tx'] = $net['tx'];<br>    file_put_contents($netFile,json_encode($prev));<br><br>    echo json_encode([<br>        'cpu'=&gt;cpu(), 'mem'=&gt;mem(), 'disk'=&gt;disk(), 'temp'=&gt;temp(),<br>        'lan'=&gt;$net, 'speed_rx'=&gt;round(max(0,$rx_speed),1), 'speed_tx'=&gt;round(max(0,$tx_speed),1),<br>        'ips'=&gt;$ip_counts, 'total_conns'=&gt;count($ip_counts), 'term'=&gt;$_SESSION['terminal']<br>    ]);<br>    exit;<br>}<br>$local_ip = $_SERVER['SERVER_ADDR'] ?? '127.0.0.1';<br>?&gt;<br>&lt;!DOCTYPE html&gt;<br>&lt;html lang=\"es\"&gt;<br>&lt;head&gt;<br>&lt;meta charset=\"utf-8\"&gt;<br>&lt;title&gt;Admin Dashboard&lt;\/title&gt;<br>&lt;link rel=\"icon\" type=\"image\/png\" href=\"assets\/img\/logo.png\"&gt;<br>&lt;script src=\"assets\/js\/chart.umd.min.js\"&gt;&lt;\/script&gt;<br>&lt;style&gt;<br>body{background:#0f172a;color:#e5e7eb;font-family:system-ui;margin:0;padding:20px}<br>.top-actions{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-bottom:15px}<br>.top-actions button,.top-actions a{background:#1e293b;color:#fff;border:none;padding:10px 18px;border-radius:8px;cursor:pointer;text-decoration:none;font-weight:bold}<br>.card{background:#1e293b;padding:20px;border-radius:12px;margin-bottom:20px}<br>.chart-box{height:250px}<br>.net-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(130px, 1fr));gap:10px;font-size:11px;color:#94a3b8;margin-bottom:15px;padding-bottom:10px;border-bottom:1px solid #334155}<br>.net-grid b{color:#facc15;display:block;font-size:9px;text-transform:uppercase}<br>.terminal{background:#020617;border-radius:12px;padding:10px;margin-bottom:15px;border:2px solid #facc15}<br>.term-log{height:15em;overflow:auto;font-family:monospace;font-size:12px}<br>.cmd{color:#38bdf8} .ok{color:#22c55e} .err{color:#ef4444}<br>.refresh-control{display:flex;gap:15px;align-items:center;margin-bottom:15px;color:#facc15;font-size:13px}<br>.ip-list{display:flex;flex-wrap:wrap;gap:5px;margin-top:10px}<br>.ip-tag{padding:2px 6px;border-radius:4px;background:#0f172a;font-family:monospace;font-size:11px;border:1px solid transparent;display:inline-block}<br>.ip-tag.ok{border-color:#22c55e} .ip-tag.err{border-color:#ef4444}<br>select{background:#1e293b;color:#facc15;border:1px solid #facc15;padding:4px;border-radius:4px;cursor:pointer}<br>&lt;\/style&gt;<br>&lt;\/head&gt;<br>&lt;body&gt;<br><br>&lt;div class=\"terminal\"&gt;<br>    &lt;b&gt;&#x1f4df; Terminal de Eventos&lt;\/b&gt;<br>    &lt;button onclick=\"clearTerm()\" style=\"float:right;background:#facc15;border:none;padding:2px 8px;border-radius:4px;cursor:pointer;color:#000;font-weight:bold\"&gt;Limpiar&lt;\/button&gt;<br>    &lt;div class=\"term-log\" id=\"term\"&gt;&lt;\/div&gt;<br>&lt;\/div&gt;<br><br>&lt;div class=\"top-actions\"&gt;<br>    &lt;a href=\"adminplus.php\" class=\"btn-auditoria\" style=\"background:#1e293b;color:#fff;border:none;padding:10px 18px;border-radius:8px;text-decoration:none;font-weight:bold\"&gt;&#x1f6e1;&#xfe0f; Auditor\u00eda LAN&lt;\/a&gt;<br>    &lt;button onclick=\"runCmd('reiniciar_php')\"&gt;&#x1f501; PHP&lt;\/button&gt;<br>    &lt;button onclick=\"runCmd('permisos')\"&gt;&#x1f510; Permisos&lt;\/button&gt;<br>    &lt;button onclick=\"runCmd('backup')\"&gt;&#x1f4e6; Backup&lt;\/button&gt;<br>    &lt;a href=\"http:\/\/&lt;?= $local_ip ?&gt;\/phpmyadmin\" target=\"_blank\"&gt;&#x1f5a5;&#xfe0f; phpMyAdmin&lt;\/a&gt;<br>    &lt;a href=\"http:\/\/&lt;?= $local_ip ?&gt;:8080\" target=\"_blank\"&gt;&#x1f4c1; FileBrowser&lt;\/a&gt;<br>&lt;\/div&gt;<br><br>&lt;div class=\"refresh-control\"&gt;<br>    &lt;span&gt;&lt;b&gt;Refresco:&lt;\/b&gt; &lt;input type=\"range\" min=\"100\" max=\"5000\" step=\"100\" value=\"1000\" id=\"refreshDial\"&gt; &lt;span id=\"refreshVal\"&gt;1000ms&lt;\/span&gt;&lt;\/span&gt;<br>    &lt;button id=\"togglePauseBtn\" style=\"background:none;border:1px solid #facc15;color:#facc15;padding:2px 8px;border-radius:4px;cursor:pointer\"&gt;&#x23ef;&#xfe0f; Pausar&lt;\/button&gt;<br>    &lt;span&gt;&lt;b&gt;Interfaz:&lt;\/b&gt; <br>        &lt;select id=\"ifaceSelect\" onchange=\"location.href='?set_iface='+this.value\"&gt;<br>            &lt;option value=\"eth0\" &lt;?= $active_iface=='eth0'?'selected':'' ?&gt;&gt;eth0 (Cable)&lt;\/option&gt;<br>            &lt;option value=\"wlan0\" &lt;?= $active_iface=='wlan0'?'selected':'' ?&gt;&gt;wlan0 (WiFi)&lt;\/option&gt;<br>        &lt;\/select&gt;<br>    &lt;\/span&gt;<br>&lt;\/div&gt;<br><br>&lt;div class=\"card\"&gt;<br>    &lt;h3 style=\"margin-top:0\"&gt;&#x1f4ca; Carga de Sistema&lt;\/h3&gt;<br>    &lt;div class=\"chart-box\"&gt;&lt;canvas id=\"sysChart\"&gt;&lt;\/canvas&gt;&lt;\/div&gt;<br>&lt;\/div&gt;<br><br>&lt;div class=\"card\"&gt;<br>    &lt;h3 style=\"margin-top:0\"&gt;&#x1f310; Tr\u00e1fico de Red y LAN&lt;\/h3&gt;<br>    &lt;div id=\"netGrid\" class=\"net-grid\"&gt;&lt;\/div&gt;<br>    &lt;div class=\"chart-box\"&gt;&lt;canvas id=\"lanChart\"&gt;&lt;\/canvas&gt;&lt;\/div&gt;<br>    &lt;h4&gt;&#x1f50c; Clientes Conectados (\u00danicos): &lt;span id=\"connTotal\" style=\"color:#facc15\"&gt;0&lt;\/span&gt;&lt;\/h4&gt;<br>    &lt;div id=\"ipMonitor\" class=\"ip-list\"&gt;&lt;\/div&gt;<br>&lt;\/div&gt;<br><br>&lt;script&gt;<br>let paused = false, refreshMs = 1000, timer = null;<br>const yellowAxis = { ticks:{color:'#facc15', font:{size:10}}, grid:{color:'rgba(250,204,21,0.05)'} };<br>const chartOpts = { borderWidth: 1.0, pointRadius: 1, tension: 0.3 };<br><br>const sysChart = new Chart(document.getElementById('sysChart'),{<br>    type:'line',<br>    data:{labels:[],datasets:[<br>        {label:'CPU %',data:[],borderColor:'#3b82f6', ...chartOpts},<br>        {label:'MEM %',data:[],borderColor:'#10b981', ...chartOpts},<br>        {label:'DISK %',data:[],borderColor:'#f59e0b', ...chartOpts},<br>        {label:'TEMP \u00b0C',data:[],borderColor:'#ef4444', ...chartOpts}<br>    ]},<br>    options:{responsive:true,maintainAspectRatio:false,animation:false,scales:{x:yellowAxis,y:yellowAxis},plugins:{legend:{labels:{color:'#facc15'}}}}<br>});<br><br>const lanChart = new Chart(document.getElementById('lanChart'),{<br>    type:'line',<br>    data:{labels:[],datasets:[<br>        {label:'RX KB\/s',data:[],borderColor:'#22c55e', fill:true, backgroundColor:'rgba(34,197,94,0.05)', ...chartOpts},<br>        {label:'TX KB\/s',data:[],borderColor:'#3b82f6', fill:true, backgroundColor:'rgba(59,130,246,0.05)', ...chartOpts}<br>    ]},<br>    options:{responsive:true,maintainAspectRatio:false,animation:false,scales:{x:yellowAxis,y:yellowAxis},plugins:{legend:{labels:{color:'#facc15'}}}}<br>});<br><br>function runCmd(c){ fetch(`?action=run&amp;cmd=${c}`).then(()=&gt;update(true)); }<br>function clearTerm(){ fetch('?action=clear_term').then(()=&gt;update(true)); }<br>function startTimer(){ timer=setInterval(update,refreshMs); }<br><br>document.getElementById('refreshDial').oninput=e=&gt;{<br>    refreshMs=parseInt(e.target.value); document.getElementById('refreshVal').innerText=refreshMs+'ms';<br>    if(!paused){ clearInterval(timer); startTimer(); }<br>};<br><br>document.getElementById('togglePauseBtn').onclick=()=&gt;{<br>    paused=!paused; document.getElementById('togglePauseBtn').innerText=paused?\"&#x25b6;&#xfe0f; Continuar\":\"&#x23ef;&#xfe0f; Pausar\";<br>    if(paused) clearInterval(timer); else startTimer();<br>};<br><br>function update(force=false){<br>    if(paused &amp;&amp; !force) return;<br>    fetch('?json=1').then(r=&gt;r.json()).then(d=&gt;{<br>        let t=new Date().toLocaleTimeString();<br>        sysChart.data.labels.push(t);<br>        [d.cpu, d.mem, d.disk, d.temp].forEach((v,i)=&gt;{<br>            sysChart.data.datasets[i].data.push(v);<br>            sysChart.data.datasets[i].label = sysChart.data.datasets[i].label.split(':')[0] + ': ' + v;<br>            if(sysChart.data.datasets[i].data.length&gt;50) sysChart.data.datasets[i].data.shift();<br>        });<br>        if(sysChart.data.labels.length&gt;50) sysChart.data.labels.shift();<br>        sysChart.update();<br><br>        lanChart.data.labels.push(t);<br>        [d.speed_rx, d.speed_tx].forEach((v,i)=&gt;{<br>            lanChart.data.datasets[i].data.push(v);<br>            lanChart.data.datasets[i].label = lanChart.data.datasets[i].label.split(':')[0] + ': ' + v + ' KB\/s';<br>            if(lanChart.data.datasets[i].data.length&gt;50) lanChart.data.datasets[i].data.shift();<br>        });<br>        if(lanChart.data.labels.length&gt;50) lanChart.data.labels.shift();<br>        lanChart.update();<br><br>        document.getElementById('netGrid').innerHTML = `<br>            &lt;div&gt;&lt;b&gt;Interface&lt;\/b&gt;${d.lan.interface} (${d.lan.state})&lt;\/div&gt;<br>            &lt;div&gt;&lt;b&gt;IP Local&lt;\/b&gt;${d.lan.ip}&lt;\/div&gt;<br>            &lt;div&gt;&lt;b&gt;M\u00e1scara&lt;\/b&gt;\/${d.lan.mask}&lt;\/div&gt;<br>            &lt;div&gt;&lt;b&gt;Gateway&lt;\/b&gt;${d.lan.gateway}&lt;\/div&gt;<br>            &lt;div&gt;&lt;b&gt;DNS&lt;\/b&gt;${d.lan.dns}&lt;\/div&gt;<br>            &lt;div&gt;&lt;b&gt;IP P\u00fablica&lt;\/b&gt;${d.lan.public}&lt;\/div&gt;<br>            &lt;div&gt;&lt;b&gt;MAC&lt;\/b&gt;${d.lan.mac}&lt;\/div&gt;<br>        `;<br>        document.getElementById('connTotal').innerText = d.total_conns;<br>        document.getElementById('ipMonitor').innerHTML = Object.entries(d.ips).map(([ip, info]) =&gt; <br>            `&lt;span class=\"ip-tag ${info.state}\"&gt;${ip} ${info.count &gt; 1 ? '(x'+info.count+')' : ''}&lt;\/span&gt;`<br>        ).join('');<br>        document.getElementById('term').innerHTML = d.term.map(l=&gt;`&lt;div class=\"${l.type}\"&gt;[${l.t}] ${l.msg}&lt;\/div&gt;`).join('');<br>        document.getElementById('term').scrollTop = document.getElementById('term').scrollHeight;<br>    });<br>}<br>startTimer(); update(true);<br>&lt;\/script&gt;<br>&lt;\/body&gt;<br>&lt;\/html&gt;&lt;\/body&gt;<br>&lt;\/html&gt;&lt;\/html&gt;<br><\/pre>\n\n\n\n<p class=\"has-medium-font-size\"><strong>Dashboard Secundarario (Auditoria LAN):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-preformatted has-small-font-size\">&lt;?php<br>session_start();<br>if (!isset($_SESSION['auth'])) { header(\"Location: index.php\"); exit; }<br><br>\/* ========= CONFIGURACI\u00d3N DE RUTAS DIN\u00c1MICAS ========= *\/<br>$logDir = \"\/var\/www\/apps\/admin-scripts\/\";<br>$currentDate = date(\"Y-m-d\");<br>\/\/ Ahora el log principal es el del d\u00eda actual<br>$logFile = $logDir . \"auditoria_\" . $currentDate . \".log\";<br><br>\/* ========= EXPORTACI\u00d3N CSV SELECTIVA (Mejorado) ========= *\/<br>if (isset($_GET['export_csv'])) {<br>    \/\/ Si viene un archivo espec\u00edfico por GET, lo usamos; si no, el de hoy<br>    $targetLog = isset($_GET['file']) ? $logDir . $_GET['file'] : $logFile;<br>    <br>    if (file_exists($targetLog) &amp;&amp; strpos(basename($targetLog), 'auditoria_') === 0) {<br>        header('Content-Type: text\/csv; charset=utf-8');<br>        header('Content-Disposition: attachment; filename=' . str_replace('.log', '.csv', basename($targetLog)));<br>        <br>        $output = fopen('php:\/\/output', 'w');<br>        fputcsv($output, ['Fecha y Hora', 'Descripci\u00f3n del Evento']);<br>        <br>        $lines = file($targetLog, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);<br>        foreach ($lines as $line) {<br>            if (preg_match('\/^\\[(.*?)\\] (.*)$\/', $line, $matches)) {<br>                fputcsv($output, [$matches[1], $matches[2]]);<br>            } else {<br>                fputcsv($output, ['', $line]);<br>            }<br>        }<br>        fclose($output);<br>        exit;<br>    }<br>}<br><br>\/* ========= L\u00d3GICA DE ELIMINACI\u00d3N POR BLOQUES ========= *\/<br>if (isset($_GET['delete_mode'])) {<br>    $mode = $_GET['delete_mode'];<br>    $files = glob($logDir . \"auditoria_*.log\");<br>    foreach ($files as $f) {<br>        if ($mode == 'all') @unlink($f);<br>        if ($mode == 'today' &amp;&amp; strpos($f, $currentDate) !== false) @unlink($f);<br>        if ($mode == 'week' &amp;&amp; filemtime($f) &lt; (time() - (7 * 86400))) @unlink($f);<br>    }<br>    header(\"Location: adminplus.php\"); exit;<br>}<br><br>\/* ========= FUNCIONES DE AUDITOR\u00cdA (Tus funciones originales) ========= *\/<br><br>function get_ip_geo($ip) {<br>    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {<br>        $details = @json_decode(file_get_contents(\"http:\/\/ip-api.com\/json\/{$ip}?fields=status,country,city,isp\"), true);<br>        return ($details &amp;&amp; $details['status'] === 'success') ? \"&#x1f4cd; {$details['city']}, {$details['country']} ({$details['isp']})\" : \"&#x1f310; IP P\u00fablica\";<br>    }<br>    return \"&#x1f3e0; Red Local \/ VPN\";<br>}<br><br>function get_hostname($ip) {<br>    $host = @gethostbyaddr($ip);<br>    return ($host &amp;&amp; $host !== $ip) ? \"&#x1f3f7;&#xfe0f; $host\" : \"&#x2753; Desconocido\";<br>}<br><br>function write_audit_log($msg) {<br>    global $logFile;<br>    $date = date(\"Y-m-d H:i:s\");<br>    if (is_writable(dirname($logFile)) || (file_exists($logFile) &amp;&amp; is_writable($logFile))) {<br>        file_put_contents($logFile, \"[$date] $msg\\n\", FILE_APPEND);<br>    }<br>}<br><br>\/* ========= PROCESAMIENTO AJAX (Tu l\u00f3gica original) ========= *\/<br>if (isset($_GET['ajax'])) {<br>    header(\"Content-Type: application\/json\");<br>    <br>    $raw_conns = shell_exec(\"ss -tun | awk 'NR&gt;1 {print $6}' | sed 's\/\\[\/\/g; s\/\\]\/\/g' | cut -d: -f1 | sort -u\");<br>    $geo_data = [];<br>    $lines = explode(\"\\n\", trim($raw_conns));<br>    <br>    foreach ($lines as $ip) {<br>        $ip = trim($ip);<br>        if (!empty($ip) &amp;&amp; !in_array($ip, ['127.0.0.1', '::1', '0.0.0.0', '*'])) {<br>            $geo = get_ip_geo($ip);<br>            $host = get_hostname($ip);<br>            $geo_data[$ip] = ['geo' =&gt; $geo, 'host' =&gt; $host];<br>            <br>            $type = (strpos($geo, '&#x1f4cd;') !== false) ? \"EXTERNA\" : \"LOCAL\";<br>            write_audit_log(\"Conexi\u00f3n $type: $ip ($host)\");<br>        }<br>    }<br><br>    $temp = floatval(@file_get_contents(\"\/sys\/class\/thermal\/thermal_zone0\/temp\") \/ 1000);<br>    if ($temp &gt; 75) write_audit_log(\"&#x26a0;&#xfe0f; CR\u00cdTICO: Temperatura CPU {$temp}\u00b0C\");<br><br>    $disk_usage = intval(shell_exec(\"df \/ | awk 'NR==2{gsub(\/%\/,\\\"\\\",$5); print $5}'\"));<br>    if ($disk_usage &gt; 90) write_audit_log(\"&#x26a0;&#xfe0f; CR\u00cdTICO: Disco casi lleno ({$disk_usage}%)\");<br><br>    echo json_encode([<br>        'uptime' =&gt; shell_exec(\"uptime -p\"),<br>        'cpu_detail' =&gt; shell_exec(\"ps -eo %cpu,%mem,cmd --sort=-%cpu | head -n 10\"),<br>        'ports' =&gt; shell_exec(\"netstat -tulnp | awk 'NR&gt;2 {print $4, \\\"-&gt;\\\", $7}' | sort -u\"),<br>        'geo_audit' =&gt; $geo_data,<br>        'logs' =&gt; file_exists($logFile) ? nl2br(htmlspecialchars(shell_exec(\"tail -n 25 $logFile\"))) : \"Sin eventos hoy.\"<br>    ]);<br>    exit;<br>}<br>?&gt;<br>&lt;!DOCTYPE html&gt;<br>&lt;html lang=\"es\"&gt;<br>&lt;head&gt;<br>    &lt;meta charset=\"utf-8\"&gt;<br>    &lt;title&gt;Auditor\u00eda LAN Plus&lt;\/title&gt;<br>    &lt;link rel=\"icon\" type=\"image\/png\" href=\"\/assets\/img\/logo.png\"&gt;<br>    <br>    &lt;style&gt;<br>        body{background:#0f172a;color:#e5e7eb;font-family:system-ui;margin:0;padding:20px}<br>        .grid{display:flex; flex-direction: column; gap:20px}<br>        .card{background:#1e293b;padding:20px;border-radius:12px;border:1px solid #334155; width: 100%; box-sizing: border-box;}<br>        .top-actions{display:flex;gap:10px;margin-bottom:20px;justify-content:space-between; align-items: center; flex-wrap: wrap}<br>        .btn-group{display:flex; gap:10px; align-items: center}<br>        .btn{padding:10px 18px;border-radius:8px;text-decoration:none;font-weight:bold;color:#fff;border:none;cursor:pointer; font-size: 14px}<br>        .btn-blue{background:#3b82f6} .btn-red{background:#ef4444} .btn-green{background:#10b981}<br>        <br>        .log-box{<br>            background:#020617;<br>            padding:15px;<br>            border-radius:8px;<br>            font-family:monospace;<br>            font-size:12px;<br>            height: 32em;<br>            overflow-y: auto;<br>            border:1px solid #facc15;<br>            color:#facc15; <br>            line-height: 1.6;<br>        }<br><br>        pre{font-size:12px;color:#38bdf8;background:#0f172a;padding:15px;border-radius:8px;overflow-x:auto; white-space: pre-wrap}<br>        h3{color:#facc15;margin-top:0;display:flex;align-items:center;gap:10px;font-size:18px; border-bottom: 1px solid #334155; padding-bottom: 10px}<br>        .geo-item{padding:12px;border-bottom:1px solid #334155;font-size:13px; display: flex; justify-content: space-between}<br>        select{padding:10px; border-radius:8px; background:#334155; color:#fff; border:1px solid #475569}<br>    &lt;\/style&gt;<br>&lt;\/head&gt;<br>&lt;body&gt;<br><br>    &lt;div class=\"top-actions\"&gt;<br>        &lt;a href=\"admin.php\" class=\"btn btn-blue\"&gt;&#x1f519; Volver&lt;\/a&gt;<br>        &lt;h2 style=\"margin:0; font-size:20px\"&gt;&#x1f6e1;&#xfe0f; Auditor\u00eda Full de Pantalla&lt;\/h2&gt;<br>        &lt;div class=\"btn-group\"&gt;<br>            &lt;select id=\"fileSelector\"&gt;<br>                &lt;?php<br>                $logFiles = array_reverse(glob($logDir . \"auditoria_*.log\"));<br>                foreach($logFiles as $f) {<br>                    $base = basename($f);<br>                    echo \"&lt;option value='$base'&gt;$base&lt;\/option&gt;\";<br>                }<br>                ?&gt;<br>            &lt;\/select&gt;<br>            &lt;button onclick=\"downloadCSV()\" class=\"btn btn-green\"&gt;&#x1f4ca; Exportar&lt;\/button&gt;<br>            &lt;button onclick=\"deleteLogs()\" class=\"btn btn-red\"&gt;&#x1f5d1;&#xfe0f; Limpiar&lt;\/button&gt;<br>        &lt;\/div&gt;<br>    &lt;\/div&gt;<br><br>    &lt;div class=\"grid\"&gt;<br>        &lt;div class=\"card\"&gt;<br>            &lt;h3&gt;&#x23f1;&#xfe0f; Historial de Eventos (D\u00eda: &lt;?php echo $currentDate; ?&gt;)&lt;\/h3&gt;<br>            &lt;p style=\"font-size:14px; margin-bottom: 10px\"&gt;&lt;b&gt;Uptime:&lt;\/b&gt; &lt;span id=\"uptime\"&gt;...&lt;\/span&gt;&lt;\/p&gt;<br>            &lt;div class=\"log-box\" id=\"logContent\"&gt;Cargando...&lt;\/div&gt;<br>        &lt;\/div&gt;<br>        <br>        &lt;div class=\"card\"&gt;<br>            &lt;h3&gt;&#x1f30d; Geolocalizaci\u00f3n e IPs&lt;\/h3&gt;<br>            &lt;div id=\"geoList\"&gt;Buscando conexiones...&lt;\/div&gt;<br>        &lt;\/div&gt;<br><br>        &lt;div class=\"card\"&gt;<br>            &lt;h3&gt;&#x1f525; Procesos Top&lt;\/h3&gt;<br>            &lt;pre id=\"procData\"&gt;...&lt;\/pre&gt;<br>        &lt;\/div&gt;<br><br>        &lt;div class=\"card\"&gt;<br>            &lt;h3&gt;&#x1f6aa; Servicios \/ Puertos&lt;\/h3&gt;<br>            &lt;pre id=\"portData\"&gt;...&lt;\/pre&gt;<br>        &lt;\/div&gt;<br>    &lt;\/div&gt;<br><br>    &lt;script&gt;<br>        function downloadCSV() {<br>            const file = document.getElementById('fileSelector').value;<br>            if(file) window.location.href = `?export_csv=1&amp;file=${file}`;<br>        }<br><br>        function deleteLogs() {<br>            const opc = prompt(\"\u00bfQu\u00e9 deseas borrar?\\n1: Solo hoy\\n2: Historial antiguo (m\u00e1s de 1 semana)\\n3: TODO el historial\\nEscribe el n\u00famero:\");<br>            if(opc == \"1\") { if(confirm(\"\u00bfBorrar log de hoy?\")) window.location.href = \"?delete_mode=today\"; }<br>            else if(opc == \"2\") { if(confirm(\"\u00bfBorrar logs de m\u00e1s de 7 d\u00edas?\")) window.location.href = \"?delete_mode=week\"; }<br>            else if(opc == \"3\") { if(confirm(\"\u00a1ALERTA! Esto borrar\u00e1 todos los registros hist\u00f3ricos. \u00bfContinuar?\")) window.location.href = \"?delete_mode=all\"; }<br>        }<br><br>        function updatePlus() {<br>            fetch('?ajax=1').then(r =&gt; r.json()).then(d =&gt; {<br>                document.getElementById('uptime').innerText = d.uptime;<br>                <br>                const logContainer = document.getElementById('logContent');<br>                logContainer.innerHTML = d.logs;<br>                logContainer.scrollTop = logContainer.scrollHeight;<br><br>                document.getElementById('procData').innerText = d.cpu_detail;<br>                document.getElementById('portData').innerText = d.ports;<br>                <br>                let geoHTML = '';<br>                const entries = Object.entries(d.geo_audit);<br>                if(entries.length &gt; 0) {<br>                    for (const [ip, info] of entries) {<br>                        geoHTML += `&lt;div style=\"padding:10px; border-bottom:1px solid #334155; display:flex; justify-content:space-between\"&gt;<br>                            &lt;span&gt;&lt;b&gt;${ip}&lt;\/b&gt;&lt;br&gt;&lt;small style=\"color:#94a3b8\"&gt;${info.host}&lt;\/small&gt;&lt;\/span&gt;<br>                            &lt;span style=\"text-align: right\"&gt;${info.geo}&lt;\/span&gt;<br>                        &lt;\/div&gt;`;<br>                    }<br>                } else {<br>                    geoHTML = '&lt;p style=\"padding:15px; color:#94a3b8\"&gt;Solo conexiones locales.&lt;\/p&gt;';<br>                }<br>                document.getElementById('geoList').innerHTML = geoHTML;<br>            });<br>        }<br>        setInterval(updatePlus, 3000); <br>        updatePlus();<br>    &lt;\/script&gt;<br>&lt;\/body&gt;<br>&lt;\/html&gt;&lt;\/body&gt;<br>&lt;\/html&gt;<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">2&#xfe0f;&#x20e3; Crear el archivo del dashboard (pero no lo usaremos &#x1f605;)<\/h2>\n\n\n\n<p>Archivo: <code>\/var\/www\/html\/dashboard.php<\/code> <\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>sudo nano <span style=\"background-color: initial; font-family: inherit; font-size: inherit; text-align: initial; color: initial;\">\/var\/www\/html\/dashboard.php<\/span><\/code><\/pre>\n\n\n\n<pre class=\"wp-block-preformatted has-small-font-size\">&lt;?php\nsession_start();\n$secret_pass = \"TU_PASSWORD_SEGURA\"; \/\/ Cambiar antes de producci\u00f3n\n\n\/\/ Login simple\nif(isset($_POST['password'])){\n    if($_POST['password'] === $secret_pass){\n        $_SESSION['logged_in'] = true;\n    } else {\n        $error = \"Contrase\u00f1a incorrecta\";\n    }\n}\n\nif(!isset($_SESSION['logged_in'])){\n?&gt;\n&lt;div class=\"login panel\"&gt;\n    &lt;img src=\"\/assets\/img\/logo.png\" class=\"logo\" alt=\"Logo\"&gt;\n    &lt;form method=\"POST\"&gt;\n        &lt;input type=\"password\" name=\"password\" placeholder=\"Contrase\u00f1a\"&gt;\n        &lt;input type=\"submit\" value=\"Entrar\"&gt;\n        &lt;?php if(isset($error)) echo \"&lt;p style='color:red;'&gt;$error&lt;\/p&gt;\"; ?&gt;\n    &lt;\/form&gt;\n&lt;\/div&gt;\n&lt;?php exit; }\n\n\/\/ Funci\u00f3n para ejecutar comandos seguros\nfunction runCommand($cmd){\n    $output = shell_exec($cmd . \" 2&gt;&amp;1\");\n    return htmlspecialchars($output);\n}\n\n\/\/ Recolectar datos del sistema\n$ip = runCommand(\"hostname -I | awk '{print $1}'\");\n$mac = runCommand(\"cat \/sys\/class\/net\/eth0\/address\");\n$gateway = runCommand(\"ip route | grep default | awk '{print $3}'\");\n$dns = runCommand(\"systemd-resolve --status | grep 'DNS Servers' | head -n1 | awk '{print $3}'\");\n$cpu_load = runCommand(\"top -bn1 | grep 'Cpu(s)' | awk '{print $2 + $4}'\");\n$mem_usage = runCommand(\"free -m | awk 'NR==2{printf \\\"%s\/%s MB\\\", $3,$2 }'\");\n$disk_usage = runCommand(\"df -h \/ | awk 'NR==2{printf \\\"%s\/%s\\\", $3,$2 }'\");\n$file_count = runCommand(\"find \/var\/www\/apps -type f | wc -l\");\n$connections = runCommand(\"ss -tn | awk 'NR&gt;1 {print $5}' | cut -d':' -f1 | sort | uniq -c | sort -nr\");\n?&gt;\n&lt;!DOCTYPE html&gt;\n&lt;html lang=\"es\"&gt;\n&lt;head&gt;\n    &lt;meta charset=\"UTF-8\"&gt;\n    &lt;title&gt;Mini CPanel Dashboard&lt;\/title&gt;\n    &lt;link rel=\"stylesheet\" href=\"assets\/css\/panel.css\"&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n    &lt;div class=\"panel\"&gt;\n        &lt;img src=\"assets\/img\/logo.png\" class=\"logo\" alt=\"Logo\"&gt;\n        &lt;h1&gt;Mini CPanel Dashboard&lt;\/h1&gt;\n\n        &lt;div class=\"cards\"&gt;\n            &lt;div class=\"card\"&gt;&lt;h2&gt;IP&lt;\/h2&gt;&lt;p&gt;&lt;?php echo $ip; ?&gt;&lt;\/p&gt;&lt;\/div&gt;\n            &lt;div class=\"card\"&gt;&lt;h2&gt;MAC&lt;\/h2&gt;&lt;p&gt;&lt;?php echo $mac; ?&gt;&lt;\/p&gt;&lt;\/div&gt;\n            &lt;div class=\"card\"&gt;&lt;h2&gt;Gateway&lt;\/h2&gt;&lt;p&gt;&lt;?php echo $gateway; ?&gt;&lt;\/p&gt;&lt;\/div&gt;\n            &lt;div class=\"card\"&gt;&lt;h2&gt;DNS&lt;\/h2&gt;&lt;p&gt;&lt;?php echo $dns; ?&gt;&lt;\/p&gt;&lt;\/div&gt;\n            &lt;div class=\"card\"&gt;&lt;h2&gt;CPU %&lt;\/h2&gt;&lt;p&gt;&lt;?php echo $cpu_load; ?&gt;&lt;\/p&gt;&lt;\/div&gt;\n            &lt;div class=\"card\"&gt;&lt;h2&gt;RAM&lt;\/h2&gt;&lt;p&gt;&lt;?php echo $mem_usage; ?&gt;&lt;\/p&gt;&lt;\/div&gt;\n            &lt;div class=\"card\"&gt;&lt;h2&gt;Disco&lt;\/h2&gt;&lt;p&gt;&lt;?php echo $disk_usage; ?&gt;&lt;\/p&gt;&lt;\/div&gt;\n            &lt;div class=\"card\"&gt;&lt;h2&gt;Archivos Apps&lt;\/h2&gt;&lt;p&gt;&lt;?php echo $file_count; ?&gt;&lt;\/p&gt;&lt;\/div&gt;\n        &lt;\/div&gt;\n\n        &lt;h2&gt;Conexiones activas&lt;\/h2&gt;\n        &lt;pre style=\"text-align:left; max-height:200px; overflow:auto; background:#1e293b; padding:10px; border-radius:8px;\"&gt;&lt;?php echo $connections; ?&gt;&lt;\/pre&gt;\n    &lt;\/div&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">3&#xfe0f;&#x20e3; Crear CSS b\u00e1sico<\/h2>\n\n\n\n<p>Archivo: <code>\/var\/www\/html\/assets\/css\/panel.css<\/code><\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>sudo nano <span style=\"background-color: initial; font-family: inherit; font-size: inherit; text-align: initial; color: initial;\">\/var\/www\/html\/assets\/css\/panel.css<\/span><\/code><\/pre>\n\n\n\n<pre class=\"wp-block-preformatted has-small-font-size\">body {\n    background: #0f172a;\n    color: #e5e7eb;\n    font-family: system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n    margin: 0;\n}\n\n.logo {\n    max-width: 120px;\n    height: auto;\n    margin-bottom: 20px;\n    opacity: 0.9;\n}\n\n\/* --- SEPARACI\u00d3N DE LOGIN Y PANEL --- *\/\n\n\/* El login se mantiene estrecho y centrado *\/\n.login {\n    max-width: 420px;\n    margin: 120px auto;\n    text-align: center;\n    padding: 20px;\n}\n\n\/* El panel ahora ocupar\u00e1 casi todo el ancho de la pantalla *\/\n.panel {\n    max-width: 95%; \/* Cambiado de 420px a 95% *\/\n    margin: 40px auto;\n    text-align: center;\n}\n\n\/* --- ELEMENTOS COMUNES --- *\/\n\ninput, button {\n    width: 100%;\n    padding: 12px;\n    margin-top: 10px;\n    border-radius: 8px;\n    border: none;\n    font-size: 14px;\n    box-sizing: border-box; \/* Importante para que el padding no rompa el ancho *\/\n}\n\nbutton {\n    background: #3b82f6;\n    color: white;\n    cursor: pointer;\n    font-weight: bold;\n}\n\nbutton:hover {\n    background: #2563eb;\n}\n\n\/* Estilos de los enlaces\/botones del panel *\/\n.panel a {\n    display: inline-block; \/* Cambiado de block a inline-block para que respeten el flex de arriba *\/\n    margin: 5px;\n    padding: 12px 20px;\n    background: #1e293b;\n    color: #e5e7eb;\n    text-decoration: none;\n    border-radius: 8px;\n    transition: background 0.3s;\n}\n\n.panel a:hover {\n    background: #334155;\n}<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"has-medium-font-size\"><strong>&#x1f9ef; ERRORES en la EJECUCI\u00d3N de los Scripts<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-preformatted has-small-font-size\">Correcci\u00f3n de Permisos: me he encontrado que los botones que apuntan a los Scripts, no funcionan y ha ocado aplicar estas correcciones a trav\u00e9s de PuTTY:\n\n1. Normalizaci\u00f3n de Permisos de Carpeta\nPrimero, corregimos la propiedad y los permisos de acceso para que el usuario del servidor web (www-data) pudiera \"ver\" los scripts.\n\nBash\n# Cambiar el due\u00f1o de la carpeta al usuario del servidor web\nsudo chown -R www-data:www-data \/var\/www\/apps\n\n# Dar permisos de lectura y ejecuci\u00f3n a las carpetas\nsudo chmod -R 755 \/var\/www\/apps\n2. Activaci\u00f3n de Ejecuci\u00f3n de Scripts\nEste comando fue vital para que Linux permitiera que los archivos .sh funcionaran como programas y no como simples archivos de texto.\n\nBash\n# Convertir los archivos .sh en ejecutables\nsudo chmod +x \/var\/www\/apps\/admin-scripts\/*.sh\n3. Configuraci\u00f3n de Privilegios Especiales (Sudoers)\nEsta es la parte m\u00e1s sensible. Entramos al archivo de configuraci\u00f3n de seguridad de Linux para autorizar la ejecuci\u00f3n sin contrase\u00f1a.\n\nComando para entrar:\n\nBash\nsudo visudo\nL\u00edneas a\u00f1adidas al final del archivo:\n\nPlaintext\nwww-data ALL=(ALL) NOPASSWD: \/var\/www\/apps\/admin-scripts\/reiniciar-php.sh\nwww-data ALL=(ALL) NOPASSWD: \/var\/www\/apps\/admin-scripts\/ajustar-permisos.sh\nwww-data ALL=(ALL) NOPASSWD: \/var\/www\/apps\/admin-scripts\/backup.sh\n4. Resoluci\u00f3n del Error en \/tmp\nPara el script de reinicio de PHP, habilitamos el archivo \"flag\" que el sistema utiliza para comunicarse con el servicio FPM.\n\nBash\n# Crear el archivo manualmente y darle permisos de escritura universal\nsudo touch \/tmp\/restart_php_fpm.flag\nsudo chmod 666 \/tmp\/restart_php_fpm.flag<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">&#x2705; Resultado esperado<\/h2>\n\n\n\n<p>Al abrir <code>http:\/\/IP_DE_LA_RASPI\/dashboard.php<\/code>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Solicita contrase\u00f1a (la misma que <code>admin.php<\/code>)<\/li>\n\n\n\n<li>Muestra tarjetas con IP, MAC, CPU, RAM, disco y n\u00famero de archivos<\/li>\n\n\n\n<li>Lista conexiones activas<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"919\" height=\"886\" src=\"https:\/\/www.sinetiqueta.com\/wp-content\/uploads\/2026\/02\/image-8.png\" alt=\"\" class=\"wp-image-3404\" srcset=\"https:\/\/www.sinetiqueta.com\/wp-content\/uploads\/2026\/02\/image-8.png 919w, https:\/\/www.sinetiqueta.com\/wp-content\/uploads\/2026\/02\/image-8-300x289.png 300w, https:\/\/www.sinetiqueta.com\/wp-content\/uploads\/2026\/02\/image-8-768x740.png 768w\" sizes=\"auto, (max-width: 919px) 100vw, 919px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"928\" height=\"872\" src=\"https:\/\/www.sinetiqueta.com\/wp-content\/uploads\/2026\/02\/image-9.png\" alt=\"\" class=\"wp-image-3405\" srcset=\"https:\/\/www.sinetiqueta.com\/wp-content\/uploads\/2026\/02\/image-9.png 928w, https:\/\/www.sinetiqueta.com\/wp-content\/uploads\/2026\/02\/image-9-300x282.png 300w, https:\/\/www.sinetiqueta.com\/wp-content\/uploads\/2026\/02\/image-9-768x722.png 768w\" sizes=\"auto, (max-width: 928px) 100vw, 928px\" \/><\/figure>\n","protected":false},"excerpt":{"rendered":"<p>Continuaci\u00f3n de: Mini CPanel Profesional \u2013 Procedimiento Completo para Raspberry Pi 3B En este art\u00edculo vamos a mejorar nuestro Mini CPanel, haci\u00e9ndolo m\u00e1s profesional, visual y funcional. Esto incluye la configuraci\u00f3n de red, accesos r\u00e1pidos a herramientas, logs, y un&#8230; <a href=\"https:\/\/www.sinetiqueta.com\/?p=3353\" class=\"readmore\">Leer m\u00e1s<span class=\"screen-reader-text\">&#x1f7e2; Mini CPanel \u2013 Mejora: Dashboard, Red y Accesos Profesionales (EN DESARROLLO)<\/span><span class=\"fa fa-angle-double-right\" aria-hidden=\"true\"><\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":3399,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[236,498,308],"tags":[],"class_list":["post-3353","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-raspberry-pi","category-servidor","category-ubuntu-server","content-layout-excerpt-thumb"],"jetpack_featured_media_url":"https:\/\/www.sinetiqueta.com\/wp-content\/uploads\/2026\/02\/image-7.png","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.sinetiqueta.com\/index.php?rest_route=\/wp\/v2\/posts\/3353","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.sinetiqueta.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sinetiqueta.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sinetiqueta.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sinetiqueta.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=3353"}],"version-history":[{"count":10,"href":"https:\/\/www.sinetiqueta.com\/index.php?rest_route=\/wp\/v2\/posts\/3353\/revisions"}],"predecessor-version":[{"id":3406,"href":"https:\/\/www.sinetiqueta.com\/index.php?rest_route=\/wp\/v2\/posts\/3353\/revisions\/3406"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.sinetiqueta.com\/index.php?rest_route=\/wp\/v2\/media\/3399"}],"wp:attachment":[{"href":"https:\/\/www.sinetiqueta.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3353"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sinetiqueta.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3353"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sinetiqueta.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3353"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}