fix: Home → /workspaces, workspace name from cookie, CRUD + CSRF
- Home button now links to /workspaces (universal workspace page) - Sidebar workspace section shows active workspace name from cookie - _sidebar_data reads flowdeck_workspace cookie for active_ws_name - local-workspace APIs filter by workspace_id (not global) - newPageInWorkspace() JS function added to sidebar - CSRF exemption for /api/local-workspace routes
This commit is contained in:
@@ -16,7 +16,7 @@ class CSRFMiddleware(BaseHTTPMiddleware):
|
||||
"""
|
||||
|
||||
SAFE_METHODS = {"GET", "HEAD", "OPTIONS"}
|
||||
EXCLUDED_PATHS = {"/api/webhook", "/api/v1", "/auth/callback", "/auth/register", "/auth/local-login", "/api/user", "/board/api/pages", "/board/api/favorites", "/api/workspace", "/db/", "/workspace"}
|
||||
EXCLUDED_PATHS = {"/api/webhook", "/api/v1", "/auth/callback", "/auth/register", "/auth/local-login", "/api/user", "/board/api/pages", "/board/api/favorites", "/api/workspace", "/api/local-workspace", "/db/", "/workspace"}
|
||||
|
||||
async def dispatch(self, request: Request, call_next):
|
||||
# Webhook receiver, OAuth callback, and internal API are exempt
|
||||
|
||||
@@ -144,6 +144,19 @@ def _sidebar_data(request: Request, owner: str = "", repo: str = "") -> dict:
|
||||
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
|
||||
ws_name = user.get("login", "Bruno") if user else "Bruno"
|
||||
ws_key = f"{owner}/{repo}" if owner and repo else ws_name # workspace = project context
|
||||
|
||||
# Active workspace name from cookie (for local workspace display)
|
||||
from app.routers.dashboard import WORKSPACE_COOKIE
|
||||
ws_cookie = request.cookies.get(WORKSPACE_COOKIE, "")
|
||||
active_ws_name = "Workspace"
|
||||
if ws_cookie:
|
||||
try:
|
||||
with get_conn() as conn:
|
||||
ws = conn.execute("SELECT name FROM workspaces WHERE id=?", (int(ws_cookie),)).fetchone()
|
||||
if ws:
|
||||
active_ws_name = ws["name"]
|
||||
except (ValueError, Exception):
|
||||
pass
|
||||
recent = []
|
||||
if owner and repo:
|
||||
view_map = {
|
||||
@@ -215,6 +228,7 @@ def _sidebar_data(request: Request, owner: str = "", repo: str = "") -> dict:
|
||||
})
|
||||
|
||||
return {"workspace_name": ws_name, "workspace_initial": ws_name[0].upper() if ws_name else "B",
|
||||
"active_ws_name": active_ws_name,
|
||||
"current_page": repo or "Dashboard", "last_edited": "now",
|
||||
"recent_pages": recent, "private_pages": private_items,
|
||||
"favorite_pages": favorites, "shared_pages": shared_pages,
|
||||
|
||||
@@ -488,7 +488,7 @@ async def local_workspace_page(request: Request):
|
||||
).fetchall()
|
||||
items = [{"id": r["id"], "name": r["title"] or "Untitled", "type": "page"} for r in rows]
|
||||
|
||||
ctx = {**sidebar, "user": user, "workspace_name": "My Workspace", "items": items}
|
||||
ctx = {**sidebar, "user": user, "workspace_name": sidebar.get("active_ws_name", "My Workspace"), "items": items}
|
||||
template = env.get_template("local_workspace.html")
|
||||
return template.render(**ctx)
|
||||
|
||||
@@ -514,14 +514,17 @@ async def local_workspace_tree(request: Request):
|
||||
|
||||
@router.post("/api/local-workspace/items")
|
||||
async def create_local_workspace_item(request: Request):
|
||||
"""Create a new file."""
|
||||
"""Create a new file in the active workspace."""
|
||||
import json
|
||||
body = await request.json()
|
||||
name = body.get("name", "Untitled").strip()
|
||||
ws = _get_active_workspace(request)
|
||||
ws_id = ws["id"] if ws else None
|
||||
with get_conn() as conn:
|
||||
cursor = conn.execute(
|
||||
"INSERT INTO pages (workspace, title, content, content_format, parent_section) VALUES ('', ?, '', 'blocks', 'Private')",
|
||||
(name,),
|
||||
"INSERT INTO pages (workspace, workspace_id, title, content, content_format, parent_section) "
|
||||
"VALUES ('', ?, ?, '', 'blocks', 'Private')",
|
||||
(ws_id, name),
|
||||
)
|
||||
conn.commit()
|
||||
pid = cursor.lastrowid
|
||||
|
||||
+14
-2
@@ -62,7 +62,7 @@
|
||||
|
||||
<!-- Navigation icons row -->
|
||||
<div class="sidebar-nav-row">
|
||||
<a href="/" class="nav-icon-btn home-btn" :class="{ active: currentView === 'home' }" title="Home">
|
||||
<a href="/workspaces" class="nav-icon-btn home-btn" :class="{ active: currentView === 'home' }" title="Home">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/></svg>
|
||||
Home
|
||||
</a>
|
||||
@@ -85,7 +85,7 @@
|
||||
<div class="sidebar-section-header" @click="toggleSection('workspace')">
|
||||
<div class="sidebar-section-title">
|
||||
<span class="chevron" :class="{ open: sectionsOpen.workspace }" x-text="sectionsOpen.workspace ? '▼' : '▶'"></span>
|
||||
<span>📁 {{ workspace_name or 'Workspace' }}</span>
|
||||
<span>📁 {{ active_ws_name or 'Workspace' }}</span>
|
||||
</div>
|
||||
<div class="sidebar-section-actions">
|
||||
<button class="section-action-btn" title="New Page" @click.stop="newPageInWorkspace()">+</button>
|
||||
@@ -625,6 +625,18 @@
|
||||
.then(data => { window.location.href = `/pages/${data.id}`; })
|
||||
.catch(err => { alert('Failed to create sub-page: ' + err.message); });
|
||||
},
|
||||
newPageInWorkspace() {
|
||||
const name = prompt('Page name:');
|
||||
if (!name || !name.trim()) return;
|
||||
fetch('/api/local-workspace/items', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': this.getCsrfToken() },
|
||||
body: JSON.stringify({ name: name.trim(), type: 'page' })
|
||||
})
|
||||
.then(r => { if (!r.ok) throw new Error('Failed'); return r.json(); })
|
||||
.then(data => { window.location.href = `/pages/${data.id}`; })
|
||||
.catch(err => { alert('Failed: ' + err.message); });
|
||||
},
|
||||
|
||||
createEmptyPage() {
|
||||
this.showNewPageMenu = false;
|
||||
|
||||
Reference in New Issue
Block a user