diff --git a/claude-dev/Dockerfile b/claude-dev/Dockerfile index 5eeecab..d5e3d2b 100644 --- a/claude-dev/Dockerfile +++ b/claude-dev/Dockerfile @@ -16,6 +16,7 @@ RUN apt-get update \ python3 \ ripgrep \ rsync \ + tmux \ unzip \ wget \ zip \ @@ -31,20 +32,22 @@ RUN npm install -g @anthropic-ai/claude-code RUN python3 - <<'PY' from pathlib import Path +import re path = Path("/usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js") if not path.exists(): raise SystemExit(f"claude cli not found: {path}") content = path.read_text() -old = 'F8.get("https://api.anthropic.com/api/hello",{headers:K8($)}).catch((()=>!1))' -if old not in content: +pattern = r'let A=U7\(\),q=new URL\(A\.TOKEN_URL\),K=\[`\$\{A\.BASE_API_URL\}/api/hello`,`\$\{q\.origin\}/v1/oauth/hello`\],Y=async\(_\)=>\{try\{let \$=await I8\.get\(_,\{headers:\{"User-Agent":ey\(\)\}\}\);if\(\$\.status!==200\)return\{success:!1,error:`Failed to connect to \$\{new URL\(_\)\.hostname\}: Status \$\{\$\.status\}`\};return\{success:!0\}\}catch\(\$\)\{' +replacement = 'return' +patched, count = re.subn(pattern, replacement, content, count=1) +if count != 1: raise SystemExit("preflight patch target not found in claude cli; refusing to build") -patched = content.replace(old, "!0") path.write_text(patched) -if old in path.read_text(): +if re.search(pattern, path.read_text()): raise SystemExit("preflight patch did not apply cleanly") print("Applied Claude preflight bypass patch") diff --git a/claude-dev/required-tools.txt b/claude-dev/required-tools.txt index c1641eb..99731b6 100644 --- a/claude-dev/required-tools.txt +++ b/claude-dev/required-tools.txt @@ -13,4 +13,5 @@ rsync zip unzip make +tmux claude diff --git a/dashboard/frontend/src/App.svelte b/dashboard/frontend/src/App.svelte index 68bf340..c877037 100644 --- a/dashboard/frontend/src/App.svelte +++ b/dashboard/frontend/src/App.svelte @@ -16,6 +16,21 @@ import { onMount } from "svelte"; import { getToken, checkAuth, setToken, setRefreshToken, currentUser, setCurrentUser, getPreferences, savePreferences } from "./lib/api.js"; + const KNOWN_PAGES = new Set([ + "dashboard", + "docker", + "gitea", + "files", + "terminal", + "openclaw", + "chat-digest", + "settings", + "security", + "litellm", + "cc-connect", + "info-engine", + ]); + let page = $state("dashboard"); let dark = $state(false); let sidebarOpen = $state(false); @@ -27,6 +42,26 @@ let sidebarOrder = $state(null); let orderSaveTimer = null; + function getRequestedPage() { + const requestedPage = new URLSearchParams(window.location.search).get("page"); + return KNOWN_PAGES.has(requestedPage) ? requestedPage : null; + } + + function canOpenPage(pageId) { + if (!pageId || !KNOWN_PAGES.has(pageId)) return false; + if (pageId === "settings") return userRole === "admin"; + if (["litellm", "cc-connect", "info-engine"].includes(pageId)) return hasPageAccess("dashboard"); + return hasPageAccess(pageId); + } + + function syncPageQuery(pageId) { + const url = new URL(window.location.href); + if (pageId === "dashboard") url.searchParams.delete("page"); + else url.searchParams.set("page", pageId); + if (pageId !== "terminal") url.searchParams.delete("host"); + history.replaceState({}, "", `${url.pathname}${url.search}${url.hash}`); + } + async function fetchUserInfo() { try { const data = await checkAuth(); @@ -78,6 +113,8 @@ setRefreshToken(data.refresh_token); authorized = true; await fetchUserInfo(); + const requestedPage = getRequestedPage(); + if (canOpenPage(requestedPage)) page = requestedPage; loading = false; return; } @@ -96,6 +133,8 @@ try { await fetchUserInfo(); authorized = true; + const requestedPage = getRequestedPage(); + if (canOpenPage(requestedPage)) page = requestedPage; } catch (e) { console.error("Auth check failed:", e); setToken(""); @@ -105,6 +144,11 @@ } }); + $effect(() => { + if (!authorized || loading || !canOpenPage(page)) return; + syncPageQuery(page); + }); + function toggleTheme() { dark = !dark; if (dark) {