WebTermX / index.html
userbymahadi's picture
Update index.html
41adf7c verified
<!DOCTYPE html>
<html lang="bn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>WebTermX Ultimate</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<style>
body { background: #020617; color: #f1f5f9; font-family: 'Inter', sans-serif; overflow: hidden; }
.glass { background: rgba(15, 23, 42, 0.8); backdrop-filter: blur(12px); border: 1px solid rgba(255,255,255,0.05); }
#sidebar { transition: transform 0.3s ease; width: 280px; z-index: 1000; }
.active-nav { background: #4f46e5; color: white; }
.terminal-font { font-family: 'JetBrains Mono', monospace; font-size: 11px; }
</style>
</head>
<body class="h-screen flex flex-col">
<div id="overlay" onclick="toggleSidebar()" class="fixed inset-0 bg-black/60 hidden z-[900]"></div>
<!-- Header -->
<header class="h-14 flex items-center justify-between px-4 glass border-b border-white/5 z-[100]">
<div class="flex items-center gap-3">
<button onclick="toggleSidebar()" class="p-2"><i data-lucide="menu"></i></button>
<span class="font-bold text-indigo-400">ULTRA V6</span>
</div>
<div class="flex items-center gap-2">
<!-- Refresh Button for WebSocket -->
<button onclick="reconnect()" class="p-2 bg-indigo-600/20 text-indigo-400 rounded-lg" title="Reconnect Session">
<i data-lucide="refresh-cw" id="refresh-icon"></i>
</button>
<button onclick="logout()" class="p-2 text-slate-500"><i data-lucide="power"></i></button>
</div>
</header>
<div class="flex flex-1 relative overflow-hidden">
<!-- Sidebar -->
<aside id="sidebar" class="fixed inset-y-0 left-0 glass -translate-x-full pt-16 shadow-2xl">
<div class="p-4 space-y-2">
<button onclick="openTab('terminal')" id="btn-terminal" class="w-full flex items-center gap-4 p-4 rounded-xl active-nav">
<i data-lucide="terminal"></i> Terminal
</button>
<button onclick="openTab('files')" id="btn-files" class="w-full flex items-center gap-4 p-4 rounded-xl">
<i data-lucide="folder-tree"></i> File Manager
</button>
<button onclick="openTab('proxy')" id="btn-proxy" class="w-full flex items-center gap-4 p-4 rounded-xl">
<i data-lucide="monitor"></i> Web Preview
</button>
</div>
</aside>
<main class="flex-1 p-3 overflow-y-auto">
<!-- Terminal -->
<div id="tab-terminal" class="tab-content h-full flex flex-col gap-2">
<div id="output" class="flex-1 glass rounded-2xl p-4 overflow-y-auto terminal-font leading-relaxed"></div>
<div class="flex gap-2">
<input type="text" id="cmdInput" class="flex-1 bg-slate-900 border border-slate-800 rounded-xl px-4 py-3 text-sm focus:border-indigo-500 outline-none" placeholder="Type command...">
<button onclick="sendCmd()" class="bg-indigo-600 px-5 rounded-xl"><i data-lucide="send" class="w-4 h-4"></i></button>
</div>
</div>
<!-- File Manager -->
<div id="tab-files" class="tab-content hidden space-y-3">
<div class="flex items-center gap-2 sticky top-0 bg-[#020617] py-1 z-10">
<button onclick="goBack()" class="p-2 glass rounded-lg"><i data-lucide="chevron-left"></i></button>
<button onclick="fetchFiles('/')" class="p-2 glass rounded-lg text-indigo-400" title="Root"><i data-lucide="home"></i></button>
<button onclick="createItem()" class="p-2 bg-indigo-600/20 text-indigo-400 rounded-lg"><i data-lucide="plus"></i></button>
<button onclick="document.getElementById('fileUp').click()" class="p-2 bg-emerald-600/20 text-emerald-400 rounded-lg"><i data-lucide="upload"></i></button>
<input type="file" id="fileUp" class="hidden" onchange="handleUpload()">
<div class="flex-1 text-[10px] mono truncate opacity-40 px-1" id="pathBar">/</div>
</div>
<div id="fileList" class="space-y-2 pb-24"></div>
</div>
<!-- Proxy -->
<div id="tab-proxy" class="tab-content hidden h-full flex flex-col gap-2">
<div class="flex gap-2">
<input type="text" id="proxyUrl" value="http://localhost:8080" class="flex-1 bg-slate-900 p-2 rounded-lg text-xs outline-none">
<button onclick="loadProxy()" class="bg-indigo-600 px-4 rounded-lg text-xs font-bold">PREVIEW</button>
</div>
<iframe id="proxyFrame" class="flex-1 bg-white rounded-xl border-none shadow-xl"></iframe>
</div>
</main>
</div>
<!-- Modal Editor -->
<div id="editor" class="fixed inset-0 bg-slate-950 z-[1100] hidden flex flex-col p-4">
<div class="flex justify-between items-center mb-4">
<span id="edName" class="text-[10px] mono opacity-50 truncate max-w-[200px]"></span>
<div class="flex gap-4">
<button onclick="closeEditor()" class="text-xs text-slate-400">Cancel</button>
<button onclick="saveContent()" class="bg-indigo-600 px-4 py-2 rounded-lg text-xs font-bold">Save</button>
</div>
</div>
<textarea id="edArea" class="flex-1 bg-slate-900 p-4 rounded-xl terminal-font outline-none border border-white/5"></textarea>
</div>
<!-- Login -->
<div id="login" class="fixed inset-0 z-[2000] bg-slate-950 flex items-center justify-center p-6 hidden">
<div class="w-full max-w-xs space-y-6">
<h1 class="text-center text-xl font-bold tracking-widest text-indigo-500">SECURE ACCESS</h1>
<input type="password" id="pKey" class="w-full bg-slate-900 border border-slate-800 p-4 rounded-2xl text-center outline-none focus:border-indigo-500" placeholder="Password">
<button onclick="doLogin()" class="w-full bg-indigo-600 py-4 rounded-2xl font-bold shadow-lg shadow-indigo-600/20">Enter System</button>
</div>
</div>
<script>
lucide.createIcons();
let ws, curPath = "/";
window.onload = () => {
const k = localStorage.getItem('ultra_v6_key');
if(k) connect(k);
else document.getElementById('login').classList.remove('hidden');
};
function doLogin() { connect(document.getElementById('pKey').value); }
function logout() { localStorage.clear(); location.reload(); }
function reconnect() {
const icon = document.getElementById('refresh-icon');
icon.classList.add('animate-spin');
const k = localStorage.getItem('ultra_v6_key');
if(k) connect(k);
setTimeout(() => icon.classList.remove('animate-spin'), 1000);
}
function connect(p) {
const prot = location.protocol === 'https:' ? 'wss:' : 'ws:';
if(ws) ws.close();
ws = new WebSocket(`${prot}//${location.host}/ws`);
ws.onopen = () => ws.send(p);
ws.onmessage = (e) => {
if(e.data === "AUTH_SUCCESS") {
localStorage.setItem('ultra_v6_key', p);
document.getElementById('login').classList.add('hidden');
append("System Connected.");
} else if(e.data === "AUTH_FAILED") {
alert("Unauthorized!"); logout();
} else append(e.data);
};
}
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('-translate-x-full');
document.getElementById('overlay').classList.toggle('hidden');
}
function openTab(t) {
document.querySelectorAll('.tab-content').forEach(c => c.classList.add('hidden'));
document.querySelectorAll('aside button').forEach(b => b.classList.remove('active-nav'));
document.getElementById('tab-'+t).classList.remove('hidden');
document.getElementById('btn-'+t).classList.add('active-nav');
if(t === 'files') fetchFiles(curPath);
if(window.innerWidth < 1024 && !document.getElementById('sidebar').classList.contains('-translate-x-full')) toggleSidebar();
}
function sendCmd() {
const input = document.getElementById('cmdInput');
if(input.value) { ws.send(input.value); input.value = ''; }
}
function append(t) {
const o = document.getElementById('output');
const d = document.createElement('div');
d.className = "mb-1 text-slate-300 border-l-2 border-indigo-500/30 pl-3 break-words";
d.innerText = t;
o.appendChild(d);
o.scrollTop = o.scrollHeight;
}
async function fetchFiles(path) {
curPath = path;
document.getElementById('pathBar').innerText = path;
try {
const res = await fetch(`/api/files?path=${encodeURIComponent(path)}`);
const files = await res.json();
document.getElementById('fileList').innerHTML = files.map(f => `
<div class="flex items-center justify-between p-4 glass rounded-2xl active:scale-95 transition-all" onclick="${f.is_dir ? `fetchFiles('${f.path}')` : `editFile('${f.path}')`}">
<div class="flex items-center gap-3 overflow-hidden">
<i data-lucide="${f.is_dir ? 'folder' : 'file-text'}" class="${f.is_dir ? 'text-indigo-400' : 'text-slate-500'} flex-shrink-0"></i>
<span class="text-sm font-medium truncate">${f.name}</span>
</div>
<div class="flex gap-3 flex-shrink-0">
${!f.is_dir ? `<button onclick="event.stopPropagation(); downloadF('${f.path}')" class="text-indigo-400"><i data-lucide="download" class="w-4 h-4"></i></button>` : ''}
<button onclick="event.stopPropagation(); del('${f.path}')" class="text-rose-500"><i data-lucide="trash-2" class="w-4 h-4"></i></button>
</div>
</div>
`).join('');
lucide.createIcons();
} catch(e) { append("File Error: " + e); }
}
async function handleUpload() {
const file = document.getElementById('fileUp').files[0];
const fd = new FormData();
fd.append('file', file);
await fetch(`/api/upload?path=${encodeURIComponent(curPath)}`, {method: 'POST', body: fd});
fetchFiles(curPath);
}
function downloadF(p) { window.open(`/api/download?path=${encodeURIComponent(p)}`); }
async function createItem() {
const name = prompt("Name:");
if(!name) return;
const isDir = confirm("Folder?");
await fetch('/api/create', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({name, is_dir: isDir, path: curPath})
});
fetchFiles(curPath);
}
async function editFile(p) {
const res = await fetch(`/api/read?path=${encodeURIComponent(p)}`);
const data = await res.json();
document.getElementById('edName').innerText = p;
document.getElementById('edArea').value = data.content;
document.getElementById('editor').classList.remove('hidden');
}
async function saveContent() {
const path = document.getElementById('edName').innerText;
const content = document.getElementById('edArea').value;
await fetch('/api/save', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({path, content})
});
document.getElementById('editor').classList.add('hidden');
}
function closeEditor() { document.getElementById('editor').classList.add('hidden'); }
function goBack() {
if(curPath === "/") return;
const parts = curPath.split('/').filter(p => p);
parts.pop();
fetchFiles('/' + parts.join('/'));
}
function loadProxy() {
document.getElementById('proxyFrame').src = `/proxy?url=${encodeURIComponent(document.getElementById('proxyUrl').value)}`;
}
async function del(p) { if(confirm('Delete?')) { await fetch(`/api/delete?path=${encodeURIComponent(p)}`, {method: 'DELETE'}); fetchFiles(curPath); } }
</script>
</body>
</html>