Use the web management interface¶
ZettelForge ships a browser-based management interface served at http://localhost:8088. It covers system health monitoring, threat intelligence search and synthesis, knowledge graph exploration, log streaming, bulk data ingestion, entity browsing, session history, and live configuration editing.
Prerequisites
- ZettelForge installed:
pip install zettelforge - Web dependencies installed:
pip install zettelforge[web] - At least one note stored (see Quickstart)
Step 1: Start the server¶
python web/app.py
Expected output:
ZettelForge v2.7.0 — http://127.0.0.1:8088
Open http://localhost:8088 in your browser.
Custom port¶
python web/app.py --port 9000
Disable the web UI¶
Set ZETTELFORGE_WEB_ENABLED=false in your environment, or add the following to your config.yaml:
web:
enabled: false
Step 2: Navigate the interface¶
The interface has three zones: a header (top bar with brand mark and operational stats), a sidebar (navigation links), and a content area.
Click any sidebar item to navigate. The active item has a neon green left border.
| Sidebar item | Icon | Description |
|---|---|---|
| Dashboard | layout-dashboard | System health tiles, telemetry summary, quick actions |
| Search | search | Threat intelligence recall, synthesis, and storage |
| Knowledge Graph | git-graph | 3D orbital entity graph with search and filters |
| Logs & Telemetry | scroll-text | Log file tailing, telemetry charts, live streaming |
| Ingest | upload | Manual and bulk data ingestion |
| Entities | database | Paginated entity browser with type/tier/text filters |
| History | clock | Recent session activity with date filters |
| Configuration | settings | Feature flag toggles and raw YAML editor |
Step 3: Explore each view¶
Dashboard¶
The dashboard shows system health at a glance. Four status tiles display:
- Storage — backend type (sqlite), total notes, total entities, data directory size
- LLM — provider (ollama/local/litellm), model name, local backend engine, uptime
- Embedding — provider (fastembed/ollama), model name, vector dimensions
- Queue — enrichment queue depth and operational status
Below the tiles is a telemetry summary showing today's query volume, synthesis count, and average latency. A bar chart breaks down query intents (factual, temporal, relational, causal, exploratory). Quick action buttons let you run LanceDB compaction or force a config reload.
Search¶
Three tabs:
- Recall — Enter a query (for example, "What tools does APT28 use?"). Results appear as cards showing note ID, tier pill (A/B/C color-coded), domain, content, entity tags, confidence, and timestamp.
- Synthesize — Same query input with a format selector (
direct_answer,synthesized_brief,timeline_analysis,relationship_map). Note:synthesized_brief,timeline_analysis, andrelationship_mapare gated to the ThreatRecall.ai SaaS; the OSS build falls back todirect_answerautomatically. See Memory Manager API. - Remember — Textarea for storing new CTI content. After submission, shows note ID, status (ADD/UPDATE/DELETE/NOOP), latency, and extracted entities.
Knowledge Graph¶
The knowledge graph view renders entity nodes and relationship edges as a 3D orbital canvas. Drag to rotate, scroll to zoom, click any node for a detail panel.
Node colors are assigned by entity type:
| Entity type | Color | Hex |
|---|---|---|
| cve | red | #F85149 |
| tool | purple | #A371F7 |
| campaign | amber | #D29922 |
| other | grey | #8B949E |
Note
The color map in the current frontend uses the key actor, but ZettelForge stores threat actor entities with type intrusion_set. As a result, intrusion_set nodes render as grey rather than blue. This is a known frontend issue.
Edge colors are assigned by relationship type: uses (purple), targets (red), related_to (blue), attributed_to (amber). All other edge types use the default border color.
Click any node to open a detail panel showing entity name, type, tier, confidence, and connected entities. Use the search input to filter nodes by label. The filter panel lets you toggle labels or filter by entity type.
Logs & Telemetry¶
Two-panel layout:
- Log panel — Filterable table of structlog events. Columns: timestamp, level badge (ERROR=red, WARNING=amber, INFO=default, DEBUG=dim), logger name, message. Enable auto-refresh to poll every 3 seconds. Click any row to expand the full structured JSON context.
- Telemetry panel — Aggregated daily statistics: query volume, latency percentiles, intent distribution.
Ingest¶
Three input modes:
- Text area — Paste CTI content and submit. Configure source metadata (domain: cti/sigma/yara/report/general; source_type: manual/report/feed/api; evolve toggle). After submission, shows the note ID, extracted entities, and latency.
- Bulk mode — Multi-line textarea, one item per line. Progress shows N/M stored with per-item success/failure status.
- File upload — Drag and drop
.txt,.md,.json(STIX bundle), or.csvfiles. File content populates the textarea for review before submission.
Entities¶
A paginated table of all entities in the knowledge graph. Columns: entity name, type badge, tier pill, confidence, connected count. Use the filter bar (type dropdown, tier checkboxes, text search) to narrow results. Click column headers to sort. Click any row for an inline detail panel.
History¶
A chronological table of recent activity. Columns: timestamp, type badge (recall=blue, synthesis=purple, remember=green), query text, latency (ms), result count, intent. Use the date range filter (today, 7 days, 30 days, all). Click any row to expand full details, re-run a query, or export JSON.
Configuration¶
Two modes:
- Feature Flags — Grouped cards showing every config section. Boolean fields render as toggle switches. Text/number fields are editable inputs. Secrets show as
***and are read-only. Fields requiring a restart show an amber "Restart Required" badge. - YAML Editor — Full
config.yamlcontent in a monospace textarea. Edit directly, then click "Apply".
API authentication¶
All API endpoints (except GET /) require authentication. Two modes:
Loopback (default). When a request comes from a loopback address (127.0.0.1, ::1, or localhost) and no API key is configured, the request is allowed without a key. Running python web/app.py binds to 127.0.0.1 by default, so local browser access requires no key.
API key. When ZETTELFORGE_WEB_API_KEY is set in your environment, all requests must supply that key. Pass it with either the X-API-Key header or as a Bearer token in the Authorization header:
export ZETTELFORGE_WEB_API_KEY=my-secret-key
curl -H "X-API-Key: my-secret-key" http://your-host:8088/api/health
Binding to all interfaces (--host 0.0.0.0) requires ZETTELFORGE_WEB_API_KEY to be set; the server will refuse to start without it:
ZETTELFORGE_WEB_API_KEY=my-secret-key python web/app.py --host 0.0.0.0
When you open the interface from a non-loopback host, the SPA prompts you to enter the API key. It stores the key in the browser's localStorage under the key zettelforgeApiKey. You can also set it directly:
localStorage.setItem('zettelforgeApiKey', 'my-secret-key');
Single-tenant mode
The OSS web interface runs in single-tenant mode — no user accounts or session management. Multi-tenant OAuth/JWT authentication is available via ThreatRecall.ai SaaS.
Next steps¶
- Web API Reference — Full endpoint documentation with request/response shapes.
- Configuration Reference — All config keys including the
web:section. - Quickstart Tutorial — Store your first CTI notes.
- Memory Manager API — Python API used under the hood.