- Auto-create {service-id}-admin groups at startup (group_bootstrap.py)
- get_service_admin() dep: grants access to superusers OR service group members
- /api/settings/ai and /api/settings/documents/limits now allow service admins
- AI service exposes /plugin/manifest (ai-service-admin access group)
- DocServiceSettingsPage: combined upload limits + watch directory on one page
- ServiceAdminRoute in frontend guards new /apps/documents/settings and /apps/ai/settings
- Single Settings button per app card (visible to admins and service group members)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
10 KiB
Frontend — Status
What it is
React 18 + TypeScript + Vite SPA styled with shadcn/ui (Radix primitives) and Tailwind CSS v3. Design tokens are CSS custom properties supporting light/dark themes. In dev it runs on port 5173 and proxies /api/* to backend:8000. In prod it is served by nginx on port 80.
All API calls go through src/api/client.ts (single Axios instance, JWT injected via request interceptor from localStorage).
Routes
| Path | Component | Auth |
|---|---|---|
/login |
LoginPage |
Public |
/ |
DashboardPage |
Required |
/apps |
AppsPage |
Required |
/apps/documents |
DocumentsPage |
Required |
/apps/documents/settings |
DocServiceSettingsPage |
ServiceAdminRoute (is_admin OR doc-service-admin) |
/apps/ai/settings |
AIAdminSettingsPage |
ServiceAdminRoute (is_admin OR ai-service-admin) |
/admin |
AdminPage (redirects to /admin/users) |
Admin only |
/admin/users |
AdminUsersPage |
Admin only |
/admin/groups |
AdminGroupsPage |
Admin only |
/profile |
ProfilePage |
Required |
/settings |
SettingsPage (placeholder) |
Required |
/settings/plugins/:id |
PluginSettingsPage |
Required (per-plugin access control) |
PrivateRoute redirects to /login when no token. AdminRoute redirects to / when not admin.
Current functionality
Auth
- Login form (
POST /api/auth/login) stores JWT inlocalStorage - Logout clears token and redirects to
/login GET /api/users/meverifies token on protected routes
Home dashboard (/)
Personalised landing page per user:
- Time-aware greeting with the user's display name (
full_nameor email). React JSX text rendering HTML-escapes all values — nodangerouslySetInnerHTMLis used anywhere on this page. - Grid of pinned app cards drawn from
GET /api/services, filtered to the user's saved list. - Customize mode (pencil button): shows all services;
+/−toggle buttons on each card; changes committed with Save viaPATCH /api/users/me/preferences. - Empty-state prompt when no apps are pinned.
Apps page (/apps)
Cards are rendered dynamically from GET /api/services (polled every 30 s via TanStack Query):
- healthy=true + app_path set — clickable card with "Available" badge
- healthy=true + no app_path — non-clickable card (e.g. AI Service — no user UI)
- healthy=false — non-clickable, dimmed card with "Unavailable" badge and explanation text
- Single Settings button per card — visible to global admins OR members of the service's admin group (checked via
GET /api/pluginswhich backend filters by access). Links tosvc.settings_path.
Sidebar navigation
Apps is an expandable accordion in the sidebar:
- Documents sub-item (expandable) — lists all user categories beneath it; clicking a category navigates to
/apps/documents?category_id=<id> - AI Service is not listed (no openable UI)
- Sections auto-open when navigating to their route
- In collapsed (icons-only) mode, clicking the Apps icon navigates to
/apps
Documents page (/apps/documents)
Upload: PDF file input, 202 response, error display.
Filter bar:
- Search input (400ms debounce) — matches title, filename, tags, document_type
- Status dropdown (all / pending / processing / done / failed)
- Type dropdown (all / invoice / bill / receipt / order / expense / revenue / unknown)
- Sort selector (upload date / processed date / title / filename / file size / type / status)
- Asc/Desc toggle
- "Clear filters" button (appears when any filter is active)
Pagination: Prev/Next with "X–Y of Z" count. Only shown when total > per_page.
Document row (collapsed):
- Inline title editor (pencil icon, Enter to save, Esc to cancel; shows filename in italic when no title)
- Status badge (colour-coded)
- Document type label
- File size
- View button (opens PDF in new tab via blob URL — auth-gated)
- Download button
- Delete button (confirm dialog)
Document row (expanded):
- Extracted data table — all AI-extracted JSON fields (excludes
tags,suggested_categories) - Error message — shown if status=failed
- Categories — assigned chips with remove; dropdown to assign existing; AI-suggested chips with Accept / Create & Assign / Dismiss
- Status polling — auto-refetches every 3s while status is pending/processing; invalidates document list on done/failed
AI Service Settings (/apps/ai/settings)
Accessible to global admins and ai-service-admin group members (ServiceAdminRoute).
- Provider selector (lmstudio / ollama / anthropic)
- Per-provider fields (base URL, model, API key)
- Test Connection button (
POST /api/settings/ai/test) - Save button
Document Service Settings (/apps/documents/settings)
Accessible to global admins and doc-service-admin group members (ServiceAdminRoute).
Combined settings on one page, accessed via the single "Settings" button on the app card:
- Upload Limits — max PDF size in MB (
GET/PATCH /api/settings/documents/limits) - Watch Directory — file watcher config rendered via
PluginSchemaFormfrom manifest (GET/PATCH /api/plugins/doc-service/settings)
Admin — Users page (/admin/users)
- User list with role and active status
- Inline active status toggle
- Create user form (email, name, password, admin flag)
- Delete user
Admin — Groups page (/admin/groups)
- Group list with name, description, member count
- Create group (name, optional description)
- Edit group name / description inline panel
- Delete group (with confirmation)
- Expand group row to manage members: view members, remove members, add non-members from dropdown
Profile page (/profile)
- Display and edit personal information
API client (src/api/client.ts)
Key functions:
| Function | Description |
|---|---|
listDocuments(params) |
GET /documents — returns DocumentPage; supports category_id filter |
uploadDocument(file) |
POST /documents/upload |
deleteDocument(id) |
DELETE /documents/{id} |
downloadDocument(id, filename) |
Blob URL download |
viewDocument(id) |
Blob URL → window.open, auto-revoke after 60s |
getDocumentStatus(id) |
Poll endpoint |
listCategories() |
All categories for user |
createCategory(name) |
Create category |
assignCategory(docId, catId) |
Assign |
removeCategory(docId, catId) |
Remove |
updateDocumentTags(id, tags) |
PATCH /documents/{id}/tags |
updateDocumentTitle(id, title) |
PATCH /documents/{id}/title |
confirmFolderSuggestion(docId) |
POST /documents/{id}/suggestions/folder/confirm |
rejectFolderSuggestion(docId) |
POST /documents/{id}/suggestions/folder/reject |
confirmFilenameSuggestion(docId) |
POST /documents/{id}/suggestions/filename/confirm |
rejectFilenameSuggestion(docId) |
POST /documents/{id}/suggestions/filename/reject |
getAISettings() |
GET /settings/ai (masked) |
updateAISettings(data) |
PATCH /settings/ai |
testAIConnection() |
POST /settings/ai/test |
getDocumentLimits() |
GET /settings/documents/limits |
adminListGroups() |
GET /admin/groups |
adminCreateGroup(data) |
POST /admin/groups |
adminGetGroup(id) |
GET /admin/groups/{id} with members |
adminUpdateGroup(id, data) |
PATCH /admin/groups/{id} |
adminDeleteGroup(id) |
DELETE /admin/groups/{id} |
adminAddGroupMember(gId, uId) |
POST /admin/groups/{gId}/members/{uId} |
adminRemoveGroupMember(gId, uId) |
DELETE /admin/groups/{gId}/members/{uId} |
updateDocumentLimits(data) |
PATCH /settings/documents/limits |
getPlugins() |
GET /plugins — list accessible plugins |
getPluginManifest(id) |
GET /plugins/{id}/manifest |
getPluginSettings(id) |
GET /plugins/{id}/settings |
updatePluginSettings(id, data) |
PATCH /plugins/{id}/settings |
State management
- TanStack Query — all server state;
queryKey: ["documents", params]for cache isolation per filter/page combination - No global store — local
useStatefor UI-only state (editing mode, filter params, etc.) - Token —
localStorage, read byuseAuthhook, injected by Axios interceptor
Component inventory
| Component | Path | Description |
|---|---|---|
AppShell |
src/components/AppShell.tsx |
Layout wrapper: Sidebar + scrollable main content |
Sidebar |
src/components/Sidebar.tsx |
Collapsible left nav; includes dynamic "Extensions" section |
ThemeToggle |
src/components/ThemeToggle.tsx |
Sun/moon ghost icon button; persists to localStorage |
PluginSchemaForm |
src/components/PluginSchemaForm.tsx |
JSON Schema → React form (boolean/string/number/readOnly fields) |
PluginSettingsPage |
src/pages/PluginSettingsPage.tsx |
Generic plugin settings page (manifest-driven) |
Button |
src/components/ui/button.tsx |
shadcn/ui Button (default, ghost, outline, destructive) |
Input |
src/components/ui/input.tsx |
shadcn/ui Input |
Known limitations / not implemented
- JWT in
localStorage— XSS risk; migrate tohttpOnlycookie when backend supports it - No toast / notification system — errors shown inline; success is silent
- No loading skeletons — "Loading…" text only
- No app permission UI per group — groups exist but permission grants are not yet implemented
- No app permission UI — all apps visible to all authenticated users
Future work
- UI component library: shadcn/ui + Tailwind CSS — installed and wired up
- AppShell + Sidebar replacing inline Nav component
- Light/dark theme context with OS preference detection
- Generic plugin infrastructure: Extensions sidebar section, PluginSchemaForm, PluginSettingsPage
- Suggestion badges in DocumentsPage for
suggested_folder/suggested_filename(confirm/reject buttons) - Toast notification system (upload success, save feedback, errors)
- Loading skeletons
POST /queue/jobsintegration — show AI processing queue status / progress per document- Advanced filter: extracted data fields (vendor, due date, amount) — needs backend support
- Groups admin UI — list, create, edit, delete, add/remove members
- App permissions UI per group (blocked on backend group_app_permissions)
- Document sharing UI (blocked on backend)
httpOnlycookie auth (requires backend change)- Bulk document operations (select multiple, bulk delete / bulk categorise)