diff --git a/cfg/sample/config.json b/cfg/sample/config.json
index 49a1cca..bd11c42 100644
--- a/cfg/sample/config.json
+++ b/cfg/sample/config.json
@@ -11,5 +11,6 @@
},
"auth": {
"enabled": false
- }
+ },
+ "veins": []
}
diff --git a/soleprint/artery/veins/google/api/routes.py b/soleprint/artery/veins/google/api/routes.py
index 4439669..f54446f 100644
--- a/soleprint/artery/veins/google/api/routes.py
+++ b/soleprint/artery/veins/google/api/routes.py
@@ -135,9 +135,25 @@ async def oauth_callback(
if not code:
raise HTTPException(400, "Missing authorization code")
+ # Extract redirect URL from state if present
+ # Format: "csrf_state|redirect_url" OR just "redirect_url" if no csrf state
+ redirect_url = None
+ if state:
+ if "|" in state:
+ parts = state.split("|", 1)
+ redirect_url = parts[1] if len(parts) > 1 else None
+ elif state.startswith("http"):
+ # No csrf state, just the redirect URL
+ redirect_url = state
+
try:
tokens = oauth_client.exchange_code_for_tokens(code)
token_storage.save_tokens(DEFAULT_USER_ID, tokens)
+
+ # Redirect back to original page if specified
+ if redirect_url:
+ return RedirectResponse(url=redirect_url)
+
return {
"status": "ok",
"message": "Successfully authenticated with Google",
@@ -164,6 +180,32 @@ async def get_userinfo(
raise HTTPException(400, f"Failed to get user info: {e}")
+@router.get("/oauth/status")
+async def oauth_status():
+ """Check if user is authenticated (for sidebar)."""
+ try:
+ tokens = token_storage.load_tokens(DEFAULT_USER_ID)
+ if not tokens:
+ return {"authenticated": False}
+
+ # Check if expired
+ if token_storage.is_expired(tokens):
+ if "refresh_token" not in tokens:
+ return {"authenticated": False}
+ try:
+ new_tokens = oauth_client.refresh_access_token(tokens["refresh_token"])
+ token_storage.save_tokens(DEFAULT_USER_ID, new_tokens)
+ except Exception:
+ return {"authenticated": False}
+
+ return {
+ "authenticated": True,
+ "user": {"email": f"{DEFAULT_USER_ID}@authenticated"},
+ }
+ except Exception:
+ return {"authenticated": False}
+
+
@router.get("/oauth/logout")
async def logout():
"""Clear stored tokens."""
diff --git a/soleprint/index.html b/soleprint/index.html
index 1ec3f92..1fffd99 100644
--- a/soleprint/index.html
+++ b/soleprint/index.html
@@ -215,56 +215,12 @@
color: #666;
}
+ {% if managed %}
+
+
+ {% endif %}
- {% if managed %}
-
- {% endif %}
diff --git a/soleprint/run.py b/soleprint/run.py
index 1d26421..27f2b91 100644
--- a/soleprint/run.py
+++ b/soleprint/run.py
@@ -553,20 +553,16 @@ def sidebar_config(request: Request):
host_no_port = host.split(":")[0]
scheme = request.headers.get("x-forwarded-proto", "http")
- # Soleprint tools are at /spr/* when accessed via .spr.
- soleprint_base = "/spr"
+ # Soleprint tools are at root (no prefix needed)
+ soleprint_base = ""
return {
"room": managed.get("name", "standalone"),
"soleprint_base": soleprint_base,
"auth_enabled": auth.get("enabled", False),
- "tools": {
- "artery": f"{soleprint_base}/artery",
- "atlas": f"{soleprint_base}/atlas",
- "station": f"{soleprint_base}/station",
- },
+ "veins": config.get("veins", []),
"auth": {
- "login_url": f"{soleprint_base}/artery/google/oauth/login",
+ "login_url": f"{soleprint_base}/artery/google/oauth/start",
"status_url": f"{soleprint_base}/artery/google/oauth/status",
"logout_url": f"{soleprint_base}/artery/google/oauth/logout",
},
diff --git a/soleprint/station/tools/sbwrapper/sidebar.css b/soleprint/station/tools/sbwrapper/sidebar.css
index af0dfa1..87f6469 100755
--- a/soleprint/station/tools/sbwrapper/sidebar.css
+++ b/soleprint/station/tools/sbwrapper/sidebar.css
@@ -147,6 +147,63 @@ body.spr-sidebar-active.spr-sidebar-expanded {
flex: 1;
}
+/* Vein items under artery icon */
+.spr-vein-item {
+ width: 36px;
+ height: 28px;
+ font-size: 0.7rem;
+ margin-left: 8px;
+ background: #252525;
+}
+
+.spr-vein-item .label {
+ display: block !important;
+ font-size: 0.65rem;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+#spr-sidebar.expanded .spr-vein-item {
+ margin-left: 20px;
+ width: calc(100% - 20px);
+}
+
+/* System links (atlas, station when logged in) */
+.spr-system-link {
+ height: 28px;
+ font-size: 0.75rem;
+}
+
+/* Locked state (not logged in) */
+.spr-sidebar-locked {
+ width: 44px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #555;
+ font-size: 0.5rem;
+ margin-bottom: 0.5rem;
+ text-align: center;
+}
+
+.spr-sidebar-locked .lock-text {
+ writing-mode: vertical-rl;
+ text-orientation: mixed;
+ transform: rotate(180deg);
+ white-space: nowrap;
+}
+
+#spr-sidebar.expanded .spr-sidebar-locked {
+ width: 100%;
+ justify-content: flex-start;
+ margin-left: 20px;
+}
+
+#spr-sidebar.expanded .spr-sidebar-locked .lock-text {
+ writing-mode: horizontal-tb;
+ transform: none;
+}
+
/* User/Auth Section */
.spr-sidebar-user {
margin-top: auto;
diff --git a/soleprint/station/tools/sbwrapper/sidebar.js b/soleprint/station/tools/sbwrapper/sidebar.js
index 8bb7eb3..94482ba 100755
--- a/soleprint/station/tools/sbwrapper/sidebar.js
+++ b/soleprint/station/tools/sbwrapper/sidebar.js
@@ -4,7 +4,8 @@
(function () {
"use strict";
- const SPR_BASE = "/spr";
+ // Soleprint routes are at root (no prefix)
+ const SPR_BASE = "";
let config = null;
let user = null;
@@ -56,7 +57,7 @@
async function init() {
try {
- // Load config
+ // Load config using dynamic base URL
const response = await fetch(`${SPR_BASE}/api/sidebar/config`);
config = await response.json();
@@ -88,6 +89,50 @@
}
}
+ function renderSystemSection(systemKey, title) {
+ const veins = config.veins || [];
+
+ // System icon - ALWAYS shown as non-clickable separator
+ let html = `
+
+ `;
+
+ // Nested elements below icon - auth-gated
+ if (user) {
+ // Logged in
+ if (systemKey === "artery") {
+ // Show vein links under artery
+ for (const vein of veins) {
+ html += `
+
+ `;
+ }
+ } else {
+ // Atlas/Station: clickable link to main page
+ html += `
+
+ `;
+ }
+ } else if (config.auth_enabled) {
+ // Not logged in: show "login to access" below each system icon
+ html += `
+
+ `;
+ }
+
+ return html;
+ }
+
function createSidebar() {
// Add body class
document.body.classList.add("spr-sidebar-active");
@@ -122,17 +167,9 @@
-
-
-
-
-
+ ${renderSystemSection("artery", "Artery")}
+ ${renderSystemSection("atlas", "Atlas")}
+ ${renderSystemSection("station", "Station")}
@@ -167,9 +204,12 @@
`;
} else {
+ // Include current page as redirect target after login
+ const redirectUrl = encodeURIComponent(window.location.href);
+ const loginUrl = `${config.auth.login_url}?redirect=${redirectUrl}`;
return `