Download static/index.html from asons/AND: direct link, hf CLI and curl.
- Browser
- Download file 33.9 kB
-
https://huggingface.co/spaces/asons/AND/resolve/main/static/index.html
- Command line
-
hf download hf://spaces/asons/AND/static/index.html
-
curl -L -o index.html https://huggingface.co/spaces/asons/AND/resolve/main/static/index.html
33.9 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Cloud Phone Assistant</title> | |
| <style> | |
| :root { | |
| --primary: #0d6efd; /* Blue - Login Fix */ | |
| --primary2: #fd7e14; /* Orange - Account Sync */ | |
| --primary3: #20c997; /* Green - Hubble Sync */ | |
| --ok: #198754; | |
| --err: #dc3545; | |
| --warn: #ffc107; | |
| --bg: #f5f7fa; | |
| --card: #ffffff; | |
| --muted: #6c757d; | |
| --border: #e9ecef; | |
| } | |
| * { box-sizing: border-box; } | |
| body { | |
| margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", | |
| "Microsoft YaHei", sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; color: #212529; display: flex; align-items: center; | |
| justify-content: center; padding: 20px; | |
| } | |
| .hidden { display: none ; } | |
| .container { | |
| background: var(--card); border-radius: 16px; padding: 32px; | |
| box-shadow: 0 20px 50px rgba(0,0,0,0.18); | |
| width: 100%; max-width: 820px; | |
| } | |
| h1 { font-size: 22px; font-weight: 600; margin: 0 0 6px 0; text-align: center; } | |
| .subtitle { text-align: center; color: var(--muted); font-size: 13px; margin-bottom: 24px; } | |
| .login-card { max-width: 380px; margin: 0 auto; } | |
| .field { margin-bottom: 14px; } | |
| label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 6px; font-weight: 500; } | |
| input[type=text], input[type=password] { | |
| width: 100%; padding: 10px 12px; border: 1px solid var(--border); border-radius: 8px; | |
| font-size: 14px; transition: border-color .15s; | |
| } | |
| input:focus { outline: none; border-color: var(--primary); } | |
| input:disabled { background: #f8f9fa; color: var(--muted); } | |
| .btn { | |
| width: 100%; padding: 11px; border: none; border-radius: 8px; color: white; | |
| font-size: 15px; font-weight: 500; cursor: pointer; transition: all .15s; | |
| } | |
| .btn-primary { background: var(--primary); } | |
| .btn-primary:hover:not(:disabled) { background: #0b5ed7; } | |
| .btn-orange { background: var(--primary2); } | |
| .btn-orange:hover:not(:disabled) { background: #e66a00; } | |
| .btn-teal { background: var(--primary3); } | |
| .btn-teal:hover:not(:disabled) { background: #12b886; } | |
| .btn-ok { background: var(--ok); } | |
| .btn-err { background: var(--err); } | |
| .btn-warn { background: var(--warn); color: #000; } | |
| .btn:disabled { opacity: .55; cursor: not-allowed; } | |
| .btn-text { | |
| background: transparent; color: var(--muted); border: 1px solid var(--border); | |
| } | |
| /* Mode Selection Segmented Control */ | |
| .mode-seg { | |
| display: flex; gap: 0; background: #f1f3f5; border-radius: 8px; | |
| padding: 4px; margin-bottom: 14px; | |
| } | |
| .mode-seg .seg-btn { | |
| flex: 1; padding: 9px 10px; text-align: center; font-size: 13px; | |
| border: none; background: transparent; color: var(--muted); cursor: pointer; | |
| border-radius: 6px; font-weight: 500; transition: all .15s; | |
| } | |
| .mode-seg .seg-btn.active { | |
| background: var(--card); color: #212529; | |
| box-shadow: 0 1px 3px rgba(0,0,0,0.1); font-weight: 600; | |
| } | |
| /* Highlight active seg with corresponding colors by mode */ | |
| .mode-seg .seg-btn.active[data-mode="restore"] { border-top: 2px solid var(--primary); } | |
| .mode-seg .seg-btn.active[data-mode="sync"] { border-top: 2px solid var(--primary2); } | |
| .mode-seg .seg-btn.active[data-mode="hubble"] { border-top: 2px solid var(--primary3); } | |
| .mode-seg .seg-btn.active[data-mode="hubble_sync"] { border-top: 2px solid var(--primary3); } | |
| .mode-seg .seg-btn.active[data-mode="hubble_launch"] { border-top: 2px solid var(--primary3); } | |
| .mode-seg .seg-btn.active[data-mode="hubble_quick"] { border-top: 2px solid var(--primary3); } | |
| .mode-seg .seg-btn:hover:not(.active):not(:disabled) { color: #212529; } | |
| .mode-seg .seg-btn:disabled { cursor: not-allowed; opacity: .6; } | |
| .mode-hint { | |
| font-size: 12px; color: var(--muted); margin-top: -6px; margin-bottom: 14px; | |
| padding: 8px 10px; background: #f8f9fa; border-radius: 6px; | |
| border-left: 3px solid var(--primary); | |
| } | |
| .mode-hint.sync { border-left-color: var(--primary2); background: #fff4ea; } | |
| .mode-hint.hubble { border-left-color: var(--primary3); background: #e6fcf5; } | |
| /* Workspace */ | |
| .workspace { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; align-items: start; } | |
| @media (max-width: 680px) { .workspace { grid-template-columns: 1fr; } } | |
| .form-area textarea { | |
| width: 100%; padding: 10px 12px; border: 1px solid var(--border); border-radius: 8px; | |
| font-size: 13px; font-family: Consolas, monospace; resize: vertical; | |
| } | |
| .ring-area { | |
| display: flex; flex-direction: column; align-items: center; justify-content: center; | |
| padding-top: 12px; | |
| } | |
| .ring-wrap { position: relative; width: 220px; height: 220px; } | |
| .ring-wrap svg { transform: rotate(-90deg); width: 100%; height: 100%; } | |
| .ring-bg { stroke: var(--border); } | |
| .ring-fg { | |
| stroke: var(--primary); | |
| transition: stroke-dashoffset .35s ease-out, stroke .3s; | |
| stroke-linecap: round; | |
| } | |
| .ring-fg.ok { stroke: var(--ok); } | |
| .ring-fg.err { stroke: var(--err); } | |
| .ring-fg.run { stroke: var(--primary); } | |
| .ring-fg.run.sync { stroke: var(--primary2); } | |
| .ring-fg.run.hubble { stroke: var(--primary3); } | |
| .ring-center { | |
| position: absolute; inset: 0; display: flex; flex-direction: column; | |
| align-items: center; justify-content: center; | |
| } | |
| .ring-pct { font-size: 44px; font-weight: 700; color: #212529; line-height: 1; } | |
| .ring-pct small { font-size: 20px; color: var(--muted); font-weight: 500; } | |
| .ring-label { font-size: 13px; color: var(--muted); margin-top: 8px; } | |
| .ring-label.ok { color: var(--ok); font-weight: 600; } | |
| .ring-label.err { color: var(--err); font-weight: 600; } | |
| .ring-label.run { color: var(--primary); font-weight: 600; } | |
| .ring-label.run.sync { color: var(--primary2); font-weight: 600; } | |
| .ring-label.run.hubble { color: var(--primary3); font-weight: 600; } | |
| .spinner { | |
| display: inline-block; width: 8px; height: 8px; border-radius: 50%; | |
| background: currentColor; margin-right: 6px; | |
| animation: pulse 1.2s ease-in-out infinite; | |
| vertical-align: middle; | |
| } | |
| @keyframes pulse { 0%,100% { opacity: .3; } 50% { opacity: 1; } } | |
| .topbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 18px; font-size: 13px; } | |
| .topbar .muted { color: var(--muted); } | |
| .topbar a { color: var(--muted); text-decoration: none; cursor: pointer; } | |
| .topbar a:hover { color: var(--primary); } | |
| .err-box { | |
| margin-top: 14px; padding: 10px 14px; border-radius: 6px; | |
| background: #fff3f3; border: 1px solid #f5c2c7; color: var(--err); | |
| font-size: 13px; | |
| } | |
| .admin-entry { text-align: center; margin-top: 14px; font-size: 12px; } | |
| .admin-entry a { color: var(--muted); text-decoration: none; cursor: pointer; } | |
| .admin-entry a:hover { color: var(--primary); } | |
| /* Admin Panel */ | |
| .container.admin { max-width: 960px; } | |
| .admin-head { | |
| display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; | |
| } | |
| .admin-head h1 { margin: 0; text-align: left; } | |
| .admin-gen { | |
| background: #f8f9fa; padding: 14px 16px; border-radius: 8px; | |
| display: flex; align-items: center; gap: 8px; flex-wrap: wrap; | |
| margin-bottom: 16px; | |
| } | |
| .admin-gen label { margin: 0; font-size: 13px; color: var(--muted); } | |
| .admin-gen input[type=number] { | |
| width: 90px; padding: 6px 10px; border: 1px solid var(--border); border-radius: 5px; | |
| font-size: 13px; | |
| } | |
| .admin-gen .btn { width: auto; padding: 7px 16px; font-size: 13px; margin-left: auto; } | |
| .admin-msg { | |
| padding: 9px 12px; border-radius: 6px; font-size: 13px; margin-bottom: 12px; | |
| } | |
| .admin-msg.ok { background: #d1e7dd; color: #0f5132; border: 1px solid #badbcc; } | |
| .admin-msg.err { background: #f8d7da; color: #842029; border: 1px solid #f5c2c7; } | |
| table.admin-table { width: 100%; border-collapse: collapse; font-size: 13px; } | |
| table.admin-table th { | |
| text-align: left; padding: 10px; background: #f8f9fa; color: var(--muted); | |
| font-weight: 500; border-bottom: 2px solid var(--border); font-size: 12px; | |
| } | |
| table.admin-table td { | |
| padding: 10px; border-bottom: 1px solid var(--border); vertical-align: middle; | |
| } | |
| table.admin-table tr:hover td { background: #fafbfc; } | |
| table.admin-table code { | |
| color: #d63384; background: #f1f3f5; padding: 2px 6px; border-radius: 3px; | |
| font-family: Consolas, monospace; | |
| } | |
| .badge { display: inline-block; padding: 2px 10px; border-radius: 12px; font-size: 11px; font-weight: 500; } | |
| .badge-green { background: #d1e7dd; color: #0f5132; } | |
| .badge-gray { background: #e9ecef; color: #6c757d; } | |
| .btn-tiny { | |
| padding: 4px 10px; font-size: 12px; width: auto; border-radius: 5px; | |
| border: 1px solid var(--err); background: transparent; color: var(--err); cursor: pointer; | |
| } | |
| .btn-tiny:hover { background: var(--err); color: #fff; } | |
| .link-edit { | |
| cursor: pointer; color: var(--primary); margin-left: 6px; | |
| font-size: 13px; user-select: none; | |
| } | |
| .muted-time { color: #adb5bd; font-size: 12px; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <!-- Login Section --> | |
| <section id="login"> | |
| <h1>🔧 Cloud Phone Assistant</h1> | |
| <div class="subtitle">Enter access key to continue</div> | |
| <div class="login-card"> | |
| <div class="field"> | |
| <label>Access Key</label> | |
| <input type="text" id="key" placeholder="VIP-XXXX-XXXX" autofocus> | |
| </div> | |
| <button class="btn btn-primary" id="btn-login" onclick="login()">Verify & Enter</button> | |
| <div class="admin-entry"> | |
| <a onclick="adminLogin()">Admin Gateway</a> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Workspace Section --> | |
| <section id="work" class="hidden"> | |
| <div class="topbar"> | |
| <div class="muted" id="uses-info">—</div> | |
| <a onclick="logout()">Logout</a> | |
| </div> | |
| <h1>🔧 Cloud Phone Assistant</h1> | |
| <div class="subtitle">Paste credentials, execute with one click</div> | |
| <!-- Operation Mode Segmented Control --> | |
| <div class="mode-seg" id="mode-seg"> | |
| <button class="seg-btn active" data-mode="restore" onclick="selectMode('restore')"> | |
| ① Fix Login Page | |
| </button> | |
| <button class="seg-btn" data-mode="sync" onclick="selectMode('sync')"> | |
| ② Account Sync | |
| </button> | |
| <button class="seg-btn" data-mode="hubble" onclick="selectMode('hubble')"> | |
| ③ Hubble Sync | |
| </button> | |
| <button class="seg-btn" data-mode="hubble_sync" onclick="selectMode('hubble_sync')"> | |
| ④ Sync Only | |
| </button> | |
| <button class="seg-btn" data-mode="hubble_launch" onclick="selectMode('hubble_launch')"> | |
| ⑤ Launch Page | |
| </button> | |
| <button class="seg-btn" data-mode="hubble_quick" onclick="selectMode('hubble_quick')"> | |
| ⑥ Quick Launch | |
| </button> | |
| </div> | |
| <div class="mode-hint" id="mode-hint"> | |
| Restores login page configuration for Hong Kong mobile package (Google / Email entry etc.). Does not affect accounts or alter the did. | |
| </div> | |
| <div class="workspace"> | |
| <!-- Left Panel: Input Fields --> | |
| <div class="form-area"> | |
| <div class="field"> | |
| <label>① SSH Command</label> | |
| <textarea id="ssh-cmd" rows="3" placeholder="ssh -oHostKeyAlgorithms=... user@host -p 1824 -L 7798:adb-proxy:60063 -Nf"></textarea> | |
| </div> | |
| <div class="field"> | |
| <label>② Password (Token)</label> | |
| <input type="password" id="ssh-pwd" placeholder="base64 long string"> | |
| </div> | |
| <button class="btn btn-primary" id="btn-start" onclick="startTask()">🚀 Start Repair</button> | |
| <div class="err-box hidden" id="err-box"></div> | |
| </div> | |
| <!-- Right Panel: Progress Ring --> | |
| <div class="ring-area"> | |
| <div class="ring-wrap"> | |
| <svg viewBox="0 0 120 120"> | |
| <circle cx="60" cy="60" r="54" fill="none" stroke-width="8" class="ring-bg"/> | |
| <circle cx="60" cy="60" r="54" fill="none" stroke-width="8" | |
| id="ring-fg" class="ring-fg" | |
| stroke-dasharray="339.29" stroke-dashoffset="339.29"/> | |
| </svg> | |
| <div class="ring-center"> | |
| <div class="ring-pct"><span id="pct-num">0</span><small>%</small></div> | |
| </div> | |
| </div> | |
| <div class="ring-label" id="ring-label">Ready</div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Admin Console Panel --> | |
| <section id="admin" class="hidden"> | |
| <div class="admin-head"> | |
| <h1>👑 Key Management Console</h1> | |
| <a class="muted" style="cursor:pointer;font-size:13px;color:var(--muted);text-decoration:none" onclick="logout()">Exit Console</a> | |
| </div> | |
| <div class="admin-gen"> | |
| <label>Generate</label> | |
| <input type="number" id="gen-count" value="1" min="1" max="50"> | |
| <label>keys, with</label> | |
| <input type="number" id="gen-uses" value="9999" min="1"> | |
| <label>uses each</label> | |
| <button class="btn btn-primary" onclick="adminGenerate()">Generate</button> | |
| <button class="btn btn-text" style="width:auto;padding:7px 14px;" onclick="adminLoadKeys()">Refresh</button> | |
| </div> | |
| <div id="admin-msg"></div> | |
| <table class="admin-table"> | |
| <thead> | |
| <tr> | |
| <th>Access Key</th> | |
| <th style="width:100px">Status</th> | |
| <th style="width:130px">Uses Remaining</th> | |
| <th style="width:160px">Created At</th> | |
| <th style="width:160px">Last Used</th> | |
| <th style="width:80px">Action</th> | |
| </tr> | |
| </thead> | |
| <tbody id="admin-keys"></tbody> | |
| </table> | |
| </section> | |
| </div> | |
| <script> | |
| // ============= Settings ============= | |
| const API_BASE = ""; // Standard endpoint, proxies via same-domain Pages Functions | |
| let token = ""; | |
| let currentRole = ""; | |
| let running = false; | |
| let currentMode = "restore"; // "restore" | "sync" | |
| const RING_LEN = 339.29; // 2πr r=54 | |
| // Metadata Configuration mapping for execution modes | |
| const MODES = { | |
| restore: { | |
| endpoint: "/api/v2/submit_task", | |
| btnLabel: "🚀 Start Repair", | |
| btnLabelDone: "✓ Repair Again", | |
| btnColor: "btn-primary", | |
| hint: "Reset to HK login page interface (Google/Email/Line entry). ⚠ If target package is currently signed in, account data will be cleared, requiring re-authentication.", | |
| ringColor: "", // Blue | |
| }, | |
| sync: { | |
| endpoint: "/api/v2/sync", | |
| btnLabel: "🔄 Start Sync", | |
| btnLabelDone: "✓ Sync Again", | |
| btnColor: "btn-orange", | |
| hint: "Local hardware profile account synchronization.", | |
| ringColor: "sync", | |
| }, | |
| hubble: { | |
| endpoint: "/api/v2/hubble", | |
| btnLabel: "📦 Sync + Launch", | |
| btnLabelDone: "✓ Try Again", | |
| btnColor: "btn-teal", | |
| hint: "Cross-platform mobile → hubble credentials migration + automatic launch authorization gateway. Expect an initial ~5 minutes provisioning setup time.", | |
| ringColor: "hubble", | |
| }, | |
| hubble_sync: { | |
| endpoint: "/api/v2/hubble_sync", | |
| btnLabel: "📋 Sync Data Only", | |
| btnLabelDone: "✓ Re-sync Data", | |
| btnColor: "btn-teal", | |
| hint: "Migrates mobile → hubble profile information data (keva) without loading page layout. Execution time approx. 30 seconds.", | |
| ringColor: "hubble", | |
| }, | |
| hubble_launch: { | |
| endpoint: "/api/v2/hubble_launch", | |
| btnLabel: "🚀 Launch Page Only", | |
| btnLabelDone: "✓ Re-launch Page", | |
| btnColor: "btn-teal", | |
| hint: "Warmup sequences + environment check + load gateway layer. Recommended for troubleshooting white screens following data sync. Runtime approx 2 mins.", | |
| ringColor: "hubble", | |
| }, | |
| hubble_quick: { | |
| endpoint: "/api/v2/hubble_quick", | |
| btnLabel: "⚡ Quick Launch", | |
| btnLabelDone: "⚡ Trigger Again", | |
| btnColor: "btn-teal", | |
| hint: "Dispatches direct setup commands immediately skipping initialization pipelines. ~15 seconds runtime speed, execution success not guaranteed.", | |
| ringColor: "hubble", | |
| }, | |
| }; | |
| function selectMode(m) { | |
| if (running) return; | |
| currentMode = m; | |
| // Update selection states on container UI indicators | |
| for (const b of document.querySelectorAll("#mode-seg .seg-btn")) { | |
| b.classList.toggle("active", b.dataset.mode === m); | |
| } | |
| // Update user contextual notifications | |
| const hint = document.getElementById("mode-hint"); | |
| hint.textContent = MODES[m].hint; | |
| hint.classList.remove("sync", "hubble"); | |
| if (m === "sync") hint.classList.add("sync"); | |
| if (m.startsWith("hubble")) hint.classList.add("hubble"); | |
| // Update context action trigger configuration | |
| const btn = document.getElementById("btn-start"); | |
| btn.className = "btn " + MODES[m].btnColor; | |
| btn.textContent = MODES[m].btnLabel; | |
| // Clean monitoring ring elements | |
| setRing(0, "idle"); | |
| } | |
| function lockMode(lock) { | |
| for (const b of document.querySelectorAll("#mode-seg .seg-btn")) { | |
| b.disabled = lock; | |
| } | |
| } | |
| // ============= Processing Visualization Ring ============= | |
| function setRing(pct, state) { | |
| pct = Math.max(0, Math.min(100, pct)); | |
| document.getElementById("pct-num").textContent = Math.round(pct); | |
| const fg = document.getElementById("ring-fg"); | |
| const offset = RING_LEN * (1 - pct / 100); | |
| // UI Optimisation: If rendering resets back to zero state while executing, disable transitions cleanly to avoid asset shadows. | |
| if (pct === 0 && state === "run") { | |
| fg.style.transition = 'none'; | |
| fg.setAttribute("stroke-dashoffset", offset); | |
| void fg.getBoundingClientRect(); // Triggers continuous element redrawing cycles | |
| fg.style.transition = ''; | |
| } else { | |
| fg.setAttribute("stroke-dashoffset", offset); | |
| } | |
| const label = document.getElementById("ring-label"); | |
| // Performance Optimization: Cache processing workflows via target data attributes preventing performance blockades due to extreme layout calculations. | |
| if (label.dataset.state !== state || label.dataset.mode !== currentMode) { | |
| fg.classList.remove("ok", "err", "run", "sync"); | |
| label.classList.remove("ok", "err", "run", "sync"); | |
| const modeCls = MODES[currentMode].ringColor; | |
| if (state === "run") { | |
| fg.classList.add("run"); | |
| label.classList.add("run"); | |
| if (modeCls) { fg.classList.add(modeCls); label.classList.add(modeCls); } | |
| const runLabel = {restore: "Processing", sync: "Syncing", hubble: "Syncing", hubble_sync: "Syncing", hubble_launch: "Launching", hubble_quick: "Launching"}[currentMode] || "Executing"; | |
| label.innerHTML = `<span class="spinner"></span>${runLabel}`; | |
| } else if (state === "ok") { | |
| fg.classList.add("ok"); | |
| label.classList.add("ok"); | |
| const okLabel = {restore: "✓ Fixed Successfully", sync: "✓ Synced Successfully", hubble: "✓ Synced Successfully", hubble_sync: "✓ Synced Successfully", hubble_launch: "✓ Launched Successfully", hubble_quick: "⚡ Command Sent"}[currentMode] || "✓ Completed"; | |
| label.textContent = okLabel; | |
| } else if (state === "err") { | |
| fg.classList.add("err"); | |
| label.classList.add("err"); | |
| const errLabel = {restore: "✗ Repair Failed", sync: "✗ Sync Failed", hubble: "✗ Sync Failed", hubble_sync: "✗ Sync Failed", hubble_launch: "✗ Launch Failed", hubble_quick: "✗ Failed"}[currentMode] || "✗ Failed"; | |
| label.textContent = errLabel; | |
| } else { | |
| label.textContent = "Ready"; | |
| } | |
| label.dataset.state = state; | |
| label.dataset.mode = currentMode; | |
| } | |
| } | |
| // ============= API Configuration Pipeline ============= | |
| async function api(path, opts = {}) { | |
| opts.headers = Object.assign({ "Content-Type": "application/json" }, opts.headers || {}); | |
| if (token) opts.headers["Authorization"] = token; | |
| const r = await fetch(API_BASE + path, opts); | |
| if (!r.ok) { | |
| let msg = `HTTP ${r.status}`; | |
| try { msg = (await r.json()).detail || msg; } catch {} | |
| throw new Error(msg); | |
| } | |
| return r.json(); | |
| } | |
| // ============= Account Access Authentication ============= | |
| async function login() { | |
| const key = document.getElementById("key").value.trim(); | |
| if (!key) return alert("Please enter your access key"); | |
| const btn = document.getElementById("btn-login"); | |
| btn.disabled = true; | |
| btn.textContent = "Verifying…"; | |
| try { | |
| const r = await api("/api/verify", { | |
| method: "POST", | |
| body: JSON.stringify({ key }), | |
| }); | |
| token = r.token; | |
| currentRole = r.role; | |
| if (r.role === "admin") { | |
| document.getElementById("login").classList.add("hidden"); | |
| document.getElementById("admin").classList.remove("hidden"); | |
| document.querySelector(".container").classList.add("admin"); | |
| adminLoadKeys(); | |
| } else { | |
| document.getElementById("uses-info").textContent = | |
| `Key: ${key.slice(0, 6)}*** · Remaining: ${r.uses_left ?? "?"} allocation pools`; | |
| document.getElementById("login").classList.add("hidden"); | |
| document.getElementById("work").classList.remove("hidden"); | |
| selectMode("restore"); | |
| setRing(0, "idle"); | |
| } | |
| } catch (e) { | |
| alert("Authentication failed: " + e.message); | |
| } finally { | |
| btn.disabled = false; | |
| btn.textContent = "Verify & Enter"; | |
| } | |
| } | |
| // ============= Execution Controls Manager ============= | |
| async function startTask() { | |
| if (running) return; | |
| const ssh = document.getElementById("ssh-cmd").value.trim(); | |
| const pwd = document.getElementById("ssh-pwd").value.trim(); | |
| if (!ssh) return alert("Please enter the SSH command parameter"); | |
| if (!pwd) return alert("Please enter your authorization token credentials"); | |
| const raw = ssh + "\n" + pwd; | |
| lastRingVal = -1; | |
| setRing(0, "run"); | |
| hideErr(); | |
| lockMode(true); | |
| const btn = document.getElementById("btn-start"); | |
| btn.disabled = true; | |
| btn.className = "btn btn-warn"; | |
| btn.textContent = {restore: "Fixing…", sync: "Syncing…", hubble: "Syncing…", hubble_sync: "Syncing…", hubble_launch: "Launching…", hubble_quick: "Sending…"}[currentMode] || "Executing…"; | |
| running = true; | |
| for (const id of ["ssh-cmd", "ssh-pwd"]) | |
| document.getElementById(id).disabled = true; | |
| const endpoint = MODES[currentMode].endpoint; | |
| try { | |
| const resp = await fetch(API_BASE + endpoint, { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| "Authorization": token, | |
| }, | |
| body: JSON.stringify({ raw_input: raw }), | |
| }); | |
| if (!resp.ok) { | |
| let msg = "HTTP " + resp.status; | |
| try { msg = (await resp.json()).detail || msg; } catch {} | |
| throw new Error(msg); | |
| } | |
| const reader = resp.body.getReader(); | |
| const decoder = new TextDecoder("utf-8"); | |
| let buf = ""; | |
| while (true) { | |
| const { value, done } = await reader.read(); | |
| if (done) break; | |
| buf += decoder.decode(value, { stream: true }); | |
| // Processing architecture regex updates to parse data buffers matching standard \r\n\r\n and \n\n boundaries | |
| const parts = buf.split(/\r?\n\r?\n/); | |
| buf = parts.pop(); | |
| for (const chunk of parts) { | |
| // Normalize standard \r platform layouts within line segmentation targets | |
| const dline = chunk.split(/\r?\n/).find(l => l.startsWith("data: ")); | |
| if (!dline) continue; | |
| try { | |
| handleEvent(JSON.parse(dline.slice(6))); | |
| } catch (e) { | |
| console.warn("JSON parsing failure encountered parsing dataset:", dline); | |
| } | |
| } | |
| } | |
| // Stream resolution fallbacks | |
| if (running) { | |
| setRing(100, "ok"); | |
| finishUI(true); | |
| } | |
| } catch (e) { | |
| showErr(e.message); | |
| setRing(0, "err"); | |
| finishUI(false); | |
| } | |
| } | |
| let lastRingVal = -1; | |
| function applyDone(ev) { | |
| if (ev.ok) { | |
| setRing(100, "ok"); | |
| } else { | |
| showErr(ev.error || "Unknown response error status"); | |
| setRing(ev.value || 0, "err"); | |
| } | |
| if (ev.uses_left != null) { | |
| const el = document.getElementById("uses-info"); | |
| if (el) el.textContent = el.textContent.replace(/Remaining: \S+ allocation pools/, `Remaining: ${ev.uses_left} allocation pools`); | |
| } | |
| finishUI(ev.ok); | |
| } | |
| function handleEvent(ev) { | |
| if (ev.type === "progress") { | |
| if (ev.value !== lastRingVal) { | |
| setRing(ev.value, "run"); | |
| lastRingVal = ev.value; | |
| } | |
| } else if (ev.type === "done") { | |
| applyDone(ev); | |
| } | |
| } | |
| function finishUI(ok) { | |
| running = false; | |
| lockMode(false); | |
| const btn = document.getElementById("btn-start"); | |
| btn.disabled = false; | |
| if (ok) { | |
| btn.className = "btn btn-ok"; | |
| btn.textContent = MODES[currentMode].btnLabelDone; | |
| } else { | |
| btn.className = "btn btn-err"; | |
| btn.textContent = "✗ Retry Action"; | |
| } | |
| for (const id of ["ssh-cmd", "ssh-pwd"]) | |
| document.getElementById(id).disabled = false; | |
| } | |
| function showErr(msg) { | |
| const e = document.getElementById("err-box"); | |
| e.textContent = "❌ " + msg; | |
| e.classList.remove("hidden"); | |
| } | |
| function hideErr() { document.getElementById("err-box").classList.add("hidden"); } | |
| function logout() { | |
| if (running && !confirm("An operation sequence is executing. Are you sure you want to terminate sessions?")) return; | |
| token = ""; currentRole = ""; running = false; | |
| document.getElementById("key").value = ""; | |
| const pwd = document.getElementById("ssh-pwd"); | |
| if (pwd) pwd.value = ""; | |
| document.getElementById("work").classList.add("hidden"); | |
| document.getElementById("admin").classList.add("hidden"); | |
| document.querySelector(".container").classList.remove("admin"); | |
| document.getElementById("login").classList.remove("hidden"); | |
| setRing(0, "idle"); | |
| } | |
| document.getElementById("key").addEventListener("keydown", e => { | |
| if (e.key === "Enter") login(); | |
| }); | |
| function adminLogin() { | |
| const pwd = prompt("Enter system administrator credential passphrase:"); | |
| if (!pwd) return; | |
| document.getElementById("key").value = pwd; | |
| login(); | |
| } | |
| // ============= System Administrator Configuration Modules ============= | |
| async function adminLoadKeys() { | |
| try { | |
| const rows = await api("/api/admin/list_keys"); | |
| renderKeyTable(rows); | |
| } catch (e) { | |
| showAdminMsg("Failed to load keys: " + e.message, "err"); | |
| } | |
| } | |
| async function adminGenerate() { | |
| const count = parseInt(document.getElementById("gen-count").value, 10) || 1; | |
| const uses = parseInt(document.getElementById("gen-uses").value, 10) || 9999; | |
| try { | |
| const r = await api("/api/admin/generate_keys", { | |
| method: "POST", | |
| body: JSON.stringify({ count, uses }), | |
| }); | |
| showAdminMsg( | |
| `Generated ${r.keys.length} keys (${r.uses_left} usages each): <code>${r.keys.join(", ")}</code>`, | |
| "ok" | |
| ); | |
| adminLoadKeys(); | |
| } catch (e) { | |
| showAdminMsg("Generation sequence failed: " + e.message, "err"); | |
| } | |
| } | |
| async function adminDelete(key) { | |
| if (!confirm(`Confirm absolute removal authorization profiles for key ${key}?`)) return; | |
| try { | |
| await api("/api/admin/delete_key", { | |
| method: "POST", | |
| body: JSON.stringify({ key }), | |
| }); | |
| adminLoadKeys(); | |
| } catch (e) { | |
| alert("Removal transaction failed: " + e.message); | |
| } | |
| } | |
| async function adminEditUses(key, current) { | |
| const v = prompt(`Modify database transaction allocation pools for key ${key} (Currently ${current}):`, current); | |
| if (v === null) return; | |
| const n = parseInt(v, 10); | |
| if (isNaN(n) || n < 0) return alert("Parameters provided must contain positive values"); | |
| try { | |
| await api("/api/admin/set_uses", { | |
| method: "POST", | |
| body: JSON.stringify({ key, uses: n }), | |
| }); | |
| adminLoadKeys(); | |
| } catch (e) { | |
| alert("Database status updates rejected: " + e.message); | |
| } | |
| } | |
| function fmtTime(t) { return t ? new Date(t * 1000).toLocaleString() : "-"; } | |
| function escapeHtml(s) { | |
| return String(s).replace(/[&<>"']/g, c => | |
| ({"&":"&","<":"<",">":">","\"":""","'":"'"}[c])); | |
| } | |
| function renderKeyTable(rows) { | |
| const tb = document.getElementById("admin-keys"); | |
| tb.innerHTML = ""; | |
| if (!rows.length) { | |
| tb.innerHTML = '<tr><td colspan="6" style="text-align:center;padding:24px;color:var(--muted)">No keys found in database</td></tr>'; | |
| return; | |
| } | |
| for (const r of rows) { | |
| const uses = r.uses_left ?? 0; | |
| const badge = r.status === "unused" | |
| ? '<span class="badge badge-green">Unused</span>' | |
| : '<span class="badge badge-gray">Active</span>'; | |
| const safeKey = escapeHtml(r.key); | |
| tb.innerHTML += `<tr> | |
| <td><code>${safeKey}</code></td> | |
| <td>${badge}</td> | |
| <td> | |
| <span style="color:${uses > 0 ? 'var(--ok)' : 'var(--muted)'}">${uses}</span> | |
| <span class="link-edit" title="Edit uses" onclick="adminEditUses('${safeKey}', ${uses})">✎</span> | |
| </td> | |
| <td><span class="muted-time">${fmtTime(r.created_at)}</span></td> | |
| <td><span class="muted-time">${fmtTime(r.used_at)}</span></td> | |
| <td><button class="btn-tiny" onclick="adminDelete('${safeKey}')">Delete</button></td> | |
| </tr>`; | |
| } | |
| } | |
| function showAdminMsg(html, cls) { | |
| document.getElementById("admin-msg").innerHTML = | |
| `<div class="admin-msg ${cls}">${html}</div>`; | |
| if (cls === "ok") { | |
| setTimeout(() => { | |
| const el = document.getElementById("admin-msg"); | |
| if (el) el.innerHTML = ""; | |
| }, 8000); | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> |