mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-12 15:22:55 +01:00
New data query node for Directus backend integration
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,135 @@
|
||||
# TASK-001: Backend Services Panel
|
||||
|
||||
**Task ID:** TASK-001
|
||||
**Phase:** 5 - Multi-Target Deployment
|
||||
**Priority:** 🔴 Critical
|
||||
**Estimated Duration:** 1 week
|
||||
**Status:** ✅ Complete (Phase 1)
|
||||
**Created:** 2025-12-29
|
||||
**Completed:** 2025-12-29
|
||||
**Branch:** `feature/byob-backend`
|
||||
|
||||
## Overview
|
||||
|
||||
Create a new "Backend Services" sidebar panel that allows users to configure external backend databases (Directus, Supabase, Pocketbase, or any custom REST API). This panel will sit alongside the existing "Cloud Services" panel, giving users flexibility to choose their data backend.
|
||||
|
||||
## Goals
|
||||
|
||||
1. **New Sidebar Panel** - "Backend Services" tab in the far left menu ✅
|
||||
2. **Backend List** - Display configured backends with connection status ✅
|
||||
3. **Add Backend Dialog** - Configure new backends with presets or custom ✅
|
||||
4. **Edit/Delete** - Manage existing backend configurations ✅
|
||||
5. **Schema Browser** - View tables/collections from connected backends ✅ (basic)
|
||||
|
||||
## What Was Implemented
|
||||
|
||||
### Model Layer
|
||||
|
||||
- `types.ts` - Complete TypeScript interfaces for BackendConfig, schemas, events
|
||||
- `presets.ts` - Preset configurations for Directus, Supabase, Pocketbase, Custom REST
|
||||
- `BackendServices.ts` - Singleton model with CRUD, connection testing, schema introspection
|
||||
- `index.ts` - Clean exports
|
||||
|
||||
### UI Components
|
||||
|
||||
- `BackendServicesPanel.tsx` - Main panel with backend list and actions
|
||||
- `BackendCard/BackendCard.tsx` - Individual backend card with status, actions
|
||||
- `AddBackendDialog/AddBackendDialog.tsx` - Modal for adding new backends
|
||||
- All components use proper design tokens (no hardcoded colors)
|
||||
|
||||
### Key Features
|
||||
|
||||
- **Preset Selection**: Easy setup for Directus, Supabase, Pocketbase
|
||||
- **Custom REST API**: Full configurability for any REST API
|
||||
- **Connection Testing**: Test button validates connectivity
|
||||
- **Schema Introspection**: Fetches and caches table/field definitions
|
||||
- **Active Backend**: One backend can be marked as active
|
||||
- **Persistence**: Saves to project metadata
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Step 1: Create Backend Model ✅
|
||||
|
||||
- [x] Create `BackendServices/types.ts` with TypeScript interfaces
|
||||
- [x] Create `BackendServices/BackendServices.ts` singleton model
|
||||
- [x] Create `BackendServices/presets.ts` for preset configurations
|
||||
|
||||
### Step 2: Create Panel UI ✅
|
||||
|
||||
- [x] Create `BackendServicesPanel.tsx` base panel
|
||||
- [x] Create `BackendCard.tsx` for displaying a backend
|
||||
- [x] Add panel to sidebar registry in `router.setup.ts`
|
||||
|
||||
### Step 3: Add Backend Dialog ✅
|
||||
|
||||
- [x] Create `AddBackendDialog.tsx` with preset selection
|
||||
- [x] Create backend preset configurations (Directus, Supabase, etc.)
|
||||
- [x] Implement connection testing
|
||||
- [x] Implement save/cancel logic
|
||||
|
||||
### Step 4: Schema Introspection ✅ (Basic)
|
||||
|
||||
- [x] Implement Directus schema parsing
|
||||
- [x] Implement Supabase schema parsing (OpenAPI)
|
||||
- [x] Implement Pocketbase schema parsing
|
||||
- [x] Cache schema in backend config
|
||||
|
||||
### Step 5: Integration ✅
|
||||
|
||||
- [x] Add "activeBackendId" to project settings
|
||||
- [x] Emit events when backends change
|
||||
|
||||
## Files Created
|
||||
|
||||
```
|
||||
packages/noodl-editor/src/editor/src/
|
||||
├── models/BackendServices/
|
||||
│ ├── types.ts # TypeScript interfaces
|
||||
│ ├── presets.ts # Directus, Supabase, Pocketbase presets
|
||||
│ ├── BackendServices.ts # Main singleton model
|
||||
│ └── index.ts # Exports
|
||||
└── views/panels/BackendServicesPanel/
|
||||
├── BackendServicesPanel.tsx
|
||||
├── BackendCard/
|
||||
│ ├── BackendCard.tsx
|
||||
│ └── BackendCard.module.scss
|
||||
└── AddBackendDialog/
|
||||
├── AddBackendDialog.tsx
|
||||
└── AddBackendDialog.module.scss
|
||||
```
|
||||
|
||||
## Files Modified
|
||||
|
||||
```
|
||||
packages/noodl-editor/src/editor/src/router.setup.ts # Register panel
|
||||
```
|
||||
|
||||
## Testing Status
|
||||
|
||||
- [x] Editor builds and runs without errors
|
||||
- [ ] Manual testing of panel (pending user verification)
|
||||
- [ ] Test with real Directus instance
|
||||
- [ ] Test with real Supabase instance
|
||||
|
||||
## Next Steps (Future Tasks)
|
||||
|
||||
1. **TASK-002: Data Node Integration** - Update data nodes to use Backend Services
|
||||
2. **SchemaViewer Component** - Dedicated component to browse schema
|
||||
3. **Edit Backend Dialog** - Edit existing backends (currently only add/delete)
|
||||
4. **Local Docker Wizard** - Spin up backends locally
|
||||
|
||||
## Notes
|
||||
|
||||
- Keep existing Cloud Services panel untouched (Option A from planning)
|
||||
- Use design tokens for all styling (no hardcoded colors)
|
||||
- Follow existing patterns from CloudServicePanel closely
|
||||
- Panel icon: `RestApi` (Database icon didn't exist in IconName enum)
|
||||
|
||||
## Related Tasks
|
||||
|
||||
| Task | Name | Status |
|
||||
| -------- | ------------------------------------------------------- | ----------- |
|
||||
| TASK-002 | [Data Nodes Integration](../TASK-002-data-nodes/) | Not Started |
|
||||
| TASK-003 | [Schema Viewer Component](../TASK-003-schema-viewer/) | Not Started |
|
||||
| TASK-004 | [Edit Backend Dialog](../TASK-004-edit-backend-dialog/) | Not Started |
|
||||
| TASK-005 | [Local Docker Wizard](../TASK-005-local-docker-wizard/) | Not Started |
|
||||
@@ -0,0 +1,205 @@
|
||||
# Visual Filter Builder Specification
|
||||
|
||||
The Visual Filter Builder is the **hero feature** of TASK-002. It transforms the painful experience of writing Directus filter JSON into an intuitive visual interface.
|
||||
|
||||
## The Problem
|
||||
|
||||
Directus filters require complex nested JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"_and": [
|
||||
{ "status": { "_eq": "published" } },
|
||||
{ "author": { "name": { "_contains": "John" } } },
|
||||
{ "_or": [{ "views": { "_gt": 100 } }, { "featured": { "_eq": true } }] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
This is error-prone and requires memorizing operator names.
|
||||
|
||||
## The Solution
|
||||
|
||||
A visual builder that generates this JSON automatically:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ FILTER CONDITIONS [+ Add Rule] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ ┌─ AND ─────────────────────────────────────────────────────── [×] ─┐
|
||||
│ │ │
|
||||
│ │ [status ▾] [equals ▾] [published ▾] [×] │
|
||||
│ │ │
|
||||
│ │ [author.name ▾] [contains ▾] [John ] [×] │
|
||||
│ │ │
|
||||
│ │ ┌─ OR ───────────────────────────────────────────── [×] ─┐ │
|
||||
│ │ │ [views ▾] [greater than ▾] [100 ] [×] │ │
|
||||
│ │ │ [featured ▾] [equals ▾] [true ▾] [×] │ │
|
||||
│ │ │ [+ Add Condition] │ │
|
||||
│ │ └────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ │ [+ Add Condition] [+ Add Group] │
|
||||
│ └───────────────────────────────────────────────────────────────────┘
|
||||
│ │
|
||||
│ ▶ Preview JSON │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
### 1. Schema-Aware Field Dropdown
|
||||
|
||||
Fields populated from cached schema with:
|
||||
|
||||
- Nested relation traversal (`author.name`, `category.parent.name`)
|
||||
- Field type icons
|
||||
- Smart search/filtering
|
||||
|
||||
### 2. Type-Aware Operator Selection
|
||||
|
||||
| Field Type | Available Operators |
|
||||
| ---------- | ---------------------------------------------------------------------------- |
|
||||
| String | equals, not equals, contains, starts with, ends with, is empty, is not empty |
|
||||
| Number | equals, not equals, greater than, less than, >=, <=, between |
|
||||
| Boolean | equals, not equals |
|
||||
| Date | equals, before, after, between, is empty |
|
||||
| Enum | equals, not equals, in, not in |
|
||||
| Relation | equals (ID), is empty, is not empty |
|
||||
|
||||
### 3. Type-Aware Value Input
|
||||
|
||||
| Field Type | Value UI |
|
||||
| ---------- | ------------------------------------- |
|
||||
| String | Text input |
|
||||
| Number | Number input with validation |
|
||||
| Boolean | Toggle or dropdown (true/false/null) |
|
||||
| Date | Date picker |
|
||||
| Enum | Dropdown with schema-defined values |
|
||||
| Relation | Search/select from related collection |
|
||||
|
||||
### 4. AND/OR Grouping
|
||||
|
||||
- Drag-and-drop reordering
|
||||
- Unlimited nesting depth
|
||||
- Visual indentation
|
||||
- Collapse/expand groups
|
||||
|
||||
### 5. JSON Preview
|
||||
|
||||
- Toggle to see generated Directus filter
|
||||
- Syntax highlighted
|
||||
- Copy button
|
||||
- Edit JSON directly (advanced mode)
|
||||
|
||||
## Data Model
|
||||
|
||||
```typescript
|
||||
interface FilterGroup {
|
||||
id: string;
|
||||
type: 'and' | 'or';
|
||||
conditions: (FilterCondition | FilterGroup)[];
|
||||
}
|
||||
|
||||
interface FilterCondition {
|
||||
id: string;
|
||||
field: string; // e.g., "status" or "author.name"
|
||||
operator: FilterOperator;
|
||||
value: any;
|
||||
}
|
||||
|
||||
type FilterOperator =
|
||||
| '_eq'
|
||||
| '_neq' // equals, not equals
|
||||
| '_gt'
|
||||
| '_gte' // greater than (or equal)
|
||||
| '_lt'
|
||||
| '_lte' // less than (or equal)
|
||||
| '_contains'
|
||||
| '_ncontains'
|
||||
| '_starts_with'
|
||||
| '_ends_with'
|
||||
| '_in'
|
||||
| '_nin' // in array, not in array
|
||||
| '_null'
|
||||
| '_nnull' // is null, is not null
|
||||
| '_between'; // between two values
|
||||
```
|
||||
|
||||
## Implementation Approach
|
||||
|
||||
### Component Structure
|
||||
|
||||
```
|
||||
FilterBuilder/
|
||||
├── FilterBuilder.tsx # Main container
|
||||
├── FilterBuilder.module.scss
|
||||
├── FilterGroup.tsx # AND/OR group (recursive)
|
||||
├── FilterCondition.tsx # Single condition row
|
||||
├── FieldSelector.tsx # Schema-aware field dropdown
|
||||
├── OperatorSelector.tsx # Type-aware operator dropdown
|
||||
├── ValueInput.tsx # Type-aware value input
|
||||
├── JsonPreview.tsx # Generated JSON preview
|
||||
└── types.ts # TypeScript interfaces
|
||||
```
|
||||
|
||||
### State Management
|
||||
|
||||
```typescript
|
||||
// In the Query Records node property panel
|
||||
const [filter, setFilter] = useState<FilterGroup>({
|
||||
id: 'root',
|
||||
type: 'and',
|
||||
conditions: []
|
||||
});
|
||||
|
||||
// Convert to Directus format on change
|
||||
useEffect(() => {
|
||||
const directusFilter = convertToDirectusFilter(filter);
|
||||
node.setParameter('filter', directusFilter);
|
||||
}, [filter]);
|
||||
```
|
||||
|
||||
## Directus Filter Conversion
|
||||
|
||||
```typescript
|
||||
function convertToDirectusFilter(group: FilterGroup): object {
|
||||
const key = `_${group.type}`; // _and or _or
|
||||
|
||||
const conditions = group.conditions.map((item) => {
|
||||
if ('type' in item) {
|
||||
// Nested group
|
||||
return convertToDirectusFilter(item);
|
||||
} else {
|
||||
// Single condition
|
||||
return convertCondition(item);
|
||||
}
|
||||
});
|
||||
|
||||
return { [key]: conditions };
|
||||
}
|
||||
|
||||
function convertCondition(cond: FilterCondition): object {
|
||||
// Handle nested fields like "author.name"
|
||||
const parts = cond.field.split('.');
|
||||
|
||||
let result: any = { [cond.operator]: cond.value };
|
||||
|
||||
// Build nested structure from inside out
|
||||
for (let i = parts.length - 1; i >= 0; i--) {
|
||||
result = { [parts[i]]: result };
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [ ] Users can build filters without knowing Directus JSON syntax
|
||||
- [ ] Field dropdown shows all fields from schema
|
||||
- [ ] Nested relations work (author.name)
|
||||
- [ ] Operators change based on field type
|
||||
- [ ] Value inputs match field type
|
||||
- [ ] AND/OR grouping works with nesting
|
||||
- [ ] Generated JSON is valid Directus filter
|
||||
- [ ] JSON preview shows the output
|
||||
@@ -0,0 +1,213 @@
|
||||
# Data Node Specifications
|
||||
|
||||
This document defines the four data nodes for BYOB backends.
|
||||
|
||||
## 1. Query Records Node
|
||||
|
||||
The primary node for fetching data from backends.
|
||||
|
||||
### Inputs
|
||||
|
||||
| Input | Type | Description |
|
||||
| ---------- | ------------- | ----------------------------------------------- |
|
||||
| Backend | dropdown | Select configured backend (or "Active Backend") |
|
||||
| Collection | dropdown | Select table/collection from schema |
|
||||
| Filter | FilterBuilder | Visual filter builder (see FILTER-BUILDER.md) |
|
||||
| Sort Field | dropdown | Field to sort by |
|
||||
| Sort Order | enum | Ascending / Descending |
|
||||
| Limit | number | Max records to return |
|
||||
| Offset | number | Records to skip (pagination) |
|
||||
| Fields | multi-select | Fields to return (default: all) |
|
||||
| Do | signal | Trigger the query |
|
||||
|
||||
### Outputs
|
||||
|
||||
| Output | Type | Description |
|
||||
| ------------ | ------- | --------------------------------------- |
|
||||
| Records | array | Array of record objects |
|
||||
| First Record | object | First record (convenience) |
|
||||
| Count | number | Number of records returned |
|
||||
| Total Count | number | Total matching records (for pagination) |
|
||||
| Loading | boolean | True while request in progress |
|
||||
| Error | object | Error details if failed |
|
||||
| Done | signal | Fires when query completes |
|
||||
| Failed | signal | Fires on error |
|
||||
|
||||
### Property Panel UI
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Query Records │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ BACKEND │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ ○ Active Backend (Production Directus) │ │
|
||||
│ │ ● Specific Backend: [Production Directus ▾] │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ COLLECTION │
|
||||
│ [posts ▾] │
|
||||
│ │
|
||||
│ ▼ FILTER │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ [Visual Filter Builder - see FILTER-BUILDER.md] │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ▼ SORT │
|
||||
│ [created_at ▾] [Descending ▾] │
|
||||
│ │
|
||||
│ ▼ PAGINATION │
|
||||
│ Limit: [20 ] Offset: [0 ] │
|
||||
│ │
|
||||
│ ▶ FIELDS (optional) │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Create Record Node
|
||||
|
||||
Creates a new record in a collection.
|
||||
|
||||
### Inputs
|
||||
|
||||
| Input | Type | Description |
|
||||
| ---------- | -------- | --------------------------------------- |
|
||||
| Backend | dropdown | Select configured backend |
|
||||
| Collection | dropdown | Select table/collection |
|
||||
| Data | object | Record data (dynamic ports from schema) |
|
||||
| Do | signal | Trigger creation |
|
||||
|
||||
### Dynamic Inputs
|
||||
|
||||
When a collection is selected, ports are dynamically generated for each writable field:
|
||||
|
||||
- `field_title` (string)
|
||||
- `field_content` (text)
|
||||
- `field_author` (relation → user ID)
|
||||
- etc.
|
||||
|
||||
### Outputs
|
||||
|
||||
| Output | Type | Description |
|
||||
| ------- | ------ | -------------------- |
|
||||
| Record | object | The created record |
|
||||
| ID | string | ID of created record |
|
||||
| Success | signal | Fires on success |
|
||||
| Failed | signal | Fires on error |
|
||||
| Error | object | Error details |
|
||||
|
||||
---
|
||||
|
||||
## 3. Update Record Node
|
||||
|
||||
Updates an existing record.
|
||||
|
||||
### Inputs
|
||||
|
||||
| Input | Type | Description |
|
||||
| ---------- | -------- | -------------------------------- |
|
||||
| Backend | dropdown | Select configured backend |
|
||||
| Collection | dropdown | Select table/collection |
|
||||
| Record ID | string | ID of record to update |
|
||||
| Data | object | Fields to update (dynamic ports) |
|
||||
| Do | signal | Trigger update |
|
||||
|
||||
### Dynamic Inputs
|
||||
|
||||
Same as Create Record - schema-driven field inputs.
|
||||
|
||||
### Outputs
|
||||
|
||||
| Output | Type | Description |
|
||||
| ------- | ------ | ------------------ |
|
||||
| Record | object | The updated record |
|
||||
| Success | signal | Fires on success |
|
||||
| Failed | signal | Fires on error |
|
||||
| Error | object | Error details |
|
||||
|
||||
---
|
||||
|
||||
## 4. Delete Record Node
|
||||
|
||||
Deletes a record from a collection.
|
||||
|
||||
### Inputs
|
||||
|
||||
| Input | Type | Description |
|
||||
| ---------- | -------- | ------------------------- |
|
||||
| Backend | dropdown | Select configured backend |
|
||||
| Collection | dropdown | Select table/collection |
|
||||
| Record ID | string | ID of record to delete |
|
||||
| Do | signal | Trigger deletion |
|
||||
|
||||
### Outputs
|
||||
|
||||
| Output | Type | Description |
|
||||
| ------- | ------ | ---------------- |
|
||||
| Success | signal | Fires on success |
|
||||
| Failed | signal | Fires on error |
|
||||
| Error | object | Error details |
|
||||
|
||||
---
|
||||
|
||||
## Implementation Files
|
||||
|
||||
```
|
||||
packages/noodl-runtime/src/nodes/std-library/data/
|
||||
├── directus/
|
||||
│ ├── query-records.js # Query Records runtime
|
||||
│ ├── create-record.js # Create Record runtime
|
||||
│ ├── update-record.js # Update Record runtime
|
||||
│ ├── delete-record.js # Delete Record runtime
|
||||
│ └── utils.js # Shared utilities
|
||||
|
||||
packages/noodl-editor/src/editor/src/views/propertyeditor/
|
||||
├── DataNodePropertyEditor/
|
||||
│ ├── BackendSelector.tsx # Backend dropdown
|
||||
│ ├── CollectionSelector.tsx # Collection dropdown
|
||||
│ └── DynamicFieldInputs.tsx # Schema-driven field inputs
|
||||
```
|
||||
|
||||
## Node Registration
|
||||
|
||||
```javascript
|
||||
// In node index/registration
|
||||
module.exports = {
|
||||
node: QueryRecordsNode,
|
||||
setup: function (context, graphModel) {
|
||||
// Register dynamic ports based on schema
|
||||
graphModel.on('editorImportComplete', () => {
|
||||
// Set up schema-aware dropdowns
|
||||
});
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## HTTP Request Format (Directus)
|
||||
|
||||
### Query
|
||||
|
||||
```
|
||||
GET /items/{collection}?filter={json}&sort={field}&limit={n}&offset={n}
|
||||
```
|
||||
|
||||
### Create
|
||||
|
||||
```
|
||||
POST /items/{collection}
|
||||
Body: { field1: value1, field2: value2 }
|
||||
```
|
||||
|
||||
### Update
|
||||
|
||||
```
|
||||
PATCH /items/{collection}/{id}
|
||||
Body: { field1: newValue }
|
||||
```
|
||||
|
||||
### Delete
|
||||
|
||||
```
|
||||
DELETE /items/{collection}/{id}
|
||||
```
|
||||
@@ -0,0 +1,87 @@
|
||||
# TASK-002: Data Nodes Integration
|
||||
|
||||
**Task ID:** TASK-002
|
||||
**Phase:** 5 - Multi-Target Deployment (BYOB)
|
||||
**Priority:** 🔴 Critical
|
||||
**Difficulty:** 🔴 Hard
|
||||
**Estimated Time:** 1-2 weeks
|
||||
**Prerequisites:** TASK-001 (Backend Services Panel)
|
||||
**Branch:** `feature/byob-data-nodes`
|
||||
|
||||
## Objective
|
||||
|
||||
Create visual data nodes (Query, Create, Update, Delete) that connect to the configured Backend Services, with a **Visual Filter Builder** as the hero feature.
|
||||
|
||||
## Background
|
||||
|
||||
Users can now configure backend connections (TASK-001), but there's no way to actually USE them. This task bridges that gap by creating data nodes that:
|
||||
|
||||
- Read from the cached schema to populate field dropdowns
|
||||
- Execute queries against the configured backend
|
||||
- Provide a visual way to build complex filters (the key differentiator)
|
||||
|
||||
The **Visual Filter Builder** eliminates the pain of writing Directus filter JSON manually.
|
||||
|
||||
## User Story
|
||||
|
||||
> As a Noodl user, I want to visually build queries against my Directus backend, so I don't have to learn the complex filter JSON syntax.
|
||||
|
||||
## Current State
|
||||
|
||||
- Backend Services Panel exists (TASK-001 ✅)
|
||||
- Schema introspection works for Directus
|
||||
- No data nodes exist for BYOB backends
|
||||
- Users would have to use raw HTTP nodes
|
||||
|
||||
## Desired State
|
||||
|
||||
- Query Records node with visual filter builder
|
||||
- Create/Update/Delete Record nodes
|
||||
- All nodes populate dropdowns from cached schema
|
||||
- Filters generate correct Directus JSON
|
||||
|
||||
## Scope
|
||||
|
||||
### In Scope
|
||||
|
||||
- Query Records node (most complex - has filter builder)
|
||||
- Create Record node
|
||||
- Update Record node
|
||||
- Delete Record node
|
||||
- Directus backend support (primary focus)
|
||||
|
||||
### Out of Scope
|
||||
|
||||
- Supabase/Pocketbase adapters (future task)
|
||||
- Realtime subscriptions
|
||||
- Batch operations
|
||||
- File upload nodes
|
||||
|
||||
---
|
||||
|
||||
## Known Limitations (Dec 2025)
|
||||
|
||||
### Directus System Collections Not Supported
|
||||
|
||||
**Issue**: The Query Data node only uses `/items/{collection}` API endpoint, which doesn't work for Directus system tables.
|
||||
|
||||
**Affected Collections**:
|
||||
|
||||
- `directus_users` - User management (use `/users` endpoint)
|
||||
- `directus_roles` - Role management (use `/roles` endpoint)
|
||||
- `directus_files` - File management (use `/files` endpoint)
|
||||
- `directus_folders` - Folder management (use `/folders` endpoint)
|
||||
- `directus_activity` - Activity log (use `/activity` endpoint)
|
||||
- `directus_permissions` - Permissions (use `/permissions` endpoint)
|
||||
- And other `directus_*` system tables
|
||||
|
||||
**Current Behavior**: These collections may appear in the Collection dropdown (if schema introspection includes them), but queries will fail with 404 or forbidden errors.
|
||||
|
||||
**Future Enhancement**: Add an "API Path Type" dropdown to the Query node:
|
||||
|
||||
- **Items** (default) - Uses `/items/{collection}` for user collections
|
||||
- **System** - Uses `/{collection_without_directus_prefix}` for system tables
|
||||
|
||||
**Alternative Workaround**: Use the HTTP Request node with manual endpoint construction for system table access.
|
||||
|
||||
**Related**: This limitation also affects Create/Update/Delete Record nodes (when implemented).
|
||||
@@ -0,0 +1,158 @@
|
||||
# TASK-003: Schema Viewer Component
|
||||
|
||||
**Task ID:** TASK-003
|
||||
**Phase:** 5 - Multi-Target Deployment (BYOB)
|
||||
**Priority:** 🟡 Medium
|
||||
**Difficulty:** 🟢 Easy
|
||||
**Estimated Time:** 2-3 days
|
||||
**Prerequisites:** TASK-001 (Backend Services Panel)
|
||||
**Branch:** `feature/byob-schema-viewer`
|
||||
|
||||
## Objective
|
||||
|
||||
Create a dedicated Schema Viewer component that displays the cached schema from connected backends in an interactive, collapsible tree view.
|
||||
|
||||
## Background
|
||||
|
||||
TASK-001 implemented basic schema introspection, but the current UI only shows collection names in a simple list. Users need to:
|
||||
|
||||
- See all fields in each collection
|
||||
- Understand field types and constraints
|
||||
- Easily copy field names for use in nodes
|
||||
- Refresh schema when backend changes
|
||||
|
||||
## User Story
|
||||
|
||||
> As a Noodl user, I want to browse my backend's data schema visually, so I can understand what data is available and use correct field names.
|
||||
|
||||
## Current State
|
||||
|
||||
- Schema is fetched and cached (TASK-001 ✅)
|
||||
- Only collection names shown in Backend Services Panel
|
||||
- No field details visible
|
||||
- No way to copy field paths
|
||||
|
||||
## Desired State
|
||||
|
||||
- Expandable tree view of collections → fields
|
||||
- Field type icons and badges
|
||||
- Copy field path on click
|
||||
- Relation indicators
|
||||
- Search/filter collections
|
||||
- Refresh button
|
||||
|
||||
## UI Design
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ SCHEMA BROWSER [🔍 Search] [↻ Refresh] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ▼ users (12 fields) │
|
||||
│ ├─ 🔑 id (uuid) PRIMARY │
|
||||
│ ├─ 📧 email (email) REQUIRED UNIQUE │
|
||||
│ ├─ 📝 name (string) │
|
||||
│ ├─ 🖼️ avatar (image) │
|
||||
│ ├─ 🔗 role → roles (relation-one) │
|
||||
│ ├─ 📅 created_at (datetime) READ-ONLY │
|
||||
│ └─ 📅 updated_at (datetime) READ-ONLY │
|
||||
│ │
|
||||
│ ▶ posts (8 fields) │
|
||||
│ ▶ comments (6 fields) │
|
||||
│ ▶ categories (4 fields) │
|
||||
│ ▶ media (7 fields) │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
### 1. Collapsible Tree Structure
|
||||
|
||||
- Collections at root level
|
||||
- Fields as children
|
||||
- Expand/collapse with animation
|
||||
- "Expand All" / "Collapse All" buttons
|
||||
|
||||
### 2. Field Information Display
|
||||
|
||||
Each field shows:
|
||||
|
||||
- Name
|
||||
- Type (with icon)
|
||||
- Constraints (REQUIRED, UNIQUE, PRIMARY)
|
||||
- Default value (if set)
|
||||
- Relation target (for relation fields)
|
||||
|
||||
### 3. Field Type Icons
|
||||
|
||||
| Type | Icon | Color |
|
||||
| -------------- | ---- | ------- |
|
||||
| string | 📝 | default |
|
||||
| text | 📄 | default |
|
||||
| number/integer | 🔢 | blue |
|
||||
| boolean | ✅ | green |
|
||||
| datetime | 📅 | purple |
|
||||
| email | 📧 | blue |
|
||||
| url | 🔗 | blue |
|
||||
| image/file | 🖼️ | orange |
|
||||
| relation | 🔗 | cyan |
|
||||
| json | {} | gray |
|
||||
| uuid | 🔑 | yellow |
|
||||
|
||||
### 4. Copy Field Path
|
||||
|
||||
- Click on field name → copies to clipboard
|
||||
- For nested paths: `collection.field`
|
||||
- Toast notification: "Copied: author.name"
|
||||
|
||||
### 5. Search/Filter
|
||||
|
||||
- Filter collections by name
|
||||
- Filter fields within collections
|
||||
- Highlight matching text
|
||||
|
||||
### 6. Refresh Schema
|
||||
|
||||
- Manual refresh button
|
||||
- Shows last synced timestamp
|
||||
- Loading indicator during refresh
|
||||
|
||||
## Implementation
|
||||
|
||||
### File Structure
|
||||
|
||||
```
|
||||
packages/noodl-editor/src/editor/src/views/panels/BackendServicesPanel/
|
||||
├── SchemaViewer/
|
||||
│ ├── SchemaViewer.tsx # Main component
|
||||
│ ├── SchemaViewer.module.scss
|
||||
│ ├── CollectionTree.tsx # Collection list
|
||||
│ ├── FieldRow.tsx # Single field display
|
||||
│ ├── FieldTypeIcon.tsx # Type icon component
|
||||
│ └── types.ts
|
||||
```
|
||||
|
||||
### Component API
|
||||
|
||||
```typescript
|
||||
interface SchemaViewerProps {
|
||||
backend: BackendConfig;
|
||||
onRefresh: () => Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
### Integration Point
|
||||
|
||||
The SchemaViewer should be embedded in the BackendServicesPanel, shown when a backend is selected or expanded.
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [ ] Tree view displays all collections from schema
|
||||
- [ ] Fields expand/collapse per collection
|
||||
- [ ] Field types shown with appropriate icons
|
||||
- [ ] Constraints (REQUIRED, UNIQUE) visible
|
||||
- [ ] Click to copy field path works
|
||||
- [ ] Search filters collections and fields
|
||||
- [ ] Refresh button fetches fresh schema
|
||||
- [ ] Last synced timestamp displayed
|
||||
@@ -0,0 +1,183 @@
|
||||
# TASK-004: Edit Backend Dialog
|
||||
|
||||
**Task ID:** TASK-004
|
||||
**Phase:** 5 - Multi-Target Deployment (BYOB)
|
||||
**Priority:** 🟡 Medium
|
||||
**Difficulty:** 🟢 Easy
|
||||
**Estimated Time:** 1-2 days
|
||||
**Prerequisites:** TASK-001 (Backend Services Panel)
|
||||
**Branch:** `feature/byob-edit-backend`
|
||||
|
||||
## Objective
|
||||
|
||||
Add the ability to edit existing backend configurations. Currently users can only add new backends or delete them - there's no way to update URL, credentials, or settings.
|
||||
|
||||
## Background
|
||||
|
||||
TASK-001 implemented Add Backend and Delete Backend functionality. Users frequently need to:
|
||||
|
||||
- Update API tokens when they rotate
|
||||
- Change URLs between environments
|
||||
- Modify authentication settings
|
||||
- Rename backends for clarity
|
||||
|
||||
## User Story
|
||||
|
||||
> As a Noodl user, I want to edit my backend configurations, so I can update credentials or settings without recreating the entire configuration.
|
||||
|
||||
## Current State
|
||||
|
||||
- Add Backend Dialog exists (TASK-001 ✅)
|
||||
- Delete backend works
|
||||
- No edit capability - must delete and recreate
|
||||
- `updateBackend()` method exists in model but no UI
|
||||
|
||||
## Desired State
|
||||
|
||||
- "Edit" button on BackendCard
|
||||
- Opens pre-filled dialog with current values
|
||||
- Save updates existing config
|
||||
- Preserve schema cache and connection history
|
||||
|
||||
## UI Design
|
||||
|
||||
The Edit dialog reuses AddBackendDialog with modifications:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Edit Backend [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ NAME │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Production Directus │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ TYPE │
|
||||
│ [Directus ▾] ← Disabled (can't change type) │
|
||||
│ │
|
||||
│ URL │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ https://api.myapp.com │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ AUTHENTICATION │
|
||||
│ Method: [Bearer Token ▾] │
|
||||
│ Token: [••••••••••••••••••••••••••• ] [👁] [Update] │
|
||||
│ │
|
||||
│ ⚠️ Changing URL or credentials will require re-syncing schema │
|
||||
│ │
|
||||
│ [Cancel] [Test] [Save Changes] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
### Option A: Modify AddBackendDialog (Recommended)
|
||||
|
||||
Add an `editMode` prop to the existing dialog:
|
||||
|
||||
```typescript
|
||||
interface AddBackendDialogProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (backend: BackendConfig) => void;
|
||||
editBackend?: BackendConfig; // If provided, dialog is in edit mode
|
||||
}
|
||||
```
|
||||
|
||||
### Option B: Create Separate EditBackendDialog
|
||||
|
||||
Create a new component specifically for editing. More code duplication but cleaner separation.
|
||||
|
||||
### Recommendation
|
||||
|
||||
**Option A** - Reusing AddBackendDialog is simpler since 90% of the UI is identical.
|
||||
|
||||
### Key Differences in Edit Mode
|
||||
|
||||
| Aspect | Add Mode | Edit Mode |
|
||||
| -------------- | ----------------- | ---------------------------- |
|
||||
| Title | "Add Backend" | "Edit Backend" |
|
||||
| Type selector | Enabled | Disabled (can't change type) |
|
||||
| Submit button | "Add Backend" | "Save Changes" |
|
||||
| Initial values | Empty/defaults | Populated from editBackend |
|
||||
| On submit | `createBackend()` | `updateBackend()` |
|
||||
|
||||
### Changes Required
|
||||
|
||||
1. **AddBackendDialog.tsx**
|
||||
|
||||
- Accept optional `editBackend` prop
|
||||
- Initialize form with existing values
|
||||
- Disable type selector in edit mode
|
||||
- Change button text/behavior
|
||||
|
||||
2. **BackendCard.tsx**
|
||||
|
||||
- Add "Edit" button to actions
|
||||
- Open dialog with `editBackend` prop
|
||||
|
||||
3. **BackendServicesPanel.tsx**
|
||||
- Handle edit dialog state
|
||||
- Pass selected backend to dialog
|
||||
|
||||
## Code Changes
|
||||
|
||||
### BackendCard Actions
|
||||
|
||||
```tsx
|
||||
// Current
|
||||
<IconButton icon={IconName.Trash} onClick={onDelete} />
|
||||
|
||||
// After
|
||||
<IconButton icon={IconName.Pencil} onClick={onEdit} />
|
||||
<IconButton icon={IconName.Trash} onClick={onDelete} />
|
||||
```
|
||||
|
||||
### Dialog Mode Detection
|
||||
|
||||
```tsx
|
||||
function AddBackendDialog({ editBackend, onSave, ... }) {
|
||||
const isEditMode = !!editBackend;
|
||||
|
||||
const [name, setName] = useState(editBackend?.name || '');
|
||||
const [url, setUrl] = useState(editBackend?.url || '');
|
||||
// ... etc
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (isEditMode) {
|
||||
await BackendServices.instance.updateBackend({
|
||||
id: editBackend.id,
|
||||
name,
|
||||
url,
|
||||
auth
|
||||
});
|
||||
} else {
|
||||
await BackendServices.instance.createBackend({ ... });
|
||||
}
|
||||
onSave();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog title={isEditMode ? 'Edit Backend' : 'Add Backend'}>
|
||||
{/* ... form fields ... */}
|
||||
<Button
|
||||
label={isEditMode ? 'Save Changes' : 'Add Backend'}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [ ] Edit button appears on BackendCard
|
||||
- [ ] Clicking Edit opens dialog with pre-filled values
|
||||
- [ ] Backend type selector is disabled in edit mode
|
||||
- [ ] URL can be changed
|
||||
- [ ] Credentials can be updated
|
||||
- [ ] Save calls `updateBackend()` method
|
||||
- [ ] Schema cache is preserved after edit
|
||||
- [ ] Connection status updates after credential change
|
||||
@@ -0,0 +1,270 @@
|
||||
# TASK-005: Local Docker Backend Wizard
|
||||
|
||||
**Task ID:** TASK-005
|
||||
**Phase:** 5 - Multi-Target Deployment (BYOB)
|
||||
**Priority:** 🟢 Low
|
||||
**Difficulty:** 🟡 Medium
|
||||
**Estimated Time:** 3-5 days
|
||||
**Prerequisites:** TASK-001, Docker installed on user's machine
|
||||
**Branch:** `feature/byob-docker-wizard`
|
||||
|
||||
## Objective
|
||||
|
||||
Create a wizard that helps users spin up local backend instances (Directus, Pocketbase, Supabase) via Docker, and automatically configures the connection in Noodl.
|
||||
|
||||
## Background
|
||||
|
||||
Many users want to develop locally before deploying to production backends. Currently they must:
|
||||
|
||||
1. Manually install Docker
|
||||
2. Find and run the correct Docker commands
|
||||
3. Wait for the backend to start
|
||||
4. Manually configure the connection in Noodl
|
||||
|
||||
This wizard automates steps 2-4.
|
||||
|
||||
## User Story
|
||||
|
||||
> As a Noodl developer, I want to quickly spin up a local backend for development, so I can start building without setting up cloud infrastructure.
|
||||
|
||||
## Desired State
|
||||
|
||||
- "Start Local Backend" button in Backend Services Panel
|
||||
- Wizard to select backend type and configure ports
|
||||
- One-click Docker container launch
|
||||
- Automatic backend configuration after startup
|
||||
- Status monitoring and stop/restart controls
|
||||
|
||||
## UI Design
|
||||
|
||||
### Step 1: Select Backend Type
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Start Local Backend [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Select a backend to run locally via Docker: │
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ [Directus] │ │ [Pocketbase]│ │ [Supabase] │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ Directus │ │ Pocketbase │ │ Supabase │ │
|
||||
│ │ ● Selected │ │ │ │ (complex) │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||
│ │
|
||||
│ ℹ️ Requires Docker to be installed and running │
|
||||
│ │
|
||||
│ [Cancel] [Next →] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Step 2: Configure Options
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Configure Directus [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ CONTAINER NAME │
|
||||
│ [noodl-directus ] │
|
||||
│ │
|
||||
│ PORT │
|
||||
│ [8055 ] (default: 8055) │
|
||||
│ │
|
||||
│ ADMIN CREDENTIALS │
|
||||
│ Email: [admin@example.com ] │
|
||||
│ Password: [•••••••• ] │
|
||||
│ │
|
||||
│ DATABASE │
|
||||
│ ○ SQLite (simple, no extra setup) │
|
||||
│ ● PostgreSQL (recommended for production parity) │
|
||||
│ │
|
||||
│ DATA PERSISTENCE │
|
||||
│ ☑ Persist data between restarts (uses Docker volume) │
|
||||
│ │
|
||||
│ [← Back] [Start Backend] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Step 3: Starting / Progress
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Starting Directus... [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░] 45% │
|
||||
│ │
|
||||
│ ✅ Checking Docker... │
|
||||
│ ✅ Pulling directus/directus:latest... │
|
||||
│ ⏳ Starting container... │
|
||||
│ ○ Waiting for health check... │
|
||||
│ ○ Configuring connection... │
|
||||
│ │
|
||||
│ ───────────────────────────────────────────────────────────────────│
|
||||
│ $ docker run -d --name noodl-directus -p 8055:8055 ... │
|
||||
│ │
|
||||
│ [Cancel] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Step 4: Success
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Backend Ready! 🎉 [×] │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ✅ Directus is running at http://localhost:8055 │
|
||||
│ │
|
||||
│ ADMIN PANEL │
|
||||
│ URL: http://localhost:8055/admin │
|
||||
│ Email: admin@example.com │
|
||||
│ Password: (as configured) │
|
||||
│ │
|
||||
│ CONNECTION │
|
||||
│ ✅ "Local Directus" backend added to your project │
|
||||
│ ✅ Schema synced (0 collections - add some in admin panel) │
|
||||
│ │
|
||||
│ [Open Admin Panel] [Done] │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Docker Commands
|
||||
|
||||
### Directus (SQLite)
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name noodl-directus \
|
||||
-p 8055:8055 \
|
||||
-e KEY="noodl-directus-key" \
|
||||
-e SECRET="noodl-directus-secret" \
|
||||
-e ADMIN_EMAIL="admin@example.com" \
|
||||
-e ADMIN_PASSWORD="password123" \
|
||||
-e DB_CLIENT="sqlite3" \
|
||||
-e DB_FILENAME="/directus/database/data.db" \
|
||||
-v noodl-directus-data:/directus/database \
|
||||
-v noodl-directus-uploads:/directus/uploads \
|
||||
directus/directus:latest
|
||||
```
|
||||
|
||||
### Pocketbase
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name noodl-pocketbase \
|
||||
-p 8090:8090 \
|
||||
-v noodl-pocketbase-data:/pb_data \
|
||||
ghcr.io/muchobien/pocketbase:latest
|
||||
```
|
||||
|
||||
### Supabase (docker-compose required)
|
||||
|
||||
Supabase is more complex and requires multiple containers. Consider either:
|
||||
|
||||
- Linking to official Supabase local dev docs
|
||||
- Providing a bundled docker-compose.yml
|
||||
- Skipping Supabase for initial implementation
|
||||
|
||||
## Implementation
|
||||
|
||||
### File Structure
|
||||
|
||||
```
|
||||
packages/noodl-editor/src/editor/src/views/panels/BackendServicesPanel/
|
||||
├── LocalDockerWizard/
|
||||
│ ├── LocalDockerWizard.tsx # Main wizard component
|
||||
│ ├── LocalDockerWizard.module.scss
|
||||
│ ├── steps/
|
||||
│ │ ├── SelectBackendStep.tsx
|
||||
│ │ ├── ConfigureStep.tsx
|
||||
│ │ ├── ProgressStep.tsx
|
||||
│ │ └── SuccessStep.tsx
|
||||
│ ├── docker/
|
||||
│ │ ├── dockerCommands.ts # Docker command builders
|
||||
│ │ ├── directus.ts # Directus-specific config
|
||||
│ │ └── pocketbase.ts # Pocketbase-specific config
|
||||
│ └── types.ts
|
||||
```
|
||||
|
||||
### Docker Detection
|
||||
|
||||
```typescript
|
||||
async function checkDockerAvailable(): Promise<boolean> {
|
||||
try {
|
||||
const { stdout } = await exec('docker --version');
|
||||
return stdout.includes('Docker version');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function checkDockerRunning(): Promise<boolean> {
|
||||
try {
|
||||
await exec('docker info');
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Container Management
|
||||
|
||||
```typescript
|
||||
interface DockerContainer {
|
||||
name: string;
|
||||
image: string;
|
||||
ports: Record<string, string>;
|
||||
env: Record<string, string>;
|
||||
volumes: string[];
|
||||
}
|
||||
|
||||
async function startContainer(config: DockerContainer): Promise<void> {
|
||||
const args = [
|
||||
'run',
|
||||
'-d',
|
||||
'--name',
|
||||
config.name,
|
||||
...Object.entries(config.ports).flatMap(([h, c]) => ['-p', `${h}:${c}`]),
|
||||
...Object.entries(config.env).flatMap(([k, v]) => ['-e', `${k}=${v}`]),
|
||||
...config.volumes.flatMap((v) => ['-v', v]),
|
||||
config.image
|
||||
];
|
||||
|
||||
await exec(`docker ${args.join(' ')}`);
|
||||
}
|
||||
|
||||
async function waitForHealthy(url: string, timeout = 60000): Promise<boolean> {
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < timeout) {
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
if (res.ok) return true;
|
||||
} catch {}
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [ ] Docker availability check works
|
||||
- [ ] Directus container can be started
|
||||
- [ ] Pocketbase container can be started
|
||||
- [ ] Health check waits for backend to be ready
|
||||
- [ ] Backend config auto-created after startup
|
||||
- [ ] Container name/port configurable
|
||||
- [ ] Data persists with Docker volumes
|
||||
- [ ] Error handling for common issues (port in use, etc.)
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Container status in Backend Services Panel
|
||||
- Stop/Restart/Delete buttons
|
||||
- View container logs
|
||||
- Supabase support (via docker-compose)
|
||||
- Auto-start containers when project opens
|
||||
Reference in New Issue
Block a user