Download keystone-backend/web.html from dpv007/KeyStone: direct link, hf CLI and curl.
- Browser
- Download file 17.1 kB
-
https://huggingface.co/spaces/dpv007/KeyStone/resolve/main/keystone-backend/web.html
- Command line
-
hf download hf://spaces/dpv007/KeyStone/keystone-backend/web.html
-
curl -L -o web.html https://huggingface.co/spaces/dpv007/KeyStone/resolve/main/keystone-backend/web.html
17.1 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>KeyStone Web Dashboard</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> | |
| <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Round" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --bg-color: #0f1115; | |
| --glass-bg: rgba(25, 28, 36, 0.6); | |
| --glass-border: rgba(255, 255, 255, 0.08); | |
| --text-primary: #ffffff; | |
| --text-secondary: #94a3b8; | |
| --accent: #3b82f6; | |
| --accent-hover: #2563eb; | |
| --danger: #ef4444; | |
| --danger-hover: #dc2626; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| background-color: var(--bg-color); | |
| color: var(--text-primary); | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| background-image: | |
| radial-gradient(circle at 15% 50%, rgba(59, 130, 246, 0.15), transparent 25%), | |
| radial-gradient(circle at 85% 30%, rgba(139, 92, 246, 0.15), transparent 25%); | |
| background-attachment: fixed; | |
| } | |
| .glass-panel { | |
| background: var(--glass-bg); | |
| backdrop-filter: blur(16px); | |
| -webkit-backdrop-filter: blur(16px); | |
| border: 1px solid var(--glass-border); | |
| border-radius: 16px; | |
| box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); | |
| } | |
| /* Login Screen */ | |
| #login-container { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| height: 100vh; | |
| } | |
| .login-box { | |
| padding: 40px; | |
| width: 100%; | |
| max-width: 400px; | |
| text-align: center; | |
| } | |
| .login-box h1 { | |
| font-size: 24px; | |
| font-weight: 600; | |
| margin-bottom: 8px; | |
| background: linear-gradient(to right, #60a5fa, #a78bfa); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .login-box p { | |
| color: var(--text-secondary); | |
| margin-bottom: 32px; | |
| font-size: 14px; | |
| } | |
| input { | |
| width: 100%; | |
| padding: 12px 16px; | |
| margin-bottom: 16px; | |
| background: rgba(0, 0, 0, 0.2); | |
| border: 1px solid var(--glass-border); | |
| border-radius: 8px; | |
| color: white; | |
| font-family: 'Inter', sans-serif; | |
| font-size: 14px; | |
| transition: all 0.2s; | |
| } | |
| input:focus { | |
| outline: none; | |
| border-color: var(--accent); | |
| background: rgba(0, 0, 0, 0.4); | |
| } | |
| button { | |
| width: 100%; | |
| padding: 12px; | |
| background: var(--accent); | |
| color: white; | |
| border: none; | |
| border-radius: 8px; | |
| font-weight: 600; | |
| font-size: 14px; | |
| cursor: pointer; | |
| transition: background 0.2s, transform 0.1s; | |
| font-family: 'Inter', sans-serif; | |
| } | |
| button:hover { | |
| background: var(--accent-hover); | |
| } | |
| button:active { | |
| transform: scale(0.98); | |
| } | |
| /* Dashboard */ | |
| #dashboard-container { | |
| display: none; | |
| padding: 32px; | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| width: 100%; | |
| } | |
| .header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 32px; | |
| } | |
| .header h1 { | |
| font-size: 20px; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .logout-btn { | |
| background: rgba(255, 255, 255, 0.1); | |
| width: auto; | |
| padding: 8px 16px; | |
| } | |
| .logout-btn:hover { | |
| background: rgba(239, 68, 68, 0.2); | |
| color: var(--danger); | |
| } | |
| .explorer-header { | |
| display: flex; | |
| align-items: center; | |
| padding: 16px 24px; | |
| border-bottom: 1px solid var(--glass-border); | |
| gap: 12px; | |
| } | |
| .breadcrumbs { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| flex: 1; | |
| overflow-x: auto; | |
| white-space: nowrap; | |
| } | |
| .crumb { | |
| color: var(--text-secondary); | |
| cursor: pointer; | |
| transition: color 0.2s; | |
| font-weight: 500; | |
| } | |
| .crumb:hover { | |
| color: var(--accent); | |
| } | |
| .crumb.active { | |
| color: var(--text-primary); | |
| cursor: default; | |
| } | |
| .separator { | |
| color: var(--glass-border); | |
| } | |
| .file-list { | |
| padding: 8px; | |
| min-height: 400px; | |
| } | |
| .file-item { | |
| display: flex; | |
| align-items: center; | |
| padding: 12px 16px; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| transition: background 0.2s; | |
| gap: 16px; | |
| } | |
| .file-item:hover { | |
| background: rgba(255, 255, 255, 0.05); | |
| } | |
| .file-icon { | |
| color: var(--text-secondary); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| width: 40px; | |
| height: 40px; | |
| background: rgba(0, 0, 0, 0.2); | |
| border-radius: 8px; | |
| } | |
| .file-item:hover .file-icon { | |
| color: var(--accent); | |
| } | |
| .file-name { | |
| flex: 1; | |
| font-weight: 500; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| white-space: nowrap; | |
| } | |
| .file-meta { | |
| color: var(--text-secondary); | |
| font-size: 13px; | |
| width: 120px; | |
| text-align: right; | |
| } | |
| .download-btn { | |
| background: transparent; | |
| width: 36px; | |
| height: 36px; | |
| padding: 0; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: var(--text-secondary); | |
| } | |
| .download-btn:hover { | |
| background: rgba(59, 130, 246, 0.2); | |
| color: var(--accent); | |
| } | |
| .empty-state { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| height: 300px; | |
| color: var(--text-secondary); | |
| gap: 16px; | |
| } | |
| #error-msg { | |
| color: var(--danger); | |
| font-size: 13px; | |
| margin-bottom: 16px; | |
| display: none; | |
| } | |
| /* Loading overlay */ | |
| #loader { | |
| position: fixed; | |
| top: 0; left: 0; right: 0; bottom: 0; | |
| background: rgba(15, 17, 21, 0.8); | |
| display: none; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 100; | |
| backdrop-filter: blur(4px); | |
| } | |
| .spinner { | |
| width: 40px; | |
| height: 40px; | |
| border: 3px solid rgba(255,255,255,0.1); | |
| border-radius: 50%; | |
| border-top-color: var(--accent); | |
| animation: spin 1s ease-in-out infinite; | |
| } | |
| @keyframes spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="loader"><div class="spinner"></div></div> | |
| <!-- Login Container --> | |
| <div id="login-container"> | |
| <div class="glass-panel login-box"> | |
| <h1>KeyStone</h1> | |
| <p>Secure Cloud Dashboard</p> | |
| <div id="error-msg">Invalid credentials</div> | |
| <form id="login-form"> | |
| <input type="text" id="username" placeholder="Username" required autocomplete="username"> | |
| <input type="password" id="password" placeholder="Password" required autocomplete="current-password"> | |
| <button type="submit">Sign In</button> | |
| </form> | |
| </div> | |
| </div> | |
| <!-- Dashboard Container --> | |
| <div id="dashboard-container"> | |
| <div class="header"> | |
| <h1><span class="material-icons-round" style="color: var(--accent);">cloud_done</span> KeyStone Dashboard</h1> | |
| <button class="logout-btn" onclick="logout()">Logout</button> | |
| </div> | |
| <div class="glass-panel"> | |
| <div class="explorer-header"> | |
| <button class="download-btn" onclick="goUp()" title="Go Up" id="btn-up" style="display: none;"> | |
| <span class="material-icons-round">arrow_upward</span> | |
| </button> | |
| <div class="breadcrumbs" id="breadcrumbs"> | |
| <!-- Rendered via JS --> | |
| </div> | |
| <button class="download-btn" onclick="downloadCurrentFolder()" title="Download Folder as ZIP" style="width: auto; padding: 0 16px; border-radius: 8px;"> | |
| <span class="material-icons-round">file_download</span> <span style="font-size: 13px; margin-left: 6px;">ZIP</span> | |
| </button> | |
| </div> | |
| <div class="file-list" id="file-list"> | |
| <!-- Rendered via JS --> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const API_BASE = ''; | |
| let currentPath = ''; | |
| // UI Helpers | |
| const showLoader = () => document.getElementById('loader').style.display = 'flex'; | |
| const hideLoader = () => document.getElementById('loader').style.display = 'none'; | |
| // Auth Flow | |
| const checkAuth = () => { | |
| const token = localStorage.getItem('token'); | |
| if (token) { | |
| document.getElementById('login-container').style.display = 'none'; | |
| document.getElementById('dashboard-container').style.display = 'block'; | |
| loadPath(''); | |
| } else { | |
| document.getElementById('login-container').style.display = 'flex'; | |
| document.getElementById('dashboard-container').style.display = 'none'; | |
| } | |
| }; | |
| document.getElementById('login-form').addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| const u = document.getElementById('username').value; | |
| const p = document.getElementById('password').value; | |
| showLoader(); | |
| try { | |
| const res = await fetch(`${API_BASE}/auth/login`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ username: u, password: p }) | |
| }); | |
| if (!res.ok) throw new Error('Invalid credentials'); | |
| const data = await res.json(); | |
| localStorage.setItem('token', data.token); | |
| document.getElementById('error-msg').style.display = 'none'; | |
| checkAuth(); | |
| } catch (err) { | |
| const errEl = document.getElementById('error-msg'); | |
| errEl.innerText = err.message; | |
| errEl.style.display = 'block'; | |
| } finally { | |
| hideLoader(); | |
| } | |
| }); | |
| const logout = () => { | |
| localStorage.removeItem('token'); | |
| checkAuth(); | |
| }; | |
| // Formatters | |
| const formatSize = (bytes) => { | |
| if (bytes === 0) return '--'; | |
| 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]; | |
| }; | |
| const formatDate = (ms) => { | |
| if (!ms) return '--'; | |
| return new Date(ms).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' }); | |
| }; | |
| // Explorer Logic | |
| const loadPath = async (path) => { | |
| showLoader(); | |
| const token = localStorage.getItem('token'); | |
| try { | |
| const res = await fetch(`${API_BASE}/api/web/explorer?path=${encodeURIComponent(path)}&token=${token}`); | |
| if (res.status === 401 || res.status === 403) { | |
| logout(); | |
| return; | |
| } | |
| const data = await res.json(); | |
| currentPath = data.path || ''; | |
| renderBreadcrumbs(); | |
| renderFiles(data.items); | |
| } catch (e) { | |
| console.error(e); | |
| alert("Failed to load directory."); | |
| } finally { | |
| hideLoader(); | |
| } | |
| }; | |
| const goUp = () => { | |
| if (!currentPath) return; | |
| const parts = currentPath.split('/'); | |
| parts.pop(); | |
| loadPath(parts.join('/')); | |
| }; | |
| const navigateTo = (folder) => { | |
| const newPath = currentPath ? `${currentPath}/${folder}` : folder; | |
| loadPath(newPath); | |
| }; | |
| const downloadCurrentFolder = () => { | |
| triggerDownload(currentPath); | |
| }; | |
| const triggerDownload = (itemPath) => { | |
| const token = localStorage.getItem('token'); | |
| const url = `${API_BASE}/api/web/download?path=${encodeURIComponent(itemPath)}&token=${token}`; | |
| // Create an invisible anchor tag to trigger native browser download stream | |
| // This is safer for large files/zip folders than window.open or fetch->blob | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| const parts = itemPath.split('/'); | |
| const fallbackName = parts[parts.length - 1] || 'download'; | |
| a.download = fallbackName; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| }; | |
| const renderBreadcrumbs = () => { | |
| const container = document.getElementById('breadcrumbs'); | |
| const parts = currentPath.split('/').filter(Boolean); | |
| document.getElementById('btn-up').style.display = parts.length > 0 ? 'flex' : 'none'; | |
| let html = `<div class="crumb ${parts.length === 0 ? 'active' : ''}" onclick="loadPath('')">Home</div>`; | |
| let accPath = ""; | |
| parts.forEach((p, i) => { | |
| accPath += (accPath ? "/" : "") + p; | |
| html += `<span class="separator">/</span>`; | |
| if (i === parts.length - 1) { | |
| html += `<div class="crumb active">${p}</div>`; | |
| } else { | |
| html += `<div class="crumb" onclick="loadPath('${accPath}')">${p}</div>`; | |
| } | |
| }); | |
| container.innerHTML = html; | |
| }; | |
| const renderFiles = (items) => { | |
| const container = document.getElementById('file-list'); | |
| if (items.length === 0) { | |
| container.innerHTML = ` | |
| <div class="empty-state"> | |
| <span class="material-icons-round" style="font-size: 48px; opacity: 0.5;">folder_open</span> | |
| <p>This folder is empty</p> | |
| </div> | |
| `; | |
| return; | |
| } | |
| let html = ''; | |
| items.forEach(item => { | |
| const icon = item.is_dir ? 'folder' : (item.name.match(/\.(mp4|mov)$/i) ? 'movie' : 'image'); | |
| const fullPath = currentPath ? `${currentPath}/${item.name}` : item.name; | |
| html += ` | |
| <div class="file-item" onclick="${item.is_dir ? `navigateTo('${item.name}')` : `triggerDownload('${fullPath}')`}"> | |
| <div class="file-icon"> | |
| <span class="material-icons-round">${icon}</span> | |
| </div> | |
| <div class="file-name">${item.name}</div> | |
| <div class="file-meta">${formatDate(item.modified)}</div> | |
| <div class="file-meta">${formatSize(item.size)}</div> | |
| <button class="download-btn" onclick="event.stopPropagation(); triggerDownload('${fullPath}')" title="Download"> | |
| <span class="material-icons-round">download</span> | |
| </button> | |
| </div> | |
| `; | |
| }); | |
| container.innerHTML = html; | |
| }; | |
| // Initialize | |
| checkAuth(); | |
| </script> | |
| </body> | |
| </html> | |