homogeineze sidebar conf

This commit is contained in:
buenosairesam
2026-01-27 05:13:50 -03:00
parent 027f73794d
commit 2babd47835
6 changed files with 163 additions and 71 deletions

View File

@@ -11,5 +11,6 @@
}, },
"auth": { "auth": {
"enabled": false "enabled": false
} },
"veins": []
} }

View File

@@ -135,9 +135,25 @@ async def oauth_callback(
if not code: if not code:
raise HTTPException(400, "Missing authorization 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: try:
tokens = oauth_client.exchange_code_for_tokens(code) tokens = oauth_client.exchange_code_for_tokens(code)
token_storage.save_tokens(DEFAULT_USER_ID, tokens) token_storage.save_tokens(DEFAULT_USER_ID, tokens)
# Redirect back to original page if specified
if redirect_url:
return RedirectResponse(url=redirect_url)
return { return {
"status": "ok", "status": "ok",
"message": "Successfully authenticated with Google", "message": "Successfully authenticated with Google",
@@ -164,6 +180,32 @@ async def get_userinfo(
raise HTTPException(400, f"Failed to get user info: {e}") 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") @router.get("/oauth/logout")
async def logout(): async def logout():
"""Clear stored tokens.""" """Clear stored tokens."""

View File

@@ -215,56 +215,12 @@
color: #666; color: #666;
} }
</style> </style>
{% if managed %}
<link rel="stylesheet" href="/sidebar.css">
<script src="/sidebar.js"></script>
{% endif %}
</head> </head>
<body{% if managed %} class="has-sidebar"{% endif %}> <body{% if managed %} class="has-sidebar"{% endif %}>
{% if managed %}
<nav class="sidebar">
{% if managed_url %}
<a href="{{ managed_url }}" class="sidebar-item" title="{{ managed.name }}">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="18" height="18" rx="2"/>
<path d="M9 3v18M3 9h6"/>
</svg>
<span class="tooltip">{{ managed.name }}</span>
</a>
<div class="sidebar-divider"></div>
{% endif %}
<a href="/" class="sidebar-item active" title="Soleprint">
<svg viewBox="0 0 24 24" fill="currentColor">
<ellipse cx="8" cy="10" rx="3" ry="5" transform="rotate(-10 8 10)"/>
<ellipse cx="8" cy="17" rx="2.5" ry="3" transform="rotate(-10 8 17)"/>
<ellipse cx="16" cy="8" rx="3" ry="5" transform="rotate(10 16 8)"/>
<ellipse cx="16" cy="15" rx="2.5" ry="3" transform="rotate(10 16 15)"/>
</svg>
<span class="tooltip">Soleprint</span>
</a>
<div class="sidebar-divider"></div>
<div class="sidebar-icon" title="Artery">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2v8M12 10L6 18M12 10l6 8"/>
<circle cx="12" cy="10" r="2"/>
</svg>
</div>
<div class="sidebar-icon" title="Atlas">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="8"/>
<path d="M12 4v16M4 12h16"/>
</svg>
</div>
<div class="sidebar-icon" title="Station">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="4" y="6" width="16" height="12" rx="1"/>
<circle cx="9" cy="11" r="2"/>
<circle cx="15" cy="11" r="2"/>
</svg>
</div>
</nav>
{% endif %}
<header> <header>
<!-- Two shoe prints walking --> <!-- Two shoe prints walking -->

View File

@@ -553,20 +553,16 @@ def sidebar_config(request: Request):
host_no_port = host.split(":")[0] host_no_port = host.split(":")[0]
scheme = request.headers.get("x-forwarded-proto", "http") scheme = request.headers.get("x-forwarded-proto", "http")
# Soleprint tools are at /spr/* when accessed via <room>.spr.<domain> # Soleprint tools are at root (no prefix needed)
soleprint_base = "/spr" soleprint_base = ""
return { return {
"room": managed.get("name", "standalone"), "room": managed.get("name", "standalone"),
"soleprint_base": soleprint_base, "soleprint_base": soleprint_base,
"auth_enabled": auth.get("enabled", False), "auth_enabled": auth.get("enabled", False),
"tools": { "veins": config.get("veins", []),
"artery": f"{soleprint_base}/artery",
"atlas": f"{soleprint_base}/atlas",
"station": f"{soleprint_base}/station",
},
"auth": { "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", "status_url": f"{soleprint_base}/artery/google/oauth/status",
"logout_url": f"{soleprint_base}/artery/google/oauth/logout", "logout_url": f"{soleprint_base}/artery/google/oauth/logout",
}, },

View File

@@ -147,6 +147,63 @@ body.spr-sidebar-active.spr-sidebar-expanded {
flex: 1; 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 */ /* User/Auth Section */
.spr-sidebar-user { .spr-sidebar-user {
margin-top: auto; margin-top: auto;

View File

@@ -4,7 +4,8 @@
(function () { (function () {
"use strict"; "use strict";
const SPR_BASE = "/spr"; // Soleprint routes are at root (no prefix)
const SPR_BASE = "";
let config = null; let config = null;
let user = null; let user = null;
@@ -56,7 +57,7 @@
async function init() { async function init() {
try { try {
// Load config // Load config using dynamic base URL
const response = await fetch(`${SPR_BASE}/api/sidebar/config`); const response = await fetch(`${SPR_BASE}/api/sidebar/config`);
config = await response.json(); 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 = `
<div class="spr-sidebar-icon" title="${title}">
${icons[systemKey]}
</div>
`;
// Nested elements below icon - auth-gated
if (user) {
// Logged in
if (systemKey === "artery") {
// Show vein links under artery
for (const vein of veins) {
html += `
<a href="${SPR_BASE}/artery/${vein}" class="spr-sidebar-item spr-vein-item" title="${vein}">
<span class="label">${vein}</span>
<span class="tooltip">${vein}</span>
</a>
`;
}
} else {
// Atlas/Station: clickable link to main page
html += `
<a href="${SPR_BASE}/${systemKey}" class="spr-sidebar-item spr-system-link" title="${title}">
<span class="label">${title}</span>
<span class="tooltip">${title}</span>
</a>
`;
}
} else if (config.auth_enabled) {
// Not logged in: show "login to access" below each system icon
html += `
<div class="spr-sidebar-locked">
<span class="lock-text">login to access</span>
</div>
`;
}
return html;
}
function createSidebar() { function createSidebar() {
// Add body class // Add body class
document.body.classList.add("spr-sidebar-active"); document.body.classList.add("spr-sidebar-active");
@@ -122,17 +167,9 @@
<div class="spr-sidebar-divider"></div> <div class="spr-sidebar-divider"></div>
<div class="spr-sidebar-icon" title="Artery"> ${renderSystemSection("artery", "Artery")}
${icons.artery} ${renderSystemSection("atlas", "Atlas")}
</div> ${renderSystemSection("station", "Station")}
<div class="spr-sidebar-icon" title="Atlas">
${icons.atlas}
</div>
<div class="spr-sidebar-icon" title="Station">
${icons.station}
</div>
<div class="spr-sidebar-spacer"></div> <div class="spr-sidebar-spacer"></div>
@@ -167,9 +204,12 @@
</div> </div>
`; `;
} else { } else {
// Include current page as redirect target after login
const redirectUrl = encodeURIComponent(window.location.href);
const loginUrl = `${config.auth.login_url}?redirect=${redirectUrl}`;
return ` return `
<div class="spr-sidebar-user"> <div class="spr-sidebar-user">
<a href="${config.auth.login_url}" class="spr-sidebar-item" title="Login with Google"> <a href="${loginUrl}" class="spr-sidebar-item" title="Login with Google">
${icons.google} ${icons.google}
<span class="label">Login</span> <span class="label">Login</span>
<span class="tooltip">Login</span> <span class="tooltip">Login</span>