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"}
|
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):
|
async def dispatch(self, request: Request, call_next):
|
||||||
# Webhook receiver, OAuth callback, and internal API are exempt
|
# 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", ""))
|
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
|
||||||
ws_name = user.get("login", "Bruno") if user else "Bruno"
|
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
|
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 = []
|
recent = []
|
||||||
if owner and repo:
|
if owner and repo:
|
||||||
view_map = {
|
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",
|
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",
|
"current_page": repo or "Dashboard", "last_edited": "now",
|
||||||
"recent_pages": recent, "private_pages": private_items,
|
"recent_pages": recent, "private_pages": private_items,
|
||||||
"favorite_pages": favorites, "shared_pages": shared_pages,
|
"favorite_pages": favorites, "shared_pages": shared_pages,
|
||||||
|
|||||||
@@ -488,7 +488,7 @@ async def local_workspace_page(request: Request):
|
|||||||
).fetchall()
|
).fetchall()
|
||||||
items = [{"id": r["id"], "name": r["title"] or "Untitled", "type": "page"} for r in rows]
|
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")
|
template = env.get_template("local_workspace.html")
|
||||||
return template.render(**ctx)
|
return template.render(**ctx)
|
||||||
|
|
||||||
@@ -514,14 +514,17 @@ async def local_workspace_tree(request: Request):
|
|||||||
|
|
||||||
@router.post("/api/local-workspace/items")
|
@router.post("/api/local-workspace/items")
|
||||||
async def create_local_workspace_item(request: Request):
|
async def create_local_workspace_item(request: Request):
|
||||||
"""Create a new file."""
|
"""Create a new file in the active workspace."""
|
||||||
import json
|
import json
|
||||||
body = await request.json()
|
body = await request.json()
|
||||||
name = body.get("name", "Untitled").strip()
|
name = body.get("name", "Untitled").strip()
|
||||||
|
ws = _get_active_workspace(request)
|
||||||
|
ws_id = ws["id"] if ws else None
|
||||||
with get_conn() as conn:
|
with get_conn() as conn:
|
||||||
cursor = conn.execute(
|
cursor = conn.execute(
|
||||||
"INSERT INTO pages (workspace, title, content, content_format, parent_section) VALUES ('', ?, '', 'blocks', 'Private')",
|
"INSERT INTO pages (workspace, workspace_id, title, content, content_format, parent_section) "
|
||||||
(name,),
|
"VALUES ('', ?, ?, '', 'blocks', 'Private')",
|
||||||
|
(ws_id, name),
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
pid = cursor.lastrowid
|
pid = cursor.lastrowid
|
||||||
|
|||||||
+14
-2
@@ -62,7 +62,7 @@
|
|||||||
|
|
||||||
<!-- Navigation icons row -->
|
<!-- Navigation icons row -->
|
||||||
<div class="sidebar-nav-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>
|
<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
|
Home
|
||||||
</a>
|
</a>
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
<div class="sidebar-section-header" @click="toggleSection('workspace')">
|
<div class="sidebar-section-header" @click="toggleSection('workspace')">
|
||||||
<div class="sidebar-section-title">
|
<div class="sidebar-section-title">
|
||||||
<span class="chevron" :class="{ open: sectionsOpen.workspace }" x-text="sectionsOpen.workspace ? '▼' : '▶'"></span>
|
<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>
|
||||||
<div class="sidebar-section-actions">
|
<div class="sidebar-section-actions">
|
||||||
<button class="section-action-btn" title="New Page" @click.stop="newPageInWorkspace()">+</button>
|
<button class="section-action-btn" title="New Page" @click.stop="newPageInWorkspace()">+</button>
|
||||||
@@ -625,6 +625,18 @@
|
|||||||
.then(data => { window.location.href = `/pages/${data.id}`; })
|
.then(data => { window.location.href = `/pages/${data.id}`; })
|
||||||
.catch(err => { alert('Failed to create sub-page: ' + err.message); });
|
.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() {
|
createEmptyPage() {
|
||||||
this.showNewPageMenu = false;
|
this.showNewPageMenu = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user