Commit 541bff1
Enhance Drasi Server with Web UI, solutions, and dynamic loading of pluggins. (#84)
* feat: Add Drasi Control UI with real-time SSE updates
- React 19 + TypeScript + Vite + Tailwind gaming-inspired management UI
- Flow canvas with React Flow showing Sources → Queries → Reactions topology
- Full CRUD: create/start/stop/delete for sources, queries, reactions, instances
- Draft-based editing (local state until explicit save)
- Per-kind config forms for all source types (mock, HTTP, gRPC, Postgres, platform),
query config, and all reaction types (log, HTTP, gRPC, SSE, profiler)
- Instance selector with create dialog for multi-instance management
- Inspector panel with status, config details, connections, and action buttons
- Real-time updates via SSE instead of polling (/api/v1/events endpoint)
- Internal components (__introspection__, __attach_*) filtered from UI
- Fix: Query status now reflects actual state instead of hardcoded 'Running'
- Server: Added global component events SSE endpoint for reactive UI updates
- Server: Added ComponentStatus Added/Removed variants to observability DTOs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: add source data push proxy, expandable canvas nodes, and canvas persistence
Backend:
- Add POST /api/v1/sources/:id/push proxy endpoint to forward data to
HTTP/gRPC source listening ports, bypassing browser CORS restrictions
- Add instance-specific and default-instance route handlers for the
new push endpoint in v1/handlers.rs and v1/routes.rs
- Add reqwest 0.12 dependency for outbound HTTP client calls
- Fix clippy uninlined_format_args lint errors in shared/handlers.rs
UI Canvas:
- Add NodeShell component as a shared wrapper for all node types with
expand/collapse animation (framer-motion), per-node locking, and
consistent handle rendering
- Refactor SourceNode, QueryNode, and ReactionNode to use NodeShell,
exposing detailed config (query text, source lists, reaction
properties) in the expanded view
- Add SourcePushPanel component for inline test data submission to
HTTP/gRPC sources via the new push proxy endpoint
- Add useAutoLayout hook with collision-aware node clamping to prevent
expanded nodes from overlapping neighbors
- Add useCanvasPersistence hook to save/restore node positions,
expanded state, lock state, and viewport to localStorage per instance
- Add canvas-level lock toggle, multi-select node deletion with
confirmation, and Delete/Backspace keyboard shortcut support
- Update CSS: edges render above nodes (z-index), smooth position
transitions for displaced nodes, refined node-card transition props
UI Instance Management:
- Support ?instance= URL search param for deep-linking to instances
- Persist selected instance to localStorage across sessions
- Show 'instance not found' banner with option to create the missing
instance, pre-filling the ID in CreateInstanceDialog
UI Data Model:
- Add properties field to SourceStatusResponse and ReactionStatusResponse
- Add query and queryLanguage fields to pipeline QueryInfo
- Export layout constants (COLUMN_X, NODE_SPACING_Y, NODE_START_Y) from
graph utils for use by auto-layout
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix SSE connection exhaustion causing slow UI interactions
The Create Instance dialog (and other API calls) would hang for a long time
because the browser's HTTP/1.1 connection pool was saturated by SSE streams.
Root cause: useSources, useQueries, and useReactions each opened their own
EventSource to the same /events endpoint, consuming 3 of the browser's 6
concurrent connections per domain. With inspector panels adding per-component
SSE streams on top, new API requests (POST, GET) would queue indefinitely
behind the persistent SSE connections.
Changes:
1. Shared EventSource singleton (ui/src/hooks/useApi.ts):
- All hooks now share a single EventSource per instance via a module-level
Map<instanceId, SharedES> that multiplexes events to multiple listeners
- When the last listener unsubscribes, the EventSource is closed and removed
- Reduces SSE connections from 3 per page to 1 per instance
2. Create Instance dialog loading state (CreateInstanceDialog.tsx):
- handleSave is now async and awaits the onSave callback
- Added saving state with 'Creating...' button text and disabled state
- onSave prop typed as Promise<void> for proper async handling
- Errors during creation are caught and shown inline
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix nodes visibly shrinking on initial page load
Nodes would mount at a default width then animate to their persisted size,
causing a jarring resize flash when the UI first loaded.
Root cause: buildFlowGraph() created nodes without expanded/locked/position
state. useCanvasPersistence restored this state in a useEffect AFTER mount,
triggering a Framer Motion width animation from default → persisted size.
Fix:
1. FlowCanvas now reads persisted state from localStorage synchronously
during useMemo (before mount) and pre-applies positions, expanded, and
locked flags to the initial nodes.
2. NodeShell sets initial={{ width: targetWidth }} on the motion.div so
Framer Motion starts at the correct width — no animation on mount.
Subsequent expand/collapse interactions still animate normally.
3. Exported loadPersistedState() from useCanvasPersistence for reuse.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add Drasi logo to Admin UI and rename from 'Drasi Control' to 'Drasi Server'
- Create DrasiLogo.tsx component with the official Drasi chevron icon SVG
(lime #c3fb3b + green #48e263 arrow polygons) and full wordmark variant
- Replace placeholder gradient 'D' square in AppLayout header with DrasiLogo
- Rename 'DRASI CONTROL' to 'DRASI SERVER' in the UI header
- Update HTML page title from 'Drasi Control' to 'Drasi Server'
- Update server.rs log messages from 'Drasi Control UI' to 'Drasi Server Admin UI'
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Reorganize canvas lock controls and add multi-select node lock toggle
- Move global canvas lock from top-right panel into bottom-left React Flow
Controls toolbar as a ControlButton for consistent placement
- Hide React Flow's built-in interactivity toggle (showInteractive=false)
to avoid duplicate lock icons in the controls panel
- Add selection-aware lock/unlock button in top-right toolbar that appears
alongside delete when nodes are selected (locks all selected if any are
unlocked, unlocks all if all are locked)
- Top-right panel now only renders when canvas is unlocked and nodes are
selected, keeping the canvas clean when not in multi-select mode
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Replace bottom event bar with slide-out activity panel and update favicon
- Replace EventBar (fixed bottom bar) with EventPanel (left slide-out panel)
that shows all events as a scrollable list with colored status dots
- Wire the Activity icon in the header as a toggle button to open/close
the panel, with a badge showing unread event count
- Add Clear and Close buttons to the panel header
- Remove .event-bar CSS class (no longer needed)
- Rename EventBar.tsx to EventPanel.tsx to match new component name
- Replace placeholder favicon with Drasi chevron logo (lime + green
arrows on dark background), properly centered in 32x32 viewBox
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(ui): add light/dark theme toggle with localStorage persistence
Introduce a complete theming system that allows users to switch between
light and dark modes, with their preference remembered across sessions.
CSS variable-based theming:
- Define light theme variables on :root and dark theme variables on .dark
- Replace all hardcoded hex colors in tailwind.config.js with var(--drasi-*)
- Enable Tailwind darkMode: "class" strategy
- Add --drasi-minimap-mask variable for theme-aware minimap overlay
useTheme hook (new file):
- Reads/writes theme preference to localStorage under drasi-theme key
- Toggles .dark class on <html> element to switch CSS variable sets
- Defaults to dark theme when no preference is stored
Flash-of-unstyled-content prevention:
- Inline script in index.html applies saved theme before first paint
- Removes hardcoded class="dark" from <html>, letting the script decide
Header theme toggle:
- Sun icon (in dark mode) / Moon icon (in light mode) in AppLayout header
- Theme state and toggle callback threaded from App.tsx through props
Theme-aware component updates:
- DrasiLogo wordmark fill changed from #fff to currentColor
- FlowCanvas Background and MiniMap use CSS variables instead of hex
- NodeShell hover states use drasi-text-secondary/10 instead of white/10
- Edge strokes in graph.ts use var(--drasi-border) for inactive edges
- colors.ts adds getTheme() that reads computed CSS variables at runtime
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix smooth vertical displacement of nodes during expand/collapse
Apply vertical displacement in the same setNodes call as the expand
toggle so both CSS transitions start in the same paint frame. Add
height target locking in useAutoLayout to prevent intermediate
ResizeObserver measurements from restarting CSS transitions. Switch
to useLayoutEffect for displacement to commit before paint. Slow
height expansion from 400ms to 405ms to prevent the expanding node
from visually outrunning displaced neighbors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add solution templates feature with catalog UI and validation-first deployment
Features:
- Solution templates system with YAML-based templates in /solutions directory
- REST API endpoints: GET/POST /api/v1/catalog/solutions for browsing,
POST /api/v1/instances/{id}/solutions for deployment
- Variable substitution with ${VAR:-default} syntax and YAML comment extraction
- UI integration: Browse Catalog tab in Add dialog, deploy dialog with
instance creation, variable configuration showing descriptions and usedBy
Deployment improvements:
- Validation-first: parse and validate ALL components before creating any
- Returns all validation errors at once so users can fix them together
- Creation order: sources → queries → reactions (all stopped initially)
- Start phase only after all creations succeed
UI/UX improvements:
- Redesigned Add dialog with tabbed interface (Add Component / Browse Catalog)
- Redesigned deploy dialog with visual component breakdown
- Error messages displayed on nodes and inspector panel
- Instance selector with inline create option in deploy dialog
- Fixed crypto.randomUUID fallback for older browsers
- Smarter error handling (only clear lists on 404, not transient errors)
Other:
- Added dev-build and clean-dev-build Makefile targets
- Updated OpenAPI documentation with solution endpoints
- DtoMapper now supports variable overrides for template instantiation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(ui): standardize collapsed node heights across all component types
Add collapsedMinHeight prop to NodeShell component and set it to 85px
for all node types (Source, Query, Reaction) to ensure uniform sizing
when collapsed on the canvas.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(ui): animate edges only when both connected nodes are running
Previously, source→query edges animated based on query status only,
and query→reaction edges animated based on reaction status only.
Now edges only animate (and show green) when BOTH endpoints are running.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add live query results to UI and fix solution template label
UI changes:
- Add useQueryResults hook for fetching and streaming query results via SSE
- Display live results table in expanded QueryNode with streaming indicator
- Pass instanceId through component graph for proper API routing
Solution template fix:
- Fix iot-temperature-monitor.yaml query label: Sensor -> sensorReading
(mock source generates SensorReading nodes, not Sensor)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add comprehensive solution template tests with E2E data flow validation
Add 47 new tests for solution template functionality:
- solution_catalog_test.rs (15 tests): Template listing, details, variable
extraction, and catalog API validation
- solution_deployment_test.rs (22 tests): Solution deployment via API,
variable substitution, validation errors, multi-instance support,
scriptfile bootstrap, and query result validation
- solution_e2e_data_flow_test.rs (10 tests): Complete pipeline validation
using wiremock to capture HTTP reaction output and verify data flows
correctly from Source → Query → Reaction with payload validation
Test infrastructure:
- tests/test_support/solution_helpers.rs: Template generators, router setup,
wait helpers for query results, JSONL file creation utilities
- tests/fixtures/solutions/: Test fixture templates
Also fixes flaky log stream tests in api_integration_test.rs by using
unique instance IDs per test to avoid global log registry interference.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Improve light theme contrast and fix IoT template query label
Light theme UI improvements:
- Darken canvas background (#f8fafc → #e2e8f0) so white cards stand out
- Use pure white (#ffffff) for node cards with subtle drop shadow
- Increase border contrast (#cbd5e1 → #94a3b8) for visible card edges
- Add --drasi-edge variable (#64748b) for darker connection lines
- Improve secondary text contrast (#64748b → #475569)
Fix iot-temperature-monitor.yaml:
- Change query label from 'sensorReading' to 'SensorReading' (PascalCase)
to match the labels generated by MockSource
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Redesign node cards with compact toolbar and improved UX
- Move lock/start-stop/expand controls to bottom toolbar on right
- Change lock icon to pin with angled rotation for unpinned state
- Reduce node card padding for tighter layout (p-3 → px-2 py-1.5)
- Increase toolbar icon size to 14px for better visibility
- Keep expand button always visible (greyed out when disabled)
- Add canvas click handler to close inspector panel
- Add slide-out animation for inspector panel using Framer Motion
- Add useApi hook for direct start/stop API calls from nodes
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* UI: Redesign inspector panels and canvas controls
- Add specialized inspector panels for Source, Query, and Reaction components
- Each panel shows component-specific config and Data Flow section
- Data Flow shows INPUT sources and OUTPUT reactions/queries
- Redesign node toolbar with crossfade animation between collapsed/expanded states
- Pin and expand controls in bottom toolbar (collapsed) or top-right (expanded)
- Controls disabled (not hidden) when canvas is locked
- Add delete confirmation dialog with dependency checking
- Sources cannot be deleted if queries depend on them
- Queries cannot be deleted if reactions depend on them
- Inspector panel slides out with animation on close
- Expanded nodes show only runtime info (results/activity), config in inspector only
- Canvas controls sized to 32x32 with 18px icons
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* UI: Update component color scheme to Drasi conventions
Standardize colors across all UI components:
- Sources: Green (#22c55e)
- Queries: Blue (#3b82f6)
- Reactions: Purple (#8b5cf6)
Centralize color definitions in tailwind.config.js and utils/colors.ts.
Replace all hardcoded color values with semantic Tailwind classes
(text-drasi-source, text-drasi-query, text-drasi-reaction, etc.)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* UI: Match inspector panel icons to canvas node icons
- SourceInspectorPanel: Use kind-based icon map (Database, Globe, Radio, etc.)
- QueryInspectorPanel: Use Search icon (consistent with QueryNode)
- ReactionInspectorPanel: Use kind-based icon map (FileText, Globe, Rss, etc.)
Icons now match between node cards and their inspector panels.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add .vscode to .gitignore
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Remove .vscode from git tracking
Files were already tracked before adding to .gitignore.
Use 'git rm --cached' to untrack while keeping files on disk.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* UI: Clean up persisted node state on deletion
- Add removeNodeFromPersistedState() to clean up localStorage when a node is deleted
- Add removeInstancePersistedState() for future instance deletion support
- Update useSources/useQueries/useReactions remove() to clean up persisted state
Node positions are already partitioned by instance ID via storage key prefix.
Auto-save on node add is handled by the fingerprint change detection.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* UI: Show query language type on query nodes
Display 'GQL Query' or 'Cypher Query' instead of generic 'Continuous Query'
based on the queryLanguage property.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* UI: Add auto-layout button to canvas controls
New button arranges nodes in columns by type:
- Sources on left (x=50)
- Queries in center (x=400)
- Reactions on right (x=750)
Uses LayoutGrid icon from lucide-react.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* UI: Widen instance selector for GUID display
- Increase dropdown width from 256px to 384px (w-64 → w-96)
- Increase display name truncation from 16 to 32 characters
GUIDs are 36 characters, so most instance IDs will now display fully.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* UI: Fit view after auto-layout
Auto-layout now centers and zooms to show all nodes after rearranging.
Uses React Flow's fitView with 20% padding and 300ms animation.
Refactored AutoLayout component to expose canvas API (collision + fitView)
via ref pattern.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: Add instance management features (clone, create with template, export template)
Add three new instance management capabilities to the Drasi Server UI:
1. Create Instance with Solution Template
- Add solution template dropdown to CreateInstanceDialog
- Auto-deploy selected template after instance creation
- New instance becomes active after completion
2. Clone Instance
- Add CloneInstanceDialog with progress tracking
- Clone menu item in InstanceSelector
- Copies all sources, queries, reactions with autoStart=false
- Client-side orchestration using existing CRUD endpoints
3. Create Solution Template from Instance
- Add CreateSolutionTemplateDialog with component selection
- Users can select which sources/queries/reactions to include
- New server endpoint: POST /api/v1/instances/{id}/catalog/solutions
- Exports selected components as reusable YAML template
Backend changes:
- Add CreateSolutionTemplateRequest/Response types
- Implement create_solution_template in solutions.rs
- Add handler and route for new endpoint
- Update OpenAPI documentation with new endpoint and schemas
- Add unit tests for request validation and response types
UI changes:
- New CloneInstanceDialog.tsx component
- New CreateSolutionTemplateDialog.tsx component
- Update InstanceSelector with clone/template menu items
- Update CreateInstanceDialog with template selector
- Add createSolutionTemplate to API client
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: Add instanceId to reaction node data and fix clippy warnings
- Add missing instanceId to reaction pipeline data in App.tsx
This fixes the reaction node toolbar start/stop buttons not working
because instanceId was undefined when calling the API
- Fix clippy errors in solutions.rs by using inline format string variables
(e.g., {source_id} instead of "{}", source_id)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(ui): Improve Query inspector panel layout
- Remove redundant Quick Stats section (sources/lines/reactions boxes)
since this info is already shown in the Data Flow section
- Increase query definition box max height from 256px to 384px
- Use explicit vertical scrollbar (overflow-y-auto) for long queries
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(ui): Add interactive controls to inspector Data Flow sections
Add start/stop buttons and clickable navigation to connected components
in all three inspector panels (Source, Query, Reaction).
New features:
- ConnectedComponentItem: Reusable component for data flow items
- Clickable component names navigate to that component's inspector
- Start/Stop buttons for each connected component based on status
- Spinning indicator shown during Starting/Stopping transitions
Changes:
- Create ConnectedComponentItem.tsx shared component
- Update SourceInspectorPanel with onNavigate, onStartQuery, onStopQuery
- Update QueryInspectorPanel with onNavigate, onStart/Stop for sources and reactions
- Update ReactionInspectorPanel with onNavigate, onStartQuery, onStopQuery
- Wire up all handlers in App.tsx
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: make web UI optional via config and CLI flags
Add enableUi config property and --enable-ui/--disable-ui CLI flags
to control whether the web UI is served at /ui.
Changes:
- Add enable_ui field to DrasiServerConfig (default: true)
- Add enable_ui to ResolvedServerSettings and DrasiServer structs
- Add --enable-ui and --disable-ui CLI flags (mutually exclusive)
- CLI flags override config file setting
- Update start_api() to conditionally register UI routes
- Add enable_ui()/disable_ui() methods to DrasiServerBuilder
- Update config examples with enableUi documentation
When UI is disabled:
- /ui routes are not registered (returns 404)
- Root / redirect to /ui/ is not registered
- Log message indicates UI is disabled
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs: Comprehensive README rewrite for newcomers
Significantly expanded README.md from ~1,200 to ~2,500 lines to provide
a complete user guide for Drasi Server.
New sections added:
- Table of Contents for easy navigation
- Detailed Quick Start tutorial with step-by-step instructions
- Web UI Guide covering Flow Canvas, Inspector Panels, Activity feed
- Instances section explaining multi-instance concepts
- Solution Templates documentation including deployment and creation
- VS Code Extension installation and usage guide
- Development Utilities (Makefile commands reference)
- Complete Configuration Examples with 4 real-world scenarios
Enhanced sections:
- REST API with curl examples for all endpoints, SSE streaming
- CLI Reference with --enable-ui/--disable-ui flags, .env support
- Troubleshooting expanded to 10+ common issues with solutions
- Docker deployment with better quick-start commands
The documentation is now targeted at users with little or no Drasi
experience while remaining useful as a technical reference.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: correct temperature threshold in IoT solution template
The IoT temperature monitor solution template had a default TEMP_THRESHOLD
of 75°C, but MockSource generates temperatures in the 20-30°C range.
This meant no data ever matched the query filter.
Changes:
- Fix default threshold from 75 to 25 in iot-temperature-monitor.yaml
- Fix same issue in test fixtures and helpers
- Add comprehensive e2e tests proving data flows through the pipeline
- Fix README examples using env vars on boolean/enum fields (not supported)
- Update solution_catalog_test to expect new threshold value
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(ui): use consistent colors for start/stop buttons in DATA FLOW section
Update ConnectedComponentItem to use semantic drasi-running and drasi-error
colors instead of hardcoded green-500 and amber-500, matching the button
styling used on nodes and in ActionButtons.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(ui): auto-arrange uses actual node dimensions for proper spacing
- Use measured widths from nodeLookup instead of hardcoded TARGET_WIDTHS
- Calculate column X positions dynamically based on actual expanded node sizes
- Use measured heights for proper Y spacing between nodes
- Increase node margin to 80px for better visual separation
- Maintains Sources → Queries → Reactions column ordering (left to right)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(ui): Add SolutionInstanceWizard for template-based instance creation
- Create new SolutionInstanceWizard component with multi-step wizard flow
for selecting solution templates and configuring new instances
- Add 'Create from Solution Template' option to InstanceSelector dropdown
- Simplify CreateInstanceDialog by removing inline template selection
(templates are now handled by the dedicated wizard)
- Update App.tsx to integrate the new wizard with proper state management
- Fix query return clauses in solution templates to return specific fields
instead of entire nodes (iot-temperature-monitor, simple-log-pipeline)
- Remove redundant component refresh calls after deploy (hooks auto-refresh)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(trading): Add sector performance aggregation with real-time updates
- Add SectorPerformance component displaying aggregated sector metrics
- Create sector-performance-query with GROUP BY aggregation (count, avg, sum, min, max)
- Fix SSE client to properly extract aggregation results from SSE reaction
- Fix API handlers to use ComponentGraph as source of truth for reactions/sources
- Add row highlight animation when sector values change
- Sort sectors alphabetically and align component height with other panels
- Update useDrasi hook with debug logging for sector query
- Add Drasi Server UI URL to start-demo.sh output
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(trading): Make watchlist data-driven with three-way join
- Add watchlist table to PostgreSQL schema with CDC support
- Create ON_WATCHLIST synthetic join (watchlist → stocks → stock_prices)
- Update watchlist query to use join instead of hardcoded WHERE clause
- Change UI sorting from hardcoded order to alphabetical by symbol
- Populate watchlist with META, AMZN, AMD (stocks not in portfolio)
- Add watchlist to server config tables and replication publication
This demonstrates Drasi's multi-table join capability with real-time
updates flowing through the entire join chain.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(trading): Add CRUD UI for watchlist and portfolio management
- Add Flask REST API (trading_api.py) for database CRUD operations
- GET/POST/DELETE for watchlist items
- GET/POST/PUT/DELETE for portfolio positions
- GET for available stocks list
- Add TradingApi.ts TypeScript service for frontend API calls
- Create new Watchlist.tsx component with:
- Add button with stock selector modal
- Remove button per row
- Real-time price updates via Drasi SSE
- Enhance Portfolio.tsx with:
- Add Position button with quantity/price inputs
- Edit icon per row with modal for updates
- Delete icon with confirmation dialog
- Price change highlight animations
- Fix SSE DELETE handling:
- SSEClient now processes DELETE results and marks items with _deleted flag
- useDrasi removes deleted items from data map
- Update start-demo.sh to launch Trading API on port 9200
- Update stop-demo.sh to clean up Trading API process
This demonstrates end-to-end reactive data flow:
UI Action → Database Write → PostgreSQL CDC → Drasi Query → SSE → UI Update
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(trading): Extract queries to separate presentation-friendly file
- Create queries.ts with all Cypher query definitions
- Document synthetic joins (HAS_PRICE, OWNS_STOCK, ON_WATCHLIST)
- Add descriptions explaining what each query demonstrates
- Export ALL_QUERIES array and QUERIES_BY_ID map for easy access
- Update DrasiClient.ts to import from queries.ts
This makes queries easy to show during presentations without
digging through class implementation code.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(trading): refactor position management with new dialog component
- Extract PositionDialog as reusable component for add/edit operations
- Add purchase date support to positions (TradingApi, types, forms)
- Improve delete confirmation with full position details
- Simplify Portfolio.tsx state management (unified dialog mode)
- Enhance SSE client with better reconnection handling
- Update mock generator with purchase date support
- Consolidate form error handling within dialog component
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(trading): Extract reusable QueryTable component and shared utilities
Introduce QueryTable - a generic, sortable table component that subscribes
to Drasi queries and handles data display, sorting, animations, and row
actions. This consolidates ~500 lines of repeated table logic across
Portfolio, Watchlist, StockList, and SectorPerformance components.
New shared modules:
- QueryTable.tsx: Reusable table with column definitions, sorting, row
actions, and change animations
- PortfolioSummary.tsx: Dedicated component for portfolio stats display
- useRowAnimation.ts: Hook for tracking value changes with directional
animations (price up/down indicators)
- formatters.ts: Shared number formatting (currency, percent, volume)
Component updates:
- Portfolio: Now uses QueryTable with PositionDialog for CRUD operations
- Watchlist: Simplified to QueryTable with inline add/remove actions
- StockList: Reduced to thin wrapper around QueryTable
- SectorPerformance: Converted to declarative column definitions
Net reduction of ~475 lines while adding new features (sorting, animations).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(trading): Extract shared UI components to reduce duplication
Create a shared component library for the trading app that consolidates
repeated patterns across multiple components:
New shared components:
- Icons.tsx: Reusable icon components (ArrowUp/Down, Edit, Delete, Add, etc.)
- ChangeIndicator.tsx: Percentage change display with directional arrows
- BaseDialog.tsx: Base modal with escape-to-close and overlay-click handling
- ConfirmDialog.tsx: Destructive action confirmation with detail section
- SelectDialog.tsx: Simple select-and-confirm dialog pattern
Component updates:
- Portfolio: Uses ConfirmDialog for delete confirmation, shared icons
- Watchlist: Uses SelectDialog for add-to-watchlist, shared icons
- PositionDialog: Refactored to use BaseDialog for consistent modal behavior
- StockList, SectorPerformance: Use shared ChangeIndicator
Benefits:
- ~186 lines removed from existing components
- Consistent modal behavior (escape key, body scroll lock)
- Single source of truth for icons and change indicators
- Easier to maintain and extend
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(trading-ui): Reorganize dashboard panel layout
- Add PlaceholderTable component for Orders panel
- Rearrange grid layout into three distinct rows:
- Row 1: Watchlist (1/3) + Portfolio (2/3)
- Row 2: Sector Performance + Orders (equal 50% width each)
- Row 3: Top Gainers + Top Losers + High Volume (equal 1/3 each)
- Use separate grid containers per row for better control
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(trading-ui): Add code viewer dialog for presentations
Add a code viewer feature to QueryTable components that displays:
- React component code snippets (simplified for presentation clarity)
- Cypher queries (live from queries.ts registry)
New components:
- CodeViewerDialog: Tabbed dialog with copy-to-clipboard, large text
- CodeIcon: </> brackets icon for triggering the dialog
Features:
- Animated slide-up entrance with fade overlay
- Lighter dialog background for better contrast
- Two tabs: 'React Code' and 'Cypher Query'
- Code icon appears in table header when codeSnippet prop provided
Updated components with code snippets:
- Portfolio, Watchlist, SectorPerformance, StockList
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(trading): only delete SSE reaction if app created it
Add consistent lifecycle management between queries and reactions
in the trading app's DrasiClient:
- Add createdReaction flag to track if this session created the reaction
- Set flag when creating new reaction (404 case)
- Only delete reaction on cleanup if this session created it
- Reset flag after cleanup
This allows multiple browser sessions to safely share one SSE
reaction - only the session that originally created it will
delete it on close.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(trading): Add limit orders with Drasi future function demo
Implement limit orders feature demonstrating drasi.trueFor() temporal queries:
- Add limit_orders table with status lifecycle (pending → stale → expired)
- Create postgres-broker source with separate replication slot
- Add stale orders query (drasi.trueFor 15s pending detection)
- Add expiring orders query (drasi.trueFor duration-based expiration)
- Create Orders component with order entry dialog
- Add useOrderStatusUpdates hook for automatic status transitions
- Support string-based animations for status changes (blue flash)
- Fix DrasiClient to store empty query results (prevents loading hang)
- Fix QueryInspectorPanel query formatting
- Fix CodeViewerDialog portal rendering to prevent layout shifts
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(trading-app): fetch query definition from Drasi Server and add UI link
Replace local QUERIES_BY_ID lookup in CodeViewerDialog with a live fetch
from the Drasi Server API (GET /api/v1/queries/{id}?view=full). The
Query Definition tab now displays the full query config including id,
sources, joins, and metadata in a readable YAML-like format.
Also adds an 'Open in Drasi UI' link in the dialog header that navigates
to the Drasi Server UI with the correct instance pre-selected.
Changes:
- DrasiClient: add getQueryConfig() and getDrasiUiUrl(), discover
instance ID during initialization
- useDrasi: add useQueryDefinition() and useDrasiUiUrl() hooks
- QueryTable: use server-fetched config with formatQueryConfig() helper
- CodeViewerDialog: accept drasiUiUrl prop, render external link button,
rename tab to 'Query Definition'
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(trading-app): use camelCase Cypher aliases, remove runtime transform
Cypher AS aliases are arbitrary strings, so use camelCase directly
(e.g., AS changePercent, AS previousClose, AS profitLoss) to match
the React/TypeScript conventions. This eliminates the snake_case to
camelCase runtime transform in useDrasi.ts handleResult.
Changes:
- queries.ts: rename all 25+ snake_case AS aliases to camelCase across
all 11 query definitions
- DrasiClient.ts: update createCustomQuery alias to camelCase
- useDrasi.ts: remove key.replace(/_([a-z])/g, ...) transform, keep
portfolio numeric string parsing
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(trading-app): portfolio summary not updating due to unstable map key
The portfolio-summary-query returns a single aggregation row with no
symbol or id field, so getItemKey fell back to JSON.stringify(item).
Since values change on every price update, each update produced a new
key, causing the map to accumulate stale entries. data?.[0] always
returned the oldest (first) entry, making it appear frozen.
Fix: return a stable key 'portfolio-summary' for this single-row query
so the entry is updated in place.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(trading-app): add expandable tables for presentations
Add expand/collapse functionality to QueryTable for better readability
during presentations. Click the expand button to animate the table from
its position to near-fullscreen with enlarged text; click collapse or
press Escape to reverse.
Features:
- FLIP-style animation: captures bounding rect, renders portal at
original position, CSS-transitions to fullscreen with backdrop
- Text scales up simultaneously with the container expansion using
synchronized CSS transitions and expanded-table-text class
- All child text (including hardcoded text-sm/text-xs in column
formatters) inherits the enlarged font size via CSS override
- Portfolio summary headerSlot also scales when expanded
- Escape key and backdrop click to collapse
Fixes:
- Fixed Rules of Hooks violation (expandedStyle useMemo after early
return) that caused black screen on data load
- All dialogs (add to watchlist/portfolio, limit orders, code viewer)
now use z-[100] so they render above expanded tables (z-60)
- Removed 'Updated' timestamp from table headers, moved action
buttons (code, expand) to the right side
- Enlarged code viewer dialog text to text-2xl for readability
New shared icons: ExpandIcon, CollapseIcon
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(trading-app): wrap DECIMAL fields in toFloat() for CDC compatibility
PostgreSQL CDC (WAL replication) serializes DECIMAL/NUMERIC columns as
text strings. Cypher arithmetic on strings returns null, causing P/L
and related computed fields to show as '-' for positions added via the
API. Bootstrap-loaded data (init.sql) is unaffected because the SELECT
driver returns proper numeric types.
Wrap p.purchase_price in toFloat() in portfolio and portfolio-summary
queries, and o.target_price in the active-orders distance calculation.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(trading): duration-based trueFor queries and order management fixes
Replace trueLater with trueFor using pre-calculated duration columns to
work around PostgreSQL CDC TIMESTAMP serialization incompatibility with
Drasi's datetime() parser (space separator vs T, no timezone offset).
Schema changes (init.sql):
- Add stale_duration and expire_duration INTEGER columns to limit_orders
- Revert TIMESTAMPTZ back to TIMESTAMP (TIMESTAMPTZ broke CDC events)
Query changes (queries.ts):
- Stale orders: trueFor(status='pending', duration({seconds: o.stale_duration}))
- Expiring orders: trueFor(status='stale', duration({seconds: expire - stale}))
UI changes:
- OrderDialog computes staleDuration (floor(expiresIn/2)) and expireDuration
- TradingApi.ts and Orders.tsx pass durations through to API
- Delete button enabled for all order statuses
- Stock ticker z-index lowered from 1000 to 40 (was covering dialogs)
API changes (trading_api.py):
- Accept and insert staleDuration/expireDuration fields
- Remove status restriction on order deletion (any order can be deleted)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(trading): add scriptfile bootstrap for price-feed source
Add initial-prices.jsonl with all 50 stocks at their base prices so
queries have price data immediately on startup without waiting for the
Python price generator. Uses the same node IDs (price_{SYMBOL}) and
labels (stock_prices) as the generator so live updates seamlessly
overwrite the bootstrap values.
Configure the price-feed HTTP source with a scriptfile bootstrap
provider pointing to the JSONL file.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(trading-app): fix layout jitter and UI polish
- Widen layout from max-w-7xl (1280px) to 1400px to give tables more room
- Add min-w-0 to all grid cells to prevent content overflow in CSS Grid
- Change table scroll container from overflow-auto to overflow-y-auto
overflow-x-hidden to suppress horizontal scrollbar flicker
- Truncate Watchlist Name column to prevent wrapping
- Make Query Definition the default/first tab in code viewer dialog
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add Broker Panel UI and improve trading demo cleanup
- Add broker.html: standalone Broker Panel UI for simulating order
fills, expirations, and price updates against the trading API
- Serve Broker Panel from /broker route in trading_api.py
- Simplify DrasiClient cleanup to leave queries/reactions in place
on disconnect instead of deleting them
- Add sample expired orders to init.sql seed data
- Include Broker Panel URL in start-demo.sh output
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add Cypher/GQL syntax highlighting to Query Inspector
Replace plain <pre> query display with a read-only Monaco Editor
that provides language-aware syntax highlighting for Cypher and GQL
queries. The editor auto-sizes to content height using Monaco's
getContentHeight() API, eliminating spurious scrollbars.
- Add Monarch tokenizer definitions for Cypher and GQL languages
with keywords, builtins, labels, parameters, strings, and comments
- Add QueryCodeViewer component with custom drasi-light/drasi-dark
themes matching the app's color palette
- Strip leading blank lines and normalize indentation for clean display
- Sync editor theme via MutationObserver on the html dark class
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Reduce unnecessary re-renders with React.memo and useMemo
- Wrap SourceNode, QueryNode, ReactionNode in React.memo() to skip
re-renders when node data hasn't changed
- Memoize pipelineData in App.tsx so FlowCanvas and all child nodes
only re-render when sources/queries/reactions actually change
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Optimize canvas re-renders, query streaming, and node internals
- Add CanvasLockedContext so NodeShell reads canvas lock state from
context instead of per-node data injection, preserving data
references for React.memo to work correctly
- Remove canvasLocked from node data spreading in FlowCanvas; only
update draggable prop when lock state changes
- Replace O(n) findIndex+stableKey lookups in useQueryResults with
a Map<string, number> index for O(1) streaming event processing
- Extract formatValue to module scope in QueryNode and memoize
columns and displayRows with useMemo
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Memoize inspector props, reduce animation overhead, fix MATCH highlighting
- Memoize inspector props in App.tsx with useMemo to avoid rebuilding
on every render
- Replace Framer Motion motion.div/motion.button in NodeShell with
plain elements using CSS transitions, eliminating JS-driven
animation overhead
- Memoize toolbar buttons JSX in NodeShell with useMemo
- Replace animate-ping with animate-pulse on StatusBadge for lighter
opacity-only animation instead of scale+fade
- Fix Cypher tokenizer: prioritize keywords over function-call rule
so MATCH highlights correctly when followed by parentheses
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: update for drasi-lib 0.4.x API changes and add UI build support
Adapt drasi-server to breaking changes in drasi-lib 0.4.x:
API compatibility fixes:
- Remove Added/Removed variants from ComponentStatusDto (removed upstream)
- Add BootstrapProvider/IdentityProvider to ComponentTypeDto (added upstream)
- Update api_contract_test to match new ComponentStatus variants
Dependency updates:
- Bump drasi-lib from 0.3.8 to 0.4.0
- Bump all source plugins (mock, http, grpc, postgres, platform)
- Bump all bootstrap plugins (postgres, scriptfile, platform, noop, application)
- Bump all reaction plugins (log, http, grpc, sse, platform, profiler, etc.)
- Bump index plugins (rocksdb 0.2.2 → 0.3.0, garnet 0.1.4 → 0.1.6)
- Bump state store plugin (redb 0.1.5 → 0.1.6)
- Update Cargo.lock accordingly
UI build integration:
- Add Node.js UI builder stage to Dockerfile (multi-stage)
- Add build-ui, clean-ui targets to Makefile
- Make build/run targets depend on build-ui
- Copy built UI assets into Docker runtime image
Test improvements:
- Add integration test scaffolding for multi-instance API
- Improve solution_helpers test support utilities
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: add local plugin build workflow and fix trading demo segfault
The trading demo (and any config using autoInstallPlugins) crashed with
SIGSEGV when the server was compiled against local drasi-core via
[patch.crates-io]. Root cause: Cargo patches only affect compile-time
dependency resolution for the server binary, but cdylib plugins are
separate shared libraries loaded at runtime. Registry-downloaded plugins
were compiled against published drasi-core and had a different #[repr(C)]
vtable layout, causing an ABI mismatch and heap corruption.
Changes:
- Add 'make build-local-plugins' and 'make build-local-plugins-debug'
Makefile targets that build all cdylib plugins from ../drasi-core and
copy them to target/{release,debug}/plugins/
- Remove autoInstallPlugins and plugins list from trading config so it
uses locally-built plugins instead of downloading from the registry
- Update start-demo.sh to auto-build local plugins when none are found
- Document the [patch.crates-io] vs runtime plugin distinction in
CLAUDE.md with build commands
Also includes: snapshot-based persistence refactor, clone instance
endpoint, solution template roundtrip tests, and UI updates.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: full plugin-aware config validation for validate command
Enhance 'drasi-server validate' to perform comprehensive config
verification beyond YAML structure checking:
- Load plugins from the plugins directory and check that all required
plugin kinds (sources, reactions, bootstrappers) are available
- Validate source/reaction/bootstrap configs against plugin OpenAPI
schemas using the jsonschema crate (no FFI changes required)
- Walk env var references in config JSON and report missing vars
that have no defaults (both ${VAR} and ConfigValue patterns)
- Skip ${{...}} Handlebars template expressions (not env var refs)
- Report structured errors with component type, id, and field path
- Gracefully degrade when plugins aren't installed (warnings, not
failures)
Also adds upfront plugin availability check at server startup that
reports ALL missing plugins at once instead of failing one at a time.
New files:
- src/config/plugin_validation.rs (1311 lines, 27 unit tests)
- tests/validate_cli_example_configs_test.rs (12 integration tests
including 10 negative tests for invalid configs)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(ui): correct canvas node displacement on expand/collapse
Replace anchor-point displacement logic with full bounding-box rules
in both useAutoLayout.ts and NodeShell.tsx handleToggle.
Old behavior used only C's top-left corner and N's top edge:
if (a >= prevX2) shift right — correct but imprecise
if (b >= N.y) shift down — WRONG: used N's top, not bottom
New behavior uses full bounding boxes of both N and C:
C fully above N (b2 <= y1) -> stationary
C fully left of N (a2 <= x1) -> stationary
C fully right of N (a1 >= x2) -> shift right by dx
C below N bottom + horizontal overlap -> shift down by dy
This fixes nodes being pushed down incorrectly when they were beside
the expanding node at the same height, and fixes expanding nodes
overlapping nodes directly below them because dy was applied based
on N's top edge rather than its bottom edge.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Working on UI Canvas Node expansion.
Signed-off-by: Agent of Reality <agentx@agentofreality.com>
* refactor(ui): use fixed target heights for node layout displacement
Replace dynamic height measurement/lock/timer system with a static
TARGET_DIMS lookup table for both width and height, matching how width
already worked. This eliminates the prediction error that caused
incorrect neighbor displacement when nodes expand.
Changes:
- useAutoLayout: Replace getEffectiveHeight, heightTargets, lockUntil,
recalcTick, and pendingRecalcTimers with a simple getTargetHeight()
from a TARGET_DIMS table. Remove nodeLookup/store dependency.
Fix query expandedW (was 360, now correctly 420).
- NodeShell: Add collapsedHeight/expandedHeight props with explicit
CSS height transition. Remove DOM measurement in handleToggle,
expandContentRef, useUpdateNodeInternals, and setTimeout timer.
- Node components: Replace collapsedMinHeight with collapsedHeight
and expandedHeight props matching TARGET_DIMS values.
- index.css: Set edges z-index to 0 (below nodes instead of above).
* fix: replace unwrap() with expect() in validate CLI test
The clippy::unwrap_used deny rule flagged config_path.to_str().unwrap()
in validate_cli_example_configs_test.rs. Replace with .expect() to
provide a descriptive panic message if the path is not valid UTF-8.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: add commit-staged skill for project
Add a reusable Copilot CLI skill at .github/skills/commit-staged/SKILL.md
that provides instructions for committing staged files with accurate,
conventional commit messages. This skill is project-scoped so all
contributors can use it.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: implement runtime plugin management system
Implement the full runtime plugin management design from
DESIGN-runtime-plugin-management.md across drasi-server.
Host-sdk shared types (in drasi-core, separate commit):
- PluginRegistry moved to host-sdk with RwLock, RegisteredDescriptor,
deregistration methods, and version counter
- PluginLockfile moved to host-sdk for reuse by all hosts
- PluginLifecycleManager for load/retire with event broadcasting
- PluginWatcher with notify crate for filesystem hot-reload
- Plugin naming/discovery helpers and metadata-only scanning
- plugin_id tracking on all proxy types
- Display metadata (display_name/description/icon) on descriptor traits
Server-side implementation:
- PluginOperations shared service replacing duplicated CLI/startup helpers
- PluginOrchestrator with drain-then-retire upgrade protocol
- Plugin REST API at /api/v1/plugins/ (list, get, load, install,
upgrade, promote, retire, dependents, kinds, schema, SSE events)
- Component metadata stamping (pluginId/pluginVersion/pluginGeneration)
- OpenAPI cache with registry version invalidation
- Hot-reload config (hotReloadPlugins, hotReloadDebounceMs, hotReloadMode)
- Watcher startup wiring when hot-reload is enabled
- Init wizard: dynamic plugin discovery + registry download flow
- UI: PluginManagementPanel, SchemaForm, usePluginKinds/usePluginSchema
hooks, dynamic TypeSelector with API-driven fallback
- CLI commands refactored to use PluginOperations shared helpers
- Path traversal protection on POST /plugins/load
- CLAUDE.md updated with new architecture and API endpoints
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: local dir plugin source + init wizard select-or-install flow
Add support for pluginRegistry being a local filesystem path alongside
OCI URLs. When a path is detected (absolute, relative, drive letter,
UNC, file:// URI), plugins are scanned/copied from that directory
instead of downloaded from an OCI registry.
Rewrite init wizard flow: remove upfront "download plugins?" prompt.
Instead, each component selection step (sources, bootstrappers,
reactions) shows locally installed plugins with an inline "Install
from a registry" option. Registry can be an OCI URL or local path.
After downloading, the list refreshes and the user selects again.
Changes:
- PluginOperations: search_registry() and install_from_registry()
branch on PluginSourceKind internally — all callers unchanged
- CLI plugin search/install/upgrade: support local dir sources
- Startup auto-install: support local dir sources
- Init wizard: select-or-install pattern per component category
- attach_bootstrap_to_source for per-source bootstrap selection
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor: improve init wizard prompts and settings
- Add plugin settings prompts (pluginRegistry, verifyPlugins,
autoInstallPlugins) to server settings section
- Reorder prompts: plugin registry before hot-reload
- Reorder DrasiServerConfig fields so plugin/hot-reload settings
serialize before sources/queries/reactions in YAML output
- Pass user's pluginRegistry as default for download prompts
- Replace all with_help_message() calls with hint() helper that
prints dim-colored descriptions on a separate line above each
prompt, giving consistent look/feel across server settings,
source, bootstrap, and reaction configuration sections
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: add left panel sidebar UI with 6 tabbed sections
Replace the scattered right inspector, plugin overlay, event overlay,
and top bar buttons with a unified left panel sidebar.
Layout: fixed-width icon rail (always visible) + sliding content
panel (320px, animated with framer-motion width transition).
6 sections accessible via icon rail:
- Components: searchable catalog with filter chips, click to create
- Solutions: template list with deploy/delete/create/upload actions
- Plugins: plugin list with search, type filters, expand/collapse
- Instances: instance cards with switch/clone/create actions
- Logs: event log with search and type filter chips
- Selected Component: source/query/reaction inspector (bottom icon,
active only when a canvas node is selected)
Top bar simplified to: logo, instance selector, connection dot, theme.
Inspector content redesigned for narrow width: stacked config layout,
compact connected component rows, overflow handling.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: auto-hide left panel on canvas click when not pinned
When the sidebar is not pinned, clicking the canvas background
now closes the panel in addition to deselecting components.
Pinned panels remain open on canvas clicks.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor: replace health polling with SSE connection state
Remove the /health endpoint polling (every 5s) and replace it with
reactive connection state tracking from the existing SSE EventSource.
The UI already maintains a persistent SSE connection for component
events. The EventSource onopen/onerror/onmessage callbacks now
drive the connection indicator — no polling needed.
Three states instead of two:
- "Live" (green pulsing) — SSE connection open
- "Connecting..." (amber pulsing) — SSE reconnecting
- "Disconnected" (red) — SSE connection closed
New hook: useConnectionState(instanceId) wraps the SSE state.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: derive component catalog from installed plugins only
Remove hardcoded builtin source/reaction lists from ComponentsPanel.
The catalog now shows only components whose plugins are actually
installed, derived from GET /api/v1/plugins/kinds.
Add two query types: openCypher Query and GQL Query (queries are
built into drasi-lib and always available regardless of plugins).
Known kinds retain friendly labels, icons, and descriptions when
their plugin is installed. Unknown plugin kinds show a generic
Puzzle icon with "Plugin-provided source/reaction" description.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: schema-driven config editor + OpenAPI plugin annotations
- YAML-only config editor with monaco-yaml schema validation,
autocomplete, and pre-populated templates from plugin schemas
- Schema resolver: resolves $ref, adds oneOf titles from
discriminator, generates YAML skeleton with comments
- Remove all hardcoded source/reaction forms — all plugin config
driven by schema from GET /plugins/kinds/{cat}/{kind}/schema
- Component inspector shows config as read-only YAML viewer
- Fix plugin route nesting so /kinds/{cat}/{kind}/schema works
- Add #[utoipa::path] annotations to all 11 plugin endpoints
- Add OpenAPI tests for plugin paths and schemas
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* rename build-test-plugins to build-local-test-plugins
Rename the Makefile target to match the naming convention of
build-local-plugins and build-local-plugins-debug. The new target
now copies built test plugins into target/debug/plugins/ (instead
of leaving them in ../drasi-core/target/debug/), and tests load
plugins from that local path via CARGO_MANIFEST_DIR.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: improve UI error visibility, SSE event handling, and Monaco setup
Server DTO layer:
- Add Added/Removed variants to ComponentStatusDto so the UI receives
proper structural events when components are added or deleted
- Detect add/remove from drasi-core's message convention in
ComponentEventDto::from() conversion
UI error handling:
- Add axios response interceptor to reject {success:false} responses
as errors (server returns HTTP 200 with error body for many failures)
- Wrap all inspector start/stop/delete handlers with async/try/catch
so API errors are caught and displayed in the Logs panel
- Merge SSE component events and user-action events into a single
stream for the Logs panel (previously pushEvent messages were invisible)
UI SSE event log:
- Add useComponentEventLog hook that subscribes to the shared SSE stream
and converts ComponentGraph events into log entries
- Export subscribeComponentEvents from useApi for external subscribers
Monaco Editor:
- Downgrade monaco-editor from 0.55.1 to 0.52.2 to fix incompatibility
with monaco-yaml/monaco-worker-manager (0.55 changed createWebWorker
API from moduleId-based to Worker-based, breaking YAML language services)
- Add monaco-setup.ts with local worker configuration via getWorker
- Configure @monaco-editor/react loader to use local monaco instance
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(ui): sort sidebar panel items alphabetically
Sort items in all four left-panel lists by their display name:
- Components panel: sorted by label
- Solutions panel: sorted by name
- Plugins panel: sorted by id
- Instances panel: sorted by id
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(ui): set queryLanguage based on selected query type
Pass the query kind ("cypher" or "gql") from the Components panel
through to startDraft instead of hardcoding "query". The draft
defaults now set queryLanguage to "GQL" when GQL Query is selected
and "Cypher" when openCypher Query is selected.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(plugins): add registry search API and install dialog
Add GET /api/v1/plugins/registry/search endpoint that queries the
configured (or overridden) plugin registry and returns available
plugins matching a search query.
Add InstallPluginDialog UI component that lets users browse a plugin
registry, select plugins, and batch-install them with per-plugin
progress tracking. Integrate the dialog into PluginsPanel via a
download button in the toolbar.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: add hot-reload plugin tests and test-all Makefile target
Add comprehensive tests for the hotReloadPlugins feature:
- New tests/hot_reload_test.rs with 11 tests covering:
- PluginOrchestrator.load_plugin() with real cdylib plugins
- Hot-loaded plugin kinds available in registry
- Multiple plugin loading, reload-same-plugin behavior
- PluginWatcher → PluginOrchestrator event-driven pipeline
- Full DrasiServer E2E: config with hotReloadPlugins: true,
drop plugin at runtime, verify via REST API, create component
- Side-by-side mode skip behavior
- Watcher filtering, removal detection, change detection
Add `make test-all` target that runs every available test:
- Builds cdylib test plugins (including http reaction)
- Runs all cargo tests with --include-ignored
- Runs doctests, plugin smoke tests, and VSCode tests
Also fix pre-existing issues uncovered by test-all:
- Add drasi-reaction-http to build-local-test-plugins (needed by
solution_e2e_data_flow_test.rs)
- Fix misleading #[ignore] messages to name actual plugin deps
and point to `make build-local-test-plugins`
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: standardize plugin API error responses on ErrorResponse
Plugin endpoints used ad-hoc serde_json::json!({"error": ..., "message": ...})
while component endpoints used the structured ErrorResponse type from
shared/error.rs. This forced API consumers to handle different error
shapes depending on the endpoint.
Changes:
- Add 12 plugin-specific error codes to error_codes module
(PLUGIN_NOT_FOUND, PLUGIN_LOAD_FAILED, etc.)
- Update status_from_code() to map plugin codes to HTTP statuses
- Add ErrorResponse::into_json_response() for handlers returning
(StatusCode, Json<Value>) tuples
- Convert all 18 plugin handler error sites to use ErrorResponse
with typed error codes
- Add tests for new codes, status mappings, and into_json_response
Plugin errors now return {"code": "PLUGIN_...", "message": "..."}
matching the same envelope as component ErrorResponse.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor: replace default instance wrappers with middleware
Remove 25+ *_default handler wrapper functions (~480 lines) from
v1/handlers.rs that each extracted the default instance and forwarded
to shared handlers. Replace with a single resolve_default_instance
middleware in routes.rs that injects Extension<Arc<DrasiLib>> and
Extension<String> (instance_id) into the request, allowing the
shared handlers to serve convenience routes directly.
Net reduction: -467 lines of near-identical boilerplate.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: validate path id matches body id in upsert handlers
PUT /api/v1/.../sources/{id} and PUT /api/v1/.../reactions/{id}
previously ignored the path {id} parameter and silently used the
body's id field, allowing mismatches to go undetected.
Both upsert_source_handler and upsert_reaction_handler now accept
Path(path_id) and return an error if it doesn't match the body id.
The v1 handler wrappers forward the path id from ResourcePath.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: add path traversal protection to upgrade_plugin endpoint
The load_plugin handler correctly canonicalized paths and verified
containment within the plugins directory, but upgrade_plugin joined
the user-supplied filename directly without any traversal check.
An attacker could supply "../../etc/malicious.so" to load arbitrary
shared libraries from outside the plugins directory.
Apply the same canonicalize + starts_with containment check used
by load_plugin.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* perf: compile variable regexes once with LazyLock
The ${VAR} and ${VAR:-default} regex patterns in extract_variables()
and resolve_yaml_variables() were recompiled on every function call
using Regex::new().expect(). Move them to static LazyLock<Regex>
so they compile once on first use and are reused thereafter.
Add test_var_extract_regex_compiles and test_var_resolve_regex_compiles
to verify the static patterns are valid.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: replace expect() with error handling in get_query handler
serde_json::to_value().expect() in get_query would panic the
request handler if QueryConfigDto serialization failed (e.g.,
non-finite floats). Return 500 with a log message instead.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: preserve server-level settings across config persistence saves
ConfigPersistence::save() hard-coded defaults for plugin_registry,
auto_install_plugins, plugins, verify_plugins, trusted_identities,
hot_reload_plugins, hot_reload_debounce_ms, hot_reload_mode, and
enable_ui. The first save after startup would erase these settings
from the config file.
Store original server-level settings in a PreservedServerSettings
struct at construction time and use them when reconstructing the
DrasiServerConfig during save.
Add test_save_preserves_server_level_settings to verify all 9
settings su…1 parent 38670ba commit 541bff1
235 files changed
Lines changed: 49861 additions & 9256 deletions
File tree
- .github/skills/commit-staged
- .vscode
- config
- dev-tools/vscode/drasi-server
- examples
- getting-started
- scripts
- playground
- trading
- app
- src
- components
- hooks
- services
- grpc
- types
- utils
- database
- mock-generator
- server
- solutions
- src
- api
- mappings
- core
- queries
- models
- queries
- v1
- handlers
- config
- init
- plugin
- tests
- fixtures/solutions
- test_support
- ui
- public
- src
- api
- components
- canvas
- create
- events
- inspector
- instances
- plugins
- sidebar
- solutions
- hooks
- layouts
- utils
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
18 | 22 | | |
19 | 23 | | |
20 | 24 | | |
21 | 25 | | |
22 | 26 | | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
23 | 36 | | |
24 | 37 | | |
| 38 | + | |
25 | 39 | | |
26 | 40 | | |
27 | 41 | | |
| |||
43 | 57 | | |
44 | 58 | | |
45 | 59 | | |
| 60 | + | |
46 | 61 | | |
47 | 62 | | |
48 | 63 | | |
49 | 64 | | |
50 | 65 | | |
51 | 66 | | |
52 | 67 | | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
53 | 71 | | |
54 | 72 | | |
55 | 73 | | |
| |||
102 | 120 | | |
103 | 121 | | |
104 | 122 | | |
105 | | - | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
106 | 129 | | |
107 | 130 | | |
108 | 131 | | |
| |||
121 | 144 | | |
122 | 145 | | |
123 | 146 | | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
124 | 153 | | |
125 | 154 | | |
126 | 155 | | |
| |||
182 | 211 | | |
183 | 212 | | |
184 | 213 | | |
| 214 | + | |
| 215 | + | |
185 | 216 | | |
186 | 217 | | |
187 | 218 | | |
| |||
206 | 237 | | |
207 | 238 | | |
208 | 239 | | |
209 | | - | |
| 240 | + | |
210 | 241 | | |
211 | 242 | | |
212 | 243 | | |
| |||
291 | 322 | | |
292 | 323 | | |
293 | 324 | | |
| 325 | + | |
| 326 | + | |
294 | 327 | | |
295 | 328 | | |
296 | 329 | | |
| |||
328 | 361 | | |
329 | 362 | | |
330 | 363 | | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
331 | 375 | | |
332 | 376 | | |
333 | 377 | | |
334 | | - | |
335 | | - | |
336 | | - | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
337 | 432 | | |
338 | 433 | | |
339 | 434 | | |
| |||
342 | 437 | | |
343 | 438 | | |
344 | 439 | | |
| 440 | + | |
| 441 | + | |
345 | 442 | | |
346 | 443 | | |
347 | 444 | | |
| |||
429 | 526 | | |
430 | 527 | | |
431 | 528 | | |
432 | | - | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
0 commit comments