- _header.html: remplace click par hover (mouseenter/mouseleave)
avec timer 200ms anti-flicker, cascade sous-menus au hover
- dashboard.py: ajoute nav_workspace_id à trash, library, workspace,
gitea-workspace, local-workspace, workspaces, settings, pages/{id}
- La section du popover affiche 'Workspaces' pour Home, 'Private' pour le reste
- Les clics dans les popovers ferment le menu + naviguent
Backend:
- Added _nav_breadcrumb() to build page hierarchy chain (root → current)
- New /api/nav/menu endpoint returns workspace/folder children with has_children flag
- view_page() and view_page_root() now pass breadcrumb_items, nav_workspace_id, nav_page_id to template
Header template (_header.html):
- Replaced static breadcrumb with Alpine.js fdBreadcrumb() component
- Breadcrumb items now clickable with dropdown menus showing
Config:
- Fixed SQLite path parsing on Windows (handle drive letters without prepending /)
- Changed default theme from dark to light
- Updated OAuth test credentials (gitea_oauth_client_id/secret)
Auth:
- First real user (excluding default admin with no password) becomes admin
- Changed user count query to exclude users without password_hash
File viewer:
- Removed separate _render_file_viewer() —
Backend:
- _sidebar_data() no longer clears workspace_pages for Gitea workspaces
The local tree stays intact in the 'Private' section
- workspace_pages is always loaded from DB (local files)
Sidebar template (base.html):
- Workspace section renamed to 'Private' when gitea_workspace is true
- New 'Repository (Gitea)' section added below, visible only for Gitea
workspaces, with its own toggle (sectionsOpen.gitea) and refresh button
- Uses #sidebar-gitea-items container for the remote file tree
Gitea workspace:
- loadSidebarTree() now targets #sidebar-gitea-items
- window._gwData exposed for sidebar refresh button
- sectionsOpen now includes gitea: true by default
Move to workspace:
- movePage() opens a dialog listing all workspaces via /api/workspaces
- doMove(wsId) calls PUT /api/pages/{id}/move with workspace_id
- Updated move API to accept JSON body with workspace_id for cross-workspace moves
Gitea workspace context menu:
- Right-click on any file/folder shows Rename + Delete options
- gwRename() prompts for new name, calls PUT /api/gitea/.../file
- gwDelete() confirms then calls DELETE /api/gitea/.../file
- Both trigger refreshTree() after success
Activity popover:
- 'Edited X ago' timestamp is now clickable (▾ indicator)
- Opens popover showing edited time + created date
- formatDate() helper for readable date formatting
Backend:
- DELETE /api/local-workspace/items/{id} now soft-deletes (sets deleted_at
instead of hard DELETE) — items go to trash, not permanently gone
- New POST /api/local-workspace/items/{id}/restore to undo a delete
- _load_workspace_pages() and _load_children() now filter deleted_at IS NULL
so soft-deleted items disappear from sidebar and tree queries
Frontend:
- undoDelete() now calls the restore API per item (single or bulk)
- bulkDelete() now shows the same undo banner as context menu delete
- All delete paths (ctxMenu, bulk, modal) use the same soft-delete + undo flow
- Extracted render_workspace_tree macro into _workspace_tree_macro.html
so it can be imported independently from base.html (which has many
template variables like user, workspace_name, etc.)
- Fixed GET /api/sidebar/workspace-tree to use the extracted macro
- Added error logging for easier debugging
- New endpoint GET /api/sidebar/workspace-tree returns sidebar tree HTML fragment
- refreshSidebarTree() in appState() fetches and replaces #sidebar-workspace-items
- Custom event 'flowdeck:sidebar-refresh' dispatched after doCreate/doRename/doDelete
- newFolderInWorkspace, deleteWorkspacePage, wsCtxAction('rename') now use
refreshSidebarTree() instead of window.location.reload()
- Sidebar stays in sync without full page refresh
_require_gitea() ne doit plus utiliser le token admin global comme fallback
quand l'utilisateur n'a pas lié son compte Gitea. Chaque utilisateur doit
connecter son propre compte Gitea pour voir les projets.
Les endpoints /api/gitea/projects et /api/gitea/orgs retournent maintenant
401 si l'utilisateur n'a pas lié Gitea, et la page /workspaces affiche
'Connect your Gitea account' au lieu de lister les repos du admin.
_sidebar_data() prend un nouveau parametre include_workspace (default True).
La page /workspaces passe include_workspace=False pour que la sidebar
n'affiche ni le nom du workspace actif, ni ses pages/folders.
A local account without Gitea connection was seeing all Gitea repos
because the endpoint used the module-level gitea client (global admin token).
Now it checks get_user_gitea_client(request) and returns empty gitea_repos
if the user has no OAuth token for Gitea.
ROOT CAUSE: Local accounts ([email protected]) had no Gitea token linked,
so /api/gitea/.../tree returned 401 → loadGiteaTree() failed silently.
FIXES:
1. _require_gitea() now falls back to admin token for read ops
→ Any logged-in user can browse Gitea repos without linking account
2. get_user_gitea_client() filters by provider='gitea'
→ Prevents using wrong token if user has GitHub+Gitrea linked
3. OAuth callback now stores auth_method correctly
→ gitea_bruno gets auth_method='gitea' instead of 'local'
4. Linked Gitea token to [email protected] (user_id=127)
→ Local account can now use personal token for Gitea API
PREVIOUS FIXES (from prior commit):
- loadGiteaTree: skip if giteaWorkspace already loaded, error logging, .bind(this)
- gitea_workspace.html: use captured 'self' instead of querySelector('[x-data]')
- 12 test assertions updated to reflect admin fallback behavior
Root cause: ws dict from _get_active_workspace doesn't have 'workspace_key' column
→ ws['workspace_key'] → KeyError → 500 on New Page/New Folder
Fix: use ws['name'] instead (the workspace name from workspaces table)
- Local login JS: window.location='/workspaces' instead of '/'
- Root route /: if no workspace exists, redirect to /workspaces
- Existing users with workspaces: still go to /local-workspace
Root cause: _sidebar_data reads cookie from REQUEST, but cookie is set on RESPONSE
→ first visit: cookie empty → gitea_workspace=False → tree doesn't load
Fix: gitea_workspace_page ctx always sets gitea_workspace=True
Also passes gitea_owner/gitea_repo for Alpine tree loading
Root cause: request.session cookie expires during Gitea OAuth redirect
→ oauth_mode lost → link mode falls through to login mode
→ Creates gitea_bruno user instead of linking to local account
Fix: state now carries mode suffix (state:mode)
Callback recovers mode from state parameter even if session lost
Allows full session loss but still correctly enters link mode
Before: oauth_mode stored only in request.session cookie
→ Lost if session expires during Gitea OAuth redirect
→ Link mode falls through to login mode → creates gitea_bruno user
After: state format: <random>:<mode> (e.g., abc123:link)
→ Mode survives session cookie loss
→ Link mode correctly links to current local user
- Stale numeric workspace cookie from another user now rejected
- workspace_pages only loaded if owner_id matches current user
- Prevents sidebar tree leak of other users' content
- _get_active_workspace now verifies workspace owner matches current user
- Fallback: only picks workspace owned by current user (not any user)
- /local-workspace redirects to /workspaces when no workspace exists
- New users no longer see other users' workspaces/projects
- private_pages now queried from DB when gitea_workspace=True
- Pages with parent_section='Private' and workspace_id=mirror_ws_id
- Shown in sidebar Private section alongside remote 🔗 tree
- Opening /gitea-workspace auto-creates a local workspace with same name
- New Page/New Folder in remote context → saves to local mirror workspace
- Sidebar stays focused on remote workspace (🔗 icon, remote tree)
- local_ws_id passed to Alpine for API calls
- /api/local-workspace/items accepts workspace_id in body
Bug 1: newPageInWorkspace() allait toujours vers local-workspace API
→ Maintenant détecte workspaceKey contient '/' → Gitea API PUT file
Bug 2: workspace_name dans gitea_workspace ne persistait pas
→ Cookie flowdeck_workspace posé côté serveur au premier chargement
Bug 3: icône 📁 fixe dans sidebar — pas d'indication remote
→ 🔗 affiché quand workspace_key contient '/' (remote), 📁 sinon
newFolderInWorkspace() : avertissement si workspace distant
(car les dossiers n'existent pas dans un repo Git)
- Le titre de page (onglet) : 'bruno/flowdeck' au lieu de 'Gitea Workspace'
- Le topbar breadcrumb : owner/repo correctement peuplé
- workspace_name + workspace_initial passés au template
- POST /api/gitea/projects/{owner}/{repo}/upload (binary upload)
- get_file_commits() in GiteaClient
- GET /api/gitea/projects/{owner}/{repo}/commits (per-file history)
- POST .../sync-labels (Gitea labels → FlowDeck tags)
- New file form + upload button in gitea_workspace.html topbar
Ajout d'un paramètre _force avec timestamp dans l'URL d'autorisation
quand mode=link, pour forcer Gitea à ne pas utiliser le cache.
NOTE: Gitea ne supporte pas prompt=login. Si auto-approve persiste,
il faudra ajouter un champ token manuel en fallback.
1. _require_gitea: retrait fallback admin token → 401 si pas connecté
2. user-menu-dropdown: background var(--bg-primary) au lieu de #1E1E1E
3. applyTheme() déjà immédiat via style.setProperty sur documentElement
1. _require_gitea: fallback token admin pour les GET (orgs, projects, tree, file)
_require_user_gitea: token OAuth requis pour les write (save, delete)
2. Private Pages: table gitea_private_pages (user_id, owner, repo, title, content)
API CRUD: GET/POST/PUT/DELETE /api/gitea/projects/{o}/{r}/private-pages
UI: liste + éditeur dans le workspace Gitea, lien 🔒 Private Pages dans sidebar
Lié logiquement au projet (owner+repo), conservé entre sessions
Avant: _sidebar_data() utilisait asyncio.run() → RuntimeWarning + 302 redirects
Après: gitea_workspace.html appelle loadSidebarTree() → fetch API → injecte dans #sidebar-workspace-items
- base.html: ajout id='sidebar-workspace-items' sur le <ul> workspace
- dashboard.py: _sidebar_data() ne fait plus de fetch serveur, juste passe owner/repo
1. Tab user repos: fetch username via /auth/user → affiche 'bruno' au lieu de 'Personal'
2. Sidebar: ws_cookie='gitea:owner:repo' → _sidebar_data() fetch le tree depuis Gitea API
3. base.html: data-gitea-path/type/sha sur les items workspace pour les projets Gitea
4. gitea_workspace.html: refonte sans tree panel → navigation via sidebar
- Clic fichier → ouvre dans le contenu (lecture + edit + commit)
- Clic dossier → lazy-load les enfants dans le sidebar
5. openGitea(): set cookie flowdeck_workspace avant navigation
Avant: oauth_redirect_uri codé en dur à localhost:8080
→ impossible d'utiliser l'OAuth depuis une autre machine du réseau
Après: le redirect_uri est construit à partir du header Host de la requête
→ localhost:8080 → http://localhost:8080/auth/callback
→ 192.168.30.101:8080 → http://192.168.30.101:8080/auth/callback⚠️ L'utilisateur doit ajouter les 2 URIs dans Gitea OAuth App settings.
Avant: get_provider('gitea') → None (pas de client OAuth configuré)
→ fallback créait automatiquement une session admin
→ le bouton 'Register with Gitea' connectait l'utilisateur en admin !
Après: affiche une page d'erreur propre:
'⚠ Gitea OAuth not configured — The Gitea integration has not
been set up by the server administrator. ↩ Use local login'
1. Pas de Gitea sans config: _require_gitea() → 401 si pas de token OAuth
2. Register Gitea: boutons "Login/Register with Gitea" dynamiques selon l'onglet
3. Settings → Integrations: ✅ Active / 🔗 Connect / Disconnect + API status/disconnect
4. Workspaces: tabs par org (All | org1 | org2) + barre recherche 🔍 filtrage live
5. API: GET /api/gitea/status, DELETE /api/gitea/disconnect