Nexus Manager
Local Host Mode
Editing file.php
file.php
Cancel
Save Changes
<?php session_start(); // ========================================== // SECURITY & CONFIGURATION // ========================================== // The base directory is where this script is placed. $base_dir = realpath(__DIR__); // Sanitize the requested directory to prevent path traversal (e.g., going to ../../../etc/passwd) $req_dir = isset($_GET['dir']) ? trim($_GET['dir'], '/') : ''; $current_dir = realpath($base_dir . '/' . $req_dir); // If the resolved path doesn't start with our base_dir, fallback to base_dir if ($current_dir === false || strpos($current_dir, $base_dir) !== 0) { $current_dir = $base_dir; $req_dir = ''; } $message = ''; // ========================================== // HANDLE ACTIONS (POST) // ========================================== if ($_SERVER['REQUEST_METHOD'] === 'POST') { $action = $_POST['action'] ?? ''; // 1. Create Folder if ($action === 'create_folder' && !empty($_POST['folder_name'])) { $new_folder = $current_dir . '/' . basename($_POST['folder_name']); if (!file_exists($new_folder)) { mkdir($new_folder, 0755); $message = "Folder created successfully."; } else { $message = "Folder already exists."; } } // 2. Create File if ($action === 'create_file' && !empty($_POST['file_name'])) { $new_file = $current_dir . '/' . basename($_POST['file_name']); if (!file_exists($new_file)) { file_put_contents($new_file, ''); $message = "File created successfully."; } else { $message = "File already exists."; } } // 3. Delete Item if ($action === 'delete' && !empty($_POST['item_name'])) { $target = realpath($current_dir . '/' . basename($_POST['item_name'])); // Ensure the target is within our base directory before deleting if ($target && strpos($target, $base_dir) === 0 && $target !== __FILE__) { if (is_dir($target)) { // Basic rmdir (only works if empty). For a full recursive delete, a custom function is needed. @rmdir($target) ? $message = "Folder deleted." : $message = "Folder must be empty to delete."; } else { @unlink($target); $message = "File deleted."; } } } // 4. Save Edited File if ($action === 'save_edit' && !empty($_POST['file_path'])) { $target = realpath($base_dir . '/' . trim($_POST['file_path'], '/')); if ($target && strpos($target, $base_dir) === 0 && is_file($target)) { file_put_contents($target, $_POST['file_content']); $message = "File saved successfully."; } } // Redirect to prevent form resubmission header("Location: ?dir=" . urlencode($req_dir) . "&msg=" . urlencode($message)); exit; } if(isset($_GET['msg'])) $message = htmlspecialchars($_GET['msg']); // ========================================== // HANDLE FILE EDITOR (GET) // ========================================== $edit_mode = false; $edit_content = ''; $edit_filename = ''; $edit_filepath_rel = ''; if (isset($_GET['edit'])) { $target = realpath($current_dir . '/' . basename($_GET['edit'])); if ($target && strpos($target, $base_dir) === 0 && is_file($target)) { $edit_mode = true; $edit_content = file_get_contents($target); $edit_filename = basename($target); $edit_filepath_rel = ltrim(str_replace($base_dir, '', $target), '/'); } } // ========================================== // FETCH FILES // ========================================== $items = scandir($current_dir); $folders = []; $files = []; foreach ($items as $item) { if ($item === '.') continue; if ($item === '..' && $current_dir === $base_dir) continue; // Don't allow going above base $path = $current_dir . '/' . $item; $is_dir = is_dir($path); $size = $is_dir ? '-' : round(filesize($path) / 1024, 2) . ' KB'; $perms = substr(sprintf('%o', fileperms($path)), -4); $modified = date("M d, Y H:i", filemtime($path)); $info = [ 'name' => $item, 'is_dir' => $is_dir, 'size' => $size, 'perms' => $perms, 'modified' => $modified, 'ext' => $is_dir ? '' : strtolower(pathinfo($path, PATHINFO_EXTENSION)) ]; if ($is_dir) { $folders[] = $info; } else { $files[] = $info; } } // Sort folders and files usort($folders, function($a, $b) { return strnatcasecmp($a['name'], $b['name']); }); usort($files, function($a, $b) { return strnatcasecmp($a['name'], $b['name']); }); $all_items = array_merge($folders, $files); // Breadcrumbs $path_parts = explode('/', trim($req_dir, '/')); $breadcrumbs = []; $built_path = ''; foreach ($path_parts as $part) { if (empty($part)) continue; $built_path .= '/' . $part; $breadcrumbs[] = ['name' => $part, 'path' => trim($built_path, '/')]; } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <title>Nexus File Manager | Rose Theme</title> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Hind+Siliguri:wght@300;400;500;600;700&display=swap" rel="stylesheet"> <script src="https://cdn.tailwindcss.com"></script> <script src="https://unpkg.com/lucide@latest"></script> <script> tailwind.config = { theme: { extend: { fontFamily: { sans: ['Hind Siliguri', 'sans-serif'], mono: ['ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', "Liberation Mono", "Courier New", 'monospace'] }, colors: { brand: { 50: '#fff1f2', 100: '#ffe4e6', 500: '#f43f5e', 600: '#e11d48', 700: '#be123c', 900: '#881337' } } } } } </script> <style> ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: #fecdd3; border-radius: 10px; } ::-webkit-scrollbar-thumb:hover { background: #fda4af; } body { touch-action: manipulation; -webkit-tap-highlight-color: transparent; } .skeleton-shimmer { background: #fff1f2; background-image: linear-gradient(to right, #fff1f2 0%, #ffe4e6 20%, #fff1f2 40%, #fff1f2 100%); background-repeat: no-repeat; background-size: 800px 100%; animation: placeholderShimmer 1.5s linear infinite forwards; } @keyframes placeholderShimmer { 0% { background-position: -468px 0; } 100% { background-position: 468px 0; } } .editor-textarea { tab-size: 4; white-space: pre; overflow-wrap: normal; overflow-x: auto; } </style> </head> <body class="bg-rose-50 text-slate-800 h-screen overflow-hidden flex flex-col font-sans"> <!-- Header --> <header class="bg-white border-b border-rose-100 shadow-sm z-10"> <div class="flex items-center justify-between px-4 sm:px-6 py-3"> <div class="flex items-center gap-3"> <div class="bg-brand-100 p-2 rounded-lg"> <i data-lucide="server" class="text-brand-600 w-5 h-5"></i> </div> <div> <h1 class="font-bold text-slate-800 leading-tight">Nexus Manager</h1> <p class="text-xs text-rose-500 font-medium">Local Host Mode</p> </div> </div> <div class="flex items-center gap-2"> <button onclick="triggerSkeleton()" class="p-2 text-slate-500 hover:text-brand-600 bg-white border border-slate-200 rounded-lg shadow-sm transition-colors" title="Refresh"> <i data-lucide="refresh-cw" class="w-4 h-4"></i> </button> </div> </div> <!-- Action Bar & Breadcrumbs --> <div class="bg-white px-4 sm:px-6 py-3 border-t border-rose-50 flex flex-col sm:flex-row sm:items-center justify-between gap-4"> <nav class="flex items-center text-sm font-medium text-slate-600 overflow-x-auto whitespace-nowrap hide-scrollbar"> <a href="?dir=" class="p-1 hover:text-brand-600 rounded text-brand-600"><i data-lucide="home" class="w-4 h-4"></i></a> <?php foreach($breadcrumbs as $bc): ?> <i data-lucide="chevron-right" class="w-4 h-4 mx-1 text-rose-200"></i> <a href="?dir=<?= urlencode($bc['path']) ?>" class="hover:text-brand-600"><?= htmlspecialchars($bc['name']) ?></a> <?php endforeach; ?> <?php if($edit_mode): ?> <i data-lucide="chevron-right" class="w-4 h-4 mx-1 text-rose-200"></i> <span class="text-slate-900 font-semibold flex items-center gap-1"><i data-lucide="edit-3" class="w-3 h-3"></i> Editing <?= htmlspecialchars($edit_filename) ?></span> <?php endif; ?> </nav> <?php if(!$edit_mode): ?> <div class="flex items-center gap-2 overflow-x-auto hide-scrollbar pb-1 sm:pb-0"> <button onclick="openModal('create-folder-modal')" class="flex-shrink-0 flex items-center gap-1.5 px-3 py-1.5 bg-white border border-rose-200 text-rose-700 rounded-lg hover:bg-rose-50 text-sm font-medium transition-colors shadow-sm"> <i data-lucide="folder-plus" class="w-4 h-4"></i> Folder </button> <button onclick="openModal('create-file-modal')" class="flex-shrink-0 flex items-center gap-1.5 px-3 py-1.5 bg-brand-600 text-white border border-transparent rounded-lg hover:bg-brand-700 text-sm font-medium transition-colors shadow-sm"> <i data-lucide="file-plus" class="w-4 h-4"></i> File </button> </div> <?php endif; ?> </div> </header> <?php if($message): ?> <div id="toast-msg" class="bg-brand-100 border-l-4 border-brand-500 text-brand-700 p-4 absolute top-32 right-4 z-50 rounded shadow-md flex justify-between items-center w-72 transition-opacity duration-300"> <p class="text-sm font-medium"><?= $message ?></p> <button onclick="document.getElementById('toast-msg').style.opacity='0'; setTimeout(()=>document.getElementById('toast-msg').remove(), 300);"><i data-lucide="x" class="w-4 h-4"></i></button> </div> <?php endif; ?> <!-- Main Content --> <main class="flex-1 overflow-auto p-4 sm:p-6 relative"> <!-- Skeleton View --> <div id="skeleton-view" class="absolute inset-0 bg-rose-50 p-4 sm:p-6 z-10 flex flex-col gap-3"> <div class="h-10 skeleton-shimmer rounded-lg w-full mb-4"></div> <div class="h-14 skeleton-shimmer rounded-xl w-full"></div> <div class="h-14 skeleton-shimmer rounded-xl w-full"></div> <div class="h-14 skeleton-shimmer rounded-xl w-full"></div> <div class="h-14 skeleton-shimmer rounded-xl w-full"></div> </div> <div id="main-content" class="hidden h-full"> <?php if($edit_mode): ?> <!-- EDITOR UI --> <div class="bg-white rounded-xl shadow-sm border border-rose-200 overflow-hidden h-full flex flex-col"> <form method="POST" action="?dir=<?= urlencode($req_dir) ?>" class="h-full flex flex-col"> <input type="hidden" name="action" value="save_edit"> <input type="hidden" name="file_path" value="<?= htmlspecialchars($edit_filepath_rel) ?>"> <div class="bg-slate-800 px-4 py-2 flex justify-between items-center text-rose-50"> <div class="flex items-center gap-2"> <i data-lucide="file-code" class="w-4 h-4 text-rose-400"></i> <span class="font-mono text-sm"><?= htmlspecialchars($edit_filename) ?></span> </div> <div class="flex gap-2"> <a href="?dir=<?= urlencode($req_dir) ?>" class="px-3 py-1.5 text-xs font-medium text-slate-300 hover:text-white bg-slate-700 hover:bg-slate-600 rounded transition-colors">Cancel</a> <button type="submit" class="px-3 py-1.5 text-xs font-medium text-white bg-brand-600 hover:bg-brand-500 rounded shadow transition-colors flex items-center gap-1"> <i data-lucide="save" class="w-3 h-3"></i> Save Changes </button> </div> </div> <textarea name="file_content" class="editor-textarea flex-1 w-full bg-[#1e1e1e] text-[#d4d4d4] p-4 font-mono text-sm outline-none resize-none border-none focus:ring-0"><?= htmlspecialchars($edit_content) ?></textarea> </form> </div> <?php else: ?> <!-- FILE BROWSER UI --> <div class="bg-white rounded-xl shadow-sm border border-rose-200 overflow-hidden"> <div class="overflow-x-auto"> <table class="w-full text-left border-collapse"> <thead> <tr class="bg-rose-50 text-rose-800 text-xs uppercase tracking-wider border-b border-rose-100"> <th class="p-3 font-medium pl-6">Name</th> <th class="p-3 font-medium hidden sm:table-cell w-24">Size</th> <th class="p-3 font-medium hidden md:table-cell w-32">Type</th> <th class="p-3 font-medium hidden lg:table-cell w-24">Perms</th> <th class="p-3 font-medium hidden lg:table-cell w-40">Modified</th> <th class="p-3 font-medium w-24 text-right pr-6">Actions</th> </tr> </thead> <tbody class="text-sm divide-y divide-rose-50"> <?php if($req_dir !== ''): $parent_dir = dirname($req_dir) === '.' ? '' : dirname($req_dir); ?> <tr class="hover:bg-rose-50/50 transition-colors"> <td class="p-3 pl-6"> <a href="?dir=<?= urlencode($parent_dir) ?>" class="flex items-center gap-3"> <i data-lucide="corner-left-up" class="w-5 h-5 text-slate-400"></i> <span class="font-medium text-slate-700 hover:text-brand-600">.. (Go up)</span> </a> </td> <td class="p-3 hidden sm:table-cell">-</td> <td class="p-3 hidden md:table-cell">-</td> <td class="p-3 hidden lg:table-cell">-</td> <td class="p-3 hidden lg:table-cell">-</td> <td class="p-3 text-right pr-6"></td> </tr> <?php endif; ?> <?php foreach($all_items as $item): if($item['name'] === '..' && $req_dir === '') continue; // Determine icon $icon = "file"; $iconColor = "text-slate-400"; if ($item['is_dir']) { $icon = "folder"; $iconColor = "text-yellow-500 fill-yellow-100"; } else { $ext = $item['ext']; if(in_array($ext, ['php', 'html', 'css', 'js', 'json', 'xml'])) { $icon = "file-code"; $iconColor = "text-brand-500"; } elseif(in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'svg'])) { $icon = "image"; $iconColor = "text-emerald-500"; } elseif(in_array($ext, ['zip', 'tar', 'gz', 'rar'])) { $icon = "file-archive"; $iconColor = "text-rose-500"; } elseif($ext === 'txt') { $icon = "file-text"; $iconColor = "text-slate-500"; } } // Check if editable $editable = !$item['is_dir'] && in_array($item['ext'], ['php', 'html', 'css', 'js', 'json', 'txt', 'xml']); ?> <tr class="hover:bg-rose-50/50 transition-colors group"> <td class="p-3 pl-6"> <?php if($item['is_dir']): ?> <a href="?dir=<?= urlencode($req_dir . '/' . $item['name']) ?>" class="flex items-center gap-3"> <i data-lucide="<?= $icon ?>" class="w-5 h-5 <?= $iconColor ?>"></i> <span class="font-medium text-slate-800 group-hover:text-brand-600 transition-colors"><?= htmlspecialchars($item['name']) ?></span> </a> <?php else: ?> <div class="flex items-center gap-3"> <i data-lucide="<?= $icon ?>" class="w-5 h-5 <?= $iconColor ?>"></i> <span class="font-medium text-slate-800"><?= htmlspecialchars($item['name']) ?></span> </div> <?php endif; ?> </td> <td class="p-3 text-slate-500 hidden sm:table-cell"><?= $item['size'] ?></td> <td class="p-3 text-slate-500 hidden md:table-cell capitalize"><?= $item['is_dir'] ? 'Folder' : ($item['ext'] ?: 'File') ?></td> <td class="p-3 text-slate-500 hidden lg:table-cell"><?= $item['perms'] ?></td> <td class="p-3 text-slate-500 hidden lg:table-cell"><?= $item['modified'] ?></td> <td class="p-3 text-right pr-6"> <div class="flex items-center justify-end gap-2 opacity-100 sm:opacity-0 sm:group-hover:opacity-100 transition-opacity"> <?php if($editable): ?> <a href="?dir=<?= urlencode($req_dir) ?>&edit=<?= urlencode($item['name']) ?>" class="p-1.5 text-brand-600 hover:bg-brand-50 rounded-lg transition-colors" title="Edit File"> <i data-lucide="edit-3" class="w-4 h-4"></i> </a> <?php endif; ?> <form method="POST" action="?dir=<?= urlencode($req_dir) ?>" class="inline-block" onsubmit="return confirm('Delete <?= htmlspecialchars($item['name']) ?>?');"> <input type="hidden" name="action" value="delete"> <input type="hidden" name="item_name" value="<?= htmlspecialchars($item['name']) ?>"> <button type="submit" class="p-1.5 text-red-500 hover:bg-red-50 rounded-lg transition-colors" title="Delete"> <i data-lucide="trash-2" class="w-4 h-4"></i> </button> </form> </div> </td> </tr> <?php endforeach; ?> <?php if(count($all_items) === 0): ?> <tr> <td colspan="6" class="p-8 text-center text-slate-400"> <i data-lucide="folder-open" class="w-12 h-12 mx-auto mb-2 opacity-50"></i> <p>This directory is empty</p> </td> </tr> <?php endif; ?> </tbody> </table> </div> </div> <?php endif; ?> </div> </main> <!-- Modals --> <div id="modal-backdrop" class="hidden fixed inset-0 bg-slate-900 bg-opacity-40 backdrop-blur-sm z-40 transition-opacity"></div> <!-- Create Folder Modal --> <div id="create-folder-modal" class="hidden fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-full max-w-sm z-50 px-4"> <div class="bg-white rounded-2xl shadow-xl overflow-hidden"> <div class="px-6 py-4 border-b border-rose-100 flex justify-between items-center"> <h3 class="text-lg font-bold text-slate-800">New Folder</h3> <button onclick="closeModals()" class="text-slate-400 hover:text-slate-600"><i data-lucide="x" class="w-5 h-5"></i></button> </div> <form method="POST" action="?dir=<?= urlencode($req_dir) ?>"> <input type="hidden" name="action" value="create_folder"> <div class="p-6"> <label class="block text-sm font-medium text-slate-700 mb-2">Folder Name</label> <input type="text" name="folder_name" required placeholder="e.g., assets" class="block w-full px-3 py-2 border border-rose-200 rounded-lg focus:ring-brand-500 focus:border-brand-500 sm:text-sm outline-none"> </div> <div class="px-6 py-4 bg-rose-50 border-t border-rose-100 flex justify-end gap-3"> <button type="button" onclick="closeModals()" class="px-4 py-2 text-sm font-medium text-slate-600 hover:bg-slate-200 bg-slate-100 rounded-lg transition-colors">Cancel</button> <button type="submit" class="px-4 py-2 text-sm font-medium text-white bg-brand-600 hover:bg-brand-700 rounded-lg shadow-sm transition-colors">Create</button> </div> </form> </div> </div> <!-- Create File Modal --> <div id="create-file-modal" class="hidden fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-full max-w-sm z-50 px-4"> <div class="bg-white rounded-2xl shadow-xl overflow-hidden"> <div class="px-6 py-4 border-b border-rose-100 flex justify-between items-center"> <h3 class="text-lg font-bold text-slate-800">New File</h3> <button onclick="closeModals()" class="text-slate-400 hover:text-slate-600"><i data-lucide="x" class="w-5 h-5"></i></button> </div> <form method="POST" action="?dir=<?= urlencode($req_dir) ?>"> <input type="hidden" name="action" value="create_file"> <div class="p-6"> <label class="block text-sm font-medium text-slate-700 mb-2">File Name</label> <input type="text" name="file_name" required placeholder="e.g., style.css" class="block w-full px-3 py-2 border border-rose-200 rounded-lg focus:ring-brand-500 focus:border-brand-500 sm:text-sm outline-none"> </div> <div class="px-6 py-4 bg-rose-50 border-t border-rose-100 flex justify-end gap-3"> <button type="button" onclick="closeModals()" class="px-4 py-2 text-sm font-medium text-slate-600 hover:bg-slate-200 bg-slate-100 rounded-lg transition-colors">Cancel</button> <button type="submit" class="px-4 py-2 text-sm font-medium text-white bg-brand-600 hover:bg-brand-700 rounded-lg shadow-sm transition-colors">Create</button> </div> </form> </div> </div> <script> lucide.createIcons(); // Handle Skeleton Loading on load window.addEventListener('DOMContentLoaded', () => { const skeleton = document.getElementById('skeleton-view'); const content = document.getElementById('main-content'); setTimeout(() => { skeleton.classList.add('hidden'); content.classList.remove('hidden'); }, 500); // 500ms initial load shimmer }); // Trigger manual refresh/skeleton function triggerSkeleton() { window.location.reload(); } // Modals function openModal(id) { document.getElementById('modal-backdrop').classList.remove('hidden'); document.getElementById(id).classList.remove('hidden'); } function closeModals() { document.getElementById('modal-backdrop').classList.add('hidden'); document.querySelectorAll('[id$="-modal"]').forEach(m => m.classList.add('hidden')); } // Tab behavior for editor textarea const textarea = document.querySelector('.editor-textarea'); if (textarea) { textarea.addEventListener('keydown', function(e) { if (e.key === 'Tab') { e.preventDefault(); var start = this.selectionStart; var end = this.selectionEnd; this.value = this.value.substring(0, start) + "\t" + this.value.substring(end); this.selectionStart = this.selectionEnd = start + 1; } }); } </script> </body> </html>
New Folder
Folder Name
Cancel
Create
New File
File Name
Cancel
Create