📁 /
⬆️ Up
| Path: /home/uby7irpj/public_html/filemanager.aiphp.web.id/
Nama
Size
Aksi
📁
..
-
Delete
📁
.well-known
-
Delete
📁
cgi-bin
-
Delete
📄 error_log
5.2 KB
View
Edit
Delete
📄 filemanager.php
30 KB
View
Edit
Delete
📄 index.php
4.1 KB
View
Edit
Delete
Upload File
Upload
Buat File Baru
Buat
Buat Folder
Buat
Edit: filemanager.php
<?php /* * Simple File Manager - net2ftp style - No Login * Single file - PHP Native - Bootstrap 5 Mobile/Desktop * * Cara pakai: taruh file ini di folder yang mau dikelola. * ROOT = folder file ini berada. Aman dari directory traversal. * Tidak perlu login, seperti net2ftp quick mode. */ session_start(); define('FM_ROOT', realpath(__DIR__)); define('FM_VERSION', '1.0 - net2ftp style'); // Helper Functions function fm_clean_path($path) { $path = str_replace(['../', '..\\'], '', $path); $path = trim($path, '/'); return $path; } function fm_get_real_path($relative = '') { $relative = fm_clean_path($relative); $full = FM_ROOT . ($relative ? DIRECTORY_SEPARATOR . $relative : ''); $real = realpath($full); // Jika folder belum ada (untuk mkdir), cek parentnya if (!$real) { $parent = dirname($full); $realParent = realpath($parent); if ($realParent && strpos($realParent, FM_ROOT) === 0) { return $full; // izinkan path baru di dalam ROOT } return FM_ROOT; } // Security: harus di dalam ROOT if (strpos($real, FM_ROOT) !== 0) { return FM_ROOT; } return $real; } function fm_get_relative($realPath) { return ltrim(str_replace(FM_ROOT, '', $realPath), DIRECTORY_SEPARATOR); } function fm_human_size($bytes) { if ($bytes >= 1073741824) return number_format($bytes/1073741824,2).' GB'; if ($bytes >= 1048576) return number_format($bytes/1048576,2).' MB'; if ($bytes >= 1024) return number_format($bytes/1024,2).' KB'; if ($bytes > 0) return $bytes.' B'; return '0 B'; } function fm_perms($file) { return substr(sprintf('%o', fileperms($file)), -4); } function fm_is_text($file) { $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION)); $textExts = ['txt','php','html','htm','js','css','json','xml','md','log','htaccess','env','sql','sh','py','yml','yaml','ini','conf','csv']; if (in_array($ext, $textExts)) return true; // cek mime if (function_exists('mime_content_type')) { $mime = @mime_content_type($file); if (strpos($mime, 'text/') === 0) return true; } return filesize($file) < 2000000; // di bawah 2MB anggap bisa diedit } function fm_icon($file) { if (is_dir($file)) return 'bi-folder-fill text-warning'; $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION)); $map = [ 'jpg'=>'bi-file-earmark-image text-success','jpeg'=>'bi-file-earmark-image text-success','png'=>'bi-file-earmark-image text-success','gif'=>'bi-file-earmark-image text-success','webp'=>'bi-file-earmark-image text-success','svg'=>'bi-file-earmark-image text-success', 'zip'=>'bi-file-earmark-zip-fill text-warning','rar'=>'bi-file-earmark-zip-fill text-warning','tar'=>'bi-file-earmark-zip-fill text-warning','gz'=>'bi-file-earmark-zip-fill text-warning', 'php'=>'bi-filetype-php text-primary','js'=>'bi-filetype-js text-warning','css'=>'bi-filetype-css text-info','html'=>'bi-filetype-html text-danger', 'pdf'=>'bi-file-earmark-pdf-fill text-danger','mp4'=>'bi-file-earmark-play-fill text-dark','mp3'=>'bi-file-earmark-music-fill text-purple', ]; return $map[$ext] ?? 'bi-file-earmark text-secondary'; } // --- Handle Actions --- $dirParam = isset($_GET['dir']) ? fm_clean_path($_GET['dir']) : ''; $currentReal = fm_get_real_path($dirParam); if (!is_dir($currentReal)) $currentReal = FM_ROOT; $currentRelative = fm_get_relative($currentReal); $msg = ''; $msgType = 'info'; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $action = $_POST['action'] ?? ''; // CREATE FOLDER if ($action === 'mkdir') { $name = fm_clean_path($_POST['name'] ?? ''); if ($name) { $newPath = $currentReal . DIRECTORY_SEPARATOR . $name; if (!file_exists($newPath) && mkdir($newPath, 0755, true)) { $msg = "Folder '$name' berhasil dibuat"; $msgType='success'; } else $msg = "Gagal buat folder"; $msgType='danger'; } } // CREATE FILE if ($action === 'mkfile') { $name = fm_clean_path($_POST['name'] ?? ''); if ($name) { $newPath = $currentReal . DIRECTORY_SEPARATOR . $name; if (!file_exists($newPath) && file_put_contents($newPath, $_POST['content'] ?? '') !== false) { $msg = "File '$name' berhasil dibuat"; $msgType='success'; } else $msg = "Gagal buat file"; $msgType='danger'; } } // RENAME if ($action === 'rename') { $old = fm_clean_path($_POST['old'] ?? ''); $new = fm_clean_path($_POST['new'] ?? ''); if ($old && $new) { $oldPath = $currentReal . DIRECTORY_SEPARATOR . $old; $newPath = $currentReal . DIRECTORY_SEPARATOR . $new; if (file_exists($oldPath) && !file_exists($newPath) && rename($oldPath, $newPath)) { $msg = "Rename berhasil"; $msgType='success'; } else $msg = "Gagal rename"; $msgType='danger'; } } // DELETE if ($action === 'delete') { $target = fm_clean_path($_POST['target'] ?? ''); $path = $currentReal . DIRECTORY_SEPARATOR . $target; $realTarget = realpath($path); // Proteksi: jangan hapus file manager yang sedang jalan, tapi tetap tampilkan if ($realTarget && $realTarget === __FILE__) { $msg = "Tidak bisa hapus file manager yang sedang berjalan ($target)"; $msgType='warning'; } elseif (file_exists($path)) { if (is_dir($path)) { // recursive delete $it = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS); $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST); foreach($files as $file) { if ($file->isDir()) rmdir($file->getRealPath()); else unlink($file->getRealPath()); } rmdir($path); } else unlink($path); $msg = "Hapus '$target' berhasil"; $msgType='success'; } } // CHMOD if ($action === 'chmod') { $target = fm_clean_path($_POST['target'] ?? ''); $perm = $_POST['perm'] ?? ''; $path = $currentReal . DIRECTORY_SEPARATOR . $target; if (file_exists($path) && preg_match('/^[0-7]{3,4}$/', $perm)) { chmod($path, octdec($perm)); $msg = "Chmod $target ke $perm"; $msgType='success'; } } // SAVE EDIT if ($action === 'save') { $target = fm_clean_path($_POST['target'] ?? ''); $content = $_POST['content'] ?? ''; $path = $currentReal . DIRECTORY_SEPARATOR . $target; if (file_exists($path) && !is_dir($path)) { file_put_contents($path, $content); $msg = "File '$target' disimpan"; $msgType='success'; } } // UPLOAD if ($action === 'upload' && !empty($_FILES['files'])) { $count = 0; foreach ($_FILES['files']['tmp_name'] as $i => $tmp) { if (is_uploaded_file($tmp)) { $name = basename($_FILES['files']['name'][$i]); $dest = $currentReal . DIRECTORY_SEPARATOR . $name; if (move_uploaded_file($tmp, $dest)) $count++; } } $msg = "$count file berhasil diupload"; $msgType='success'; } // UPLOAD & UNZIP if ($action === 'upload_zip' && !empty($_FILES['zipfile'])) { $tmp = $_FILES['zipfile']['tmp_name']; if (is_uploaded_file($tmp)) { $zip = new ZipArchive(); if ($zip->open($tmp) === TRUE) { $zip->extractTo($currentReal); $zip->close(); $msg = "ZIP berhasil diekstrak"; $msgType='success'; } else $msg = "Gagal ekstrak ZIP"; $msgType='danger'; } } // Redirect to avoid resubmit (PRG) if ($msg) { $_SESSION['fm_msg'] = $msg; $_SESSION['fm_msg_type'] = $msgType; } header("Location: ?dir=".urlencode($currentRelative)); exit; } // DOWNLOAD if (isset($_GET['action']) && $_GET['action'] === 'download' && isset($_GET['file'])) { $file = fm_clean_path($_GET['file']); $path = $currentReal . DIRECTORY_SEPARATOR . $file; $real = realpath($path); if ($real && strpos($real, FM_ROOT) === 0 && is_file($real)) { header('Content-Description: File Transfer'); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="'.basename($real).'"'); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); header('Content-Length: ' . filesize($real)); readfile($real); exit; } } // ZIP & DOWNLOAD FOLDER if (isset($_GET['action']) && $_GET['action'] === 'zipdl' && isset($_GET['file'])) { $file = fm_clean_path($_GET['file']); $path = $currentReal . DIRECTORY_SEPARATOR . $file; $real = realpath($path); if ($real && strpos($real, FM_ROOT) === 0 && is_dir($real)) { $zipName = sys_get_temp_dir() . '/' . $file . '.zip'; $zip = new ZipArchive(); if ($zip->open($zipName, ZipArchive::CREATE | ZipArchive::OVERWRITE) === TRUE) { $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($real, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::LEAVES_ONLY); foreach ($files as $name => $f) { if (!$f->isDir()) { $filePath = $f->getRealPath(); $relativePath = substr($filePath, strlen($real) + 1); $zip->addFile($filePath, $relativePath); } } $zip->close(); header('Content-Type: application/zip'); header('Content-Disposition: attachment; filename="'.$file.'.zip"'); header('Content-Length: '.filesize($zipName)); readfile($zipName); unlink($zipName); exit; } } } // Flash message from session if (isset($_SESSION['fm_msg'])) { $msg = $_SESSION['fm_msg']; $msgType = $_SESSION['fm_msg_type']; unset($_SESSION['fm_msg'], $_SESSION['fm_msg_type']); } // LIST FILES $items = []; if (is_dir($currentReal)) { $scan = scandir($currentReal); foreach ($scan as $f) { if ($f === '.' || $f === '..') continue; // JANGAN HIDE - semua file termasuk index.php tetap tampil sesuai request $full = $currentReal . DIRECTORY_SEPARATOR . $f; if (!file_exists($full)) continue; $items[] = [ 'name' => $f, 'is_dir' => is_dir($full), 'size' => is_dir($full) ? 0 : filesize($full), 'mtime' => filemtime($full), 'perms' => fm_perms($full), 'full' => $full ]; } // sort: folder dulu, lalu nama usort($items, function($a,$b){ if ($a['is_dir'] !== $b['is_dir']) return $a['is_dir'] ? -1 : 1; return strcasecmp($a['name'], $b['name']); }); } // Breadcrumb $parts = $currentRelative ? explode(DIRECTORY_SEPARATOR, $currentRelative) : []; $breadcrumb = [['name'=>'ROOT','path'=>'']]; $build = ''; foreach ($parts as $p) { $build = $build ? $build . '/' . $p : $p; $breadcrumb[] = ['name'=>$p, 'path'=>$build]; } ?> <!doctype html> <html lang="id"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>File Manager - net2ftp style</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet"> <style> body{background:#f6f8fb} .navbar{backdrop-filter:blur(10px)} .fm-card{border:0;box-shadow:0 8px 30px rgba(0,0,0,.06);border-radius:16px} .table-hover tbody tr:hover{background:#f1f5f9} .breadcrumb-item+.breadcrumb-item::before{content:">"} .file-name{max-width:180px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} @media(min-width:768px){.file-name{max-width:320px}} .btn-icon{width:36px;height:36px;display:inline-flex;align-items:center;justify-content:center} .editor{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:13px;line-height:1.5} .dropzone{border:2px dashed #cbd5e1;border-radius:12px;padding:24px;text-align:center;background:#fff;transition:.2s} .dropzone.dragover{border-color:#3b82f6;background:#eff6ff} </style> </head> <body> <nav class="navbar navbar-expand-lg bg-white border-bottom sticky-top"> <div class="container-fluid px-3 px-lg-4"> <a class="navbar-brand fw-bold" href="?"><i class="bi bi-hdd-stack-fill text-primary"></i> FileMan <span class="badge bg-primary-subtle text-primary ms-1">net2ftp style</span></a> <button class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#topnav"><span class="navbar-toggler-icon"></span></button> <div class="collapse navbar-collapse" id="topnav"> <div class="ms-auto d-flex gap-2 mt-3 mt-lg-0 flex-wrap"> <button class="btn btn-outline-primary btn-sm" data-bs-toggle="modal" data-bs-target="#mkdirModal"><i class="bi bi-folder-plus"></i> Folder</button> <button class="btn btn-outline-primary btn-sm" data-bs-toggle="modal" data-bs-target="#mkfileModal"><i class="bi bi-file-earmark-plus"></i> File</button> <button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#uploadModal"><i class="bi bi-cloud-upload"></i> Upload</button> <button class="btn btn-outline-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#uploadZipModal"><i class="bi bi-file-zip"></i> Unzip</button> </div> </div> </div> </nav> <div class="container-fluid px-3 px-lg-4 py-3"> <?php if($msg): ?> <div class="alert alert-<?=$msgType?> alert-dismissible fade show py-2" role="alert"> <?=$msg?><button class="btn-close" data-bs-dismiss="alert"></button> </div> <?php endif; ?> <!-- Breadcrumb + Info --> <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3"> <nav aria-label="breadcrumb"> <ol class="breadcrumb mb-0 bg-white px-3 py-2 rounded-3 shadow-sm"> <?php foreach($breadcrumb as $i=>$b): ?> <?php if($i === count($breadcrumb)-1): ?> <li class="breadcrumb-item active fw-semibold"><?=$b['name']?></li> <?php else: ?> <li class="breadcrumb-item"><a class="text-decoration-none" href="?dir=<?=urlencode($b['path'])?>"><?=$b['name']?></a></li> <?php endif; ?> <?php endforeach; ?> </ol> </nav> <div class="d-flex gap-2 align-items-center"> <div class="input-group input-group-sm" style="width:220px"> <span class="input-group-text bg-white"><i class="bi bi-search"></i></span> <input id="searchInput" type="text" class="form-control" placeholder="Cari file..."> </div> <span class="badge bg-white text-secondary border fw-normal"><?=count($items)?> item</span> </div> </div> <div class="card fm-card"> <div class="table-responsive"> <table class="table table-hover align-middle mb-0"> <thead class="table-light"> <tr> <th class="ps-3">Nama</th> <th class="d-none d-md-table-cell">Ukuran</th> <th class="d-none d-lg-table-cell">Modifikasi</th> <th class="d-none d-md-table-cell">Perm</th> <th class="text-end pe-3">Aksi</th> </tr> </thead> <tbody id="fileTable"> <?php if($currentRelative !== ''): ?> <tr> <td class="ps-3"><a class="text-decoration-none fw-semibold" href="?dir=<?=urlencode(dirname($currentRelative) === '.' ? '' : dirname($currentRelative))?>"><i class="bi bi-arrow-90deg-up me-2"></i>.. (Naik)</a></td> <td colspan="4"></td> </tr> <?php endif; ?> <?php foreach($items as $it): $isDir = $it['is_dir']; $name = $it['name']; ?> <tr class="file-row" data-name="<?=htmlspecialchars(strtolower($name))?>"> <td class="ps-3"> <div class="d-flex align-items-center gap-2"> <i class="bi <?=fm_icon($it['full'])?> fs-5"></i> <?php if($isDir): ?> <a class="text-decoration-none text-dark fw-medium file-name" href="?dir=<?=urlencode($currentRelative ? $currentRelative.'/'.$name : $name)?>"><?=htmlspecialchars($name)?></a> <?php else: ?> <span class="file-name" title="<?=htmlspecialchars($name)?>"><?=htmlspecialchars($name)?></span> <?php endif; ?> </div> </td> <td class="d-none d-md-table-cell small text-muted"><?= $isDir ? '--' : fm_human_size($it['size']) ?></td> <td class="d-none d-lg-table-cell small text-muted"><?= date('d/m/Y H:i', $it['mtime']) ?></td> <td class="d-none d-md-table-cell"><code class="small"><?=$it['perms']?></code></td> <td class="text-end pe-3"> <div class="btn-group btn-group-sm"> <?php if(!$isDir): ?> <a class="btn btn-outline-secondary btn-icon" href="?action=download&dir=<?=urlencode($currentRelative)?>&file=<?=urlencode($name)?>" title="Download"><i class="bi bi-download"></i></a> <button class="btn btn-outline-secondary btn-icon btn-edit" data-name="<?=htmlspecialchars($name)?>" data-path="<?=htmlspecialchars($it['full'])?>" title="Edit"><i class="bi bi-pencil"></i></button> <?php else: ?> <a class="btn btn-outline-secondary btn-icon" href="?action=zipdl&dir=<?=urlencode($currentRelative)?>&file=<?=urlencode($name)?>" title="Download ZIP"><i class="bi bi-file-zip"></i></a> <?php endif; ?> <button class="btn btn-outline-secondary btn-icon btn-rename" data-name="<?=htmlspecialchars($name)?>" title="Rename"><i class="bi bi-input-cursor-text"></i></button> <button class="btn btn-outline-secondary btn-icon btn-chmod" data-name="<?=htmlspecialchars($name)?>" data-perm="<?=$it['perms']?>" title="Chmod"><i class="bi bi-shield-lock"></i></button> <button class="btn btn-outline-danger btn-icon btn-delete" data-name="<?=htmlspecialchars($name)?>" title="Hapus"><i class="bi bi-trash"></i></button> </div> </td> </tr> <?php endforeach; ?> <?php if(empty($items)): ?> <tr><td colspan="5" class="text-center py-5 text-muted"><i class="bi bi-inbox fs-1 d-block mb-2"></i>Folder kosong</td></tr> <?php endif; ?> </tbody> </table> </div> <div class="card-footer bg-white d-flex justify-content-between align-items-center small text-muted"> <span><i class="bi bi-folder2-open"></i> <?=htmlspecialchars(FM_ROOT)?> <?= $currentRelative ? ' / '.$currentRelative : '' ?></span> <span class="d-none d-md-inline">PHP <?=PHP_VERSION?> | <?=count($items)?> item | Mobile/Desktop Ready</span> </div> </div> <div class="mt-3 text-center small text-muted">Single-file file manager • Bootstrap 5 • No login • net2ftp style • by Mamad</div> </div> <!-- MODALS --> <!-- Mkdir --> <div class="modal fade" id="mkdirModal" tabindex="-1"> <div class="modal-dialog modal-dialog-centered"> <form method="post" class="modal-content"> <div class="modal-header"><h6 class="modal-title"><i class="bi bi-folder-plus me-2"></i>Buat Folder Baru</h6><button class="btn-close" data-bs-dismiss="modal"></button></div> <div class="modal-body"> <input type="hidden" name="action" value="mkdir"> <label class="form-label">Nama folder</label> <input name="name" class="form-control" placeholder="contoh: assets" required> </div> <div class="modal-footer"><button class="btn btn-secondary" data-bs-dismiss="modal" type="button">Batal</button><button class="btn btn-primary" type="submit">Buat</button></div> </form> </div> </div> <!-- Mkfile --> <div class="modal fade" id="mkfileModal" tabindex="-1"> <div class="modal-dialog modal-dialog-centered"> <form method="post" class="modal-content"> <div class="modal-header"><h6 class="modal-title"><i class="bi bi-file-earmark-plus me-2"></i>Buat File Baru</h6><button class="btn-close" data-bs-dismiss="modal"></button></div> <div class="modal-body"> <input type="hidden" name="action" value="mkfile"> <label class="form-label">Nama file</label> <input name="name" class="form-control mb-3" placeholder="contoh: index.php" required> <label class="form-label">Isi awal (opsional)</label> <textarea name="content" class="form-control editor" rows="6" placeholder="<?php echo '<?php'; ?>"></textarea> </div> <div class="modal-footer"><button class="btn btn-secondary" data-bs-dismiss="modal" type="button">Batal</button><button class="btn btn-primary" type="submit">Buat</button></div> </form> </div> </div> <!-- Upload --> <div class="modal fade" id="uploadModal" tabindex="-1"> <div class="modal-dialog modal-dialog-centered"> <form method="post" enctype="multipart/form-data" class="modal-content"> <div class="modal-header"><h6 class="modal-title"><i class="bi bi-cloud-upload me-2"></i>Upload File</h6><button class="btn-close" data-bs-dismiss="modal"></button></div> <div class="modal-body"> <input type="hidden" name="action" value="upload"> <div class="dropzone" id="dropzone"> <i class="bi bi-cloud-arrow-up fs-1 text-primary"></i> <div class="fw-semibold mt-2">Drag & drop file kesini</div> <div class="small text-muted">atau klik untuk pilih</div> <input type="file" name="files[]" id="fileInput" multiple class="d-none"> <div id="fileList" class="mt-3 small text-start"></div> </div> </div> <div class="modal-footer"><button class="btn btn-secondary" data-bs-dismiss="modal" type="button">Batal</button><button class="btn btn-primary" type="submit">Upload</button></div> </form> </div> </div> <!-- Upload Zip --> <div class="modal fade" id="uploadZipModal" tabindex="-1"> <div class="modal-dialog modal-dialog-centered"> <form method="post" enctype="multipart/form-data" class="modal-content"> <div class="modal-header"><h6 class="modal-title"><i class="bi bi-file-zip me-2"></i>Upload & Ekstrak ZIP</h6><button class="btn-close" data-bs-dismiss="modal"></button></div> <div class="modal-body"> <input type="hidden" name="action" value="upload_zip"> <input type="file" name="zipfile" accept=".zip" class="form-control" required> <div class="form-text">File ZIP akan langsung diekstrak di folder ini.</div> </div> <div class="modal-footer"><button class="btn btn-secondary" data-bs-dismiss="modal" type="button">Batal</button><button class="btn btn-primary" type="submit">Ekstrak</button></div> </form> </div> </div> <!-- Rename --> <div class="modal fade" id="renameModal" tabindex="-1"> <div class="modal-dialog modal-dialog-centered"> <form method="post" class="modal-content"> <div class="modal-header"><h6 class="modal-title">Rename</h6><button class="btn-close" data-bs-dismiss="modal"></button></div> <div class="modal-body"> <input type="hidden" name="action" value="rename"> <input type="hidden" name="old" id="renameOld"> <label class="form-label">Nama baru</label> <input name="new" id="renameNew" class="form-control" required> </div> <div class="modal-footer"><button class="btn btn-secondary" data-bs-dismiss="modal" type="button">Batal</button><button class="btn btn-primary" type="submit">Simpan</button></div> </form> </div> </div> <!-- Chmod --> <div class="modal fade" id="chmodModal" tabindex="-1"> <div class="modal-dialog modal-dialog-centered modal-sm"> <form method="post" class="modal-content"> <div class="modal-header"><h6 class="modal-title">Chmod</h6><button class="btn-close" data-bs-dismiss="modal"></button></div> <div class="modal-body"> <input type="hidden" name="action" value="chmod"> <input type="hidden" name="target" id="chmodTarget"> <label class="form-label">Permission (0755, 0644, dll)</label> <input name="perm" id="chmodPerm" class="form-control" pattern="[0-7]{3,4}" required> </div> <div class="modal-footer"><button class="btn btn-secondary" data-bs-dismiss="modal" type="button">Batal</button><button class="btn btn-primary" type="submit">Apply</button></div> </form> </div> </div> <!-- Delete --> <div class="modal fade" id="deleteModal" tabindex="-1"> <div class="modal-dialog modal-dialog-centered modal-sm"> <form method="post" class="modal-content"> <div class="modal-header"><h6 class="modal-title text-danger"><i class="bi bi-exclamation-triangle me-2"></i>Hapus?</h6><button class="btn-close" data-bs-dismiss="modal"></button></div> <div class="modal-body"> <input type="hidden" name="action" value="delete"> <input type="hidden" name="target" id="deleteTarget"> <p class="mb-0">Yakin hapus <strong id="deleteName"></strong>? Tidak bisa dikembalikan.</p> </div> <div class="modal-footer"><button class="btn btn-secondary" data-bs-dismiss="modal" type="button">Batal</button><button class="btn btn-danger" type="submit">Hapus</button></div> </form> </div> </div> <!-- Edit --> <div class="modal fade" id="editModal" tabindex="-1"> <div class="modal-dialog modal-dialog-centered modal-xl"> <form method="post" class="modal-content"> <div class="modal-header"><h6 class="modal-title"><i class="bi bi-code-square me-2"></i>Edit File: <span id="editFileName"></span></h6><button class="btn-close" data-bs-dismiss="modal"></button></div> <div class="modal-body p-0"> <input type="hidden" name="action" value="save"> <input type="hidden" name="target" id="editTarget"> <textarea name="content" id="editContent" class="form-control editor border-0 rounded-0" style="min-height:60vh;white-space:pre;overflow-wrap:normal;overflow-x:auto" spellcheck="false"></textarea> </div> <div class="modal-footer"><span class="me-auto small text-muted">Ctrl+S untuk simpan</span><button class="btn btn-secondary" data-bs-dismiss="modal" type="button">Batal</button><button class="btn btn-primary" type="submit">Simpan</button></div> </form> </div> </div> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> <script> const currentDir = <?=json_encode($currentRelative)?>; // Search filter document.getElementById('searchInput')?.addEventListener('input', e=>{ const q = e.target.value.toLowerCase(); document.querySelectorAll('.file-row').forEach(row=>{ row.style.display = row.dataset.name.includes(q) ? '' : 'none'; }); }); // Rename document.querySelectorAll('.btn-rename').forEach(btn=>{ btn.addEventListener('click', ()=>{ document.getElementById('renameOld').value = btn.dataset.name; document.getElementById('renameNew').value = btn.dataset.name; new bootstrap.Modal(document.getElementById('renameModal')).show(); }); }); // Chmod document.querySelectorAll('.btn-chmod').forEach(btn=>{ btn.addEventListener('click', ()=>{ document.getElementById('chmodTarget').value = btn.dataset.name; document.getElementById('chmodPerm').value = btn.dataset.perm; new bootstrap.Modal(document.getElementById('chmodModal')).show(); }); }); // Delete document.querySelectorAll('.btn-delete').forEach(btn=>{ btn.addEventListener('click', ()=>{ document.getElementById('deleteTarget').value = btn.dataset.name; document.getElementById('deleteName').textContent = btn.dataset.name; new bootstrap.Modal(document.getElementById('deleteModal')).show(); }); }); // Edit - fetch content via JS? For PHP native, kita load via AJAX fetch reading file content is not allowed directly, so we use fetch to read? Simpler: kita buat endpoint untuk baca file. document.querySelectorAll('.btn-edit').forEach(btn=>{ btn.addEventListener('click', async ()=>{ const name = btn.dataset.name; document.getElementById('editFileName').textContent = name; document.getElementById('editTarget').value = name; document.getElementById('editContent').value = 'Loading...'; new bootstrap.Modal(document.getElementById('editModal')).show(); try { // Ambil isi file lewat fetch ke file itu sendiri dengan parameter khusus const res = await fetch(`?dir=${encodeURIComponent(currentDir)}&action=read&file=${encodeURIComponent(name)}`); if(res.ok) { const text = await res.text(); document.getElementById('editContent').value = text; } else { document.getElementById('editContent').value = 'Gagal load file (mungkin binary)'; } } catch(e){ document.getElementById('editContent').value = 'Error: '+e; } }); }); // Drag & drop upload const dz = document.getElementById('dropzone'); const fi = document.getElementById('fileInput'); const fl = document.getElementById('fileList'); dz?.addEventListener('click', ()=>fi.click()); dz?.addEventListener('dragover', e=>{e.preventDefault(); dz.classList.add('dragover')}); dz?.addEventListener('dragleave', ()=>dz.classList.remove('dragover')); dz?.addEventListener('drop', e=>{ e.preventDefault(); dz.classList.remove('dragover'); fi.files = e.dataTransfer.files; showFiles(); }); fi?.addEventListener('change', showFiles); function showFiles(){ if(!fi.files.length){fl.innerHTML='';return} let html = '<div class="fw-semibold">Terpilih:</div><ul class="mb-0 ps-3">'; for(let f of fi.files) html+=`<li>${f.name} (${(f.size/1024).toFixed(1)} KB)</li>`; html+='</ul>'; fl.innerHTML = html; } // Ctrl+S di editor document.getElementById('editContent')?.addEventListener('keydown', e=>{ if((e.ctrlKey||e.metaKey) && e.key==='s'){e.preventDefault(); e.target.closest('form').submit();} }); </script> <?php // Endpoint untuk baca file (AJAX edit) - taruh di akhir supaya tidak ganggu output di atas tapi sebelum die if (isset($_GET['action']) && $_GET['action'] === 'read' && isset($_GET['file'])) { $file = fm_clean_path($_GET['file']); $path = $currentReal . DIRECTORY_SEPARATOR . $file; $real = realpath($path); if ($real && strpos($real, FM_ROOT) === 0 && is_file($real) && fm_is_text($real)) { // clean output buffer while (ob_get_level()) ob_end_clean(); header('Content-Type: text/plain; charset=utf-8'); readfile($real); exit; } http_response_code(404); exit('Cannot read'); } ?> </body> </html>
Simpan