Date: 2026-03-30 Status: Approved
A browser-based admin UI for FileDB v2, comparable to phpMyAdmin for MySQL or ElasticVue for Elasticsearch. It lives at clients/web/ as a standalone npm project (React + TypeScript + Vite) that talks to the FileDB REST gateway at :8080.
| Concern | Choice | Reason |
|---|---|---|
| Framework | React 18 + TypeScript | Best ecosystem for data-heavy admin UIs |
| Build tool | Vite | Fast dev server + hot reload |
| HTTP client | Fetch API wrapping existing clients/js types | Reuse proto-generated types from clients/js |
| Component library | shadcn/ui (Radix + Tailwind) | Dark-theme ready, accessible, unstyled base |
| State | React context + useState / useEffect | No global state manager needed at this scope |
| Streaming | fetch with ReadableStream (SSE-style) for Watch | REST gateway streams Watch responses |
| Persistence | localStorage | Connection settings (URL + API key) |
clients/web/
├── index.html
├── package.json
├── vite.config.ts
├── tsconfig.json
├── src/
│ ├── main.tsx # React root
│ ├── App.tsx # App shell: top bar + sidebar + content area
│ ├── api/
│ │ └── client.ts # Thin wrapper around fetch → REST gateway
│ ├── components/
│ │ ├── Sidebar.tsx # Collection list + "New Collection" button
│ │ ├── CollectionView.tsx # Tab router: Browse / Indexes / Stats / Watch
│ │ ├── BrowseTab.tsx # Filter bar + records table + pagination
│ │ ├── IndexesTab.tsx # List indexes, ensure/drop
│ │ ├── StatsTab.tsx # 4 stat cards, auto-refresh
│ │ ├── WatchTab.tsx # Live event log
│ │ ├── RecordModal.tsx # Insert / edit modal (JSON ↔ form toggle)
│ │ ├── FilterBar.tsx # Field / op / value rows + AND/OR
│ │ ├── SettingsPanel.tsx # URL + API key form
│ │ └── Toast.tsx # Success / error / info toasts
│ └── hooks/
│ ├── useCollections.ts # Fetch + refresh collection list
│ ├── useRecords.ts # Find with filter/order/limit/offset
│ └── useWatch.ts # ReadableStream-based Watch subscription
┌─────────────────────────────────────────────────────────┐
│ ⬡ FileDB │ localhost:8080 ● connected ⚙ │ ← Top bar
├──────────────┬──────────────────────────────────────────┤
│ COLLECTIONS │ Browse │ Indexes │ Stats │ ⚡ Watch │ ← Tab bar
│ ├──────────────────────────────────────────┤
│ ● users 142 │ │
│ products 38│ Tab content area │
│ orders 901 │ │
│ │ │
│ + New │ │
└──────────────┴──────────────────────────────────────────┘
localStorage), show a centered Connect form: Server URL + API KeylocalStorage on submit; loaded on mountx-api-key header● connected or red ● disconnectedPOST /v1/{collection}/records/find with composed Filter objectID, created_at, modified_at, actions{…} — click to expand inlinePOST /v1/{collection}/records (insert) or PUT /v1/{collection}/records/{id} (update)GET /v1/{collection}/indexesDELETE /v1/{collection}/indexes/{field})POST /v1/{collection}/indexesFour stat cards from GET /v1/{collection}/stats:
| Card | Field | Notes |
|---|---|---|
| Records | record_count | Plain number |
| Segments | segment_count | Plain number |
| Dirty Entries | dirty_entries | Number + percentage of total + progress bar (amber when >20%) |
| Size on Disk | size_bytes | Human-readable (KB / MB / GB) |
POST /v1/{collection}/watch via ReadableStream (the REST gateway streams Watch RPC responses as newline-delimited JSON)● watching animated badge while connectedPOST /v1/collections, then refreshes sidebarDELETE /v1/collections/{name}, then navigates to next collection in listsrc/api/client.ts)Thin wrapper over fetch:
class FileDBClient {
constructor(baseUrl: string, apiKey: string)
// Collections
listCollections(): Promise<string[]>
createCollection(name: string): Promise<void>
dropCollection(name: string): Promise<void>
// Records
insert(collection: string, data: object): Promise<{ id: number }>
findById(collection: string, id: number): Promise<Record>
find(collection: string, req: FindRequest): Promise<Record[]>
update(collection: string, id: number, data: object): Promise<void>
delete(collection: string, id: number): Promise<void>
// Indexes
listIndexes(collection: string): Promise<string[]>
ensureIndex(collection: string, field: string): Promise<void>
dropIndex(collection: string, field: string): Promise<void>
// Stats
collectionStats(collection: string): Promise<CollectionStats>
// Watch (streaming)
watch(collection: string, onEvent: (e: WatchEvent) => void): () => void // returns unsubscribe fn
}
The REST gateway at :8080 needs Access-Control-Allow-Origin: * (or the UI's origin) for browser requests. This requires adding a CORS middleware to server/rest.go. The UI will document this as a prerequisite in its README.
● disconnected, toast showncd clients/web
npm install
npm run dev # starts on http://localhost:5173, proxies /v1 → localhost:8080
npm run build # produces dist/ for deployment
Vite dev server proxies /v1 to avoid CORS issues in development. Production build requires CORS to be enabled on the server.
Per project conventions, the following must be updated when this feature lands:
docs/getting-started.md — add "Web UI" section with setup instructionsdocs/architecture.md — mention the web client and CORS requirementREADME.md — add web UI to key properties listROADMAP.md — mark web UI item as doneCan you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |