Spaces:
Sleeping
Sleeping
| <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> | |