diff --git a/development/backend/api/index.mdx b/development/backend/api/index.mdx index 932dceb..61a20ce 100644 --- a/development/backend/api/index.mdx +++ b/development/backend/api/index.mdx @@ -577,6 +577,19 @@ interface Response { **Note**: TypeScript interfaces and JavaScript variables can use camelCase internally, but all external API fields MUST use snake_case. +## Server-Sent Events (SSE) + +For real-time updates, use the `/stream` suffix pattern: + +- **REST endpoint:** `/api/{resource}/{action}` +- **SSE endpoint:** `/api/{resource}/{action}/stream` + +Example: +- `GET /api/users/me/mcp/client-activity` - REST API with pagination +- `GET /api/users/me/mcp/client-activity/stream` - SSE real-time stream + +See [Server-Sent Events (SSE)](/development/backend/api/sse) for complete SSE documentation. + ## Adding Documentation to Routes The DeployStack Backend uses **reusable JSON Schema constants** for both validation and documentation generation. This approach provides a single source of truth for API schemas. diff --git a/development/backend/api/sse.mdx b/development/backend/api/sse.mdx new file mode 100644 index 0000000..599e6b8 --- /dev/null +++ b/development/backend/api/sse.mdx @@ -0,0 +1,238 @@ +--- +title: Server-Sent Events (SSE) +description: Implementing real-time server-to-client communication using Server-Sent Events in DeployStack Backend. +--- + +## Overview + +DeployStack Backend includes `@fastify/sse` for Server-Sent Events support. SSE provides a simple, unidirectional communication channel from server to client over HTTP - ideal for live updates, notifications, and streaming data. + +The plugin is globally registered with a 30-second heartbeat interval to keep connections alive. + +## Naming Convention Standard + +All SSE endpoints **MUST** follow this URL pattern: + +- **REST endpoint:** `/api/{resource}/{action}` +- **SSE endpoint:** `/api/{resource}/{action}/stream` + +### URL Pattern Examples + +```bash +# Client Activity +GET /api/users/me/mcp/client-activity # REST API (polling) +GET /api/users/me/mcp/client-activity/stream # SSE stream + +# Notifications +GET /api/teams/:teamId/notifications # REST API (polling) +GET /api/teams/:teamId/notifications/stream # SSE stream + +# Metrics +GET /api/satellites/:satelliteId/metrics # REST API (polling) +GET /api/satellites/:satelliteId/metrics/stream # SSE stream +``` + +### Paired Endpoints + +Every SSE endpoint should have a corresponding REST endpoint: + +1. **Same Query Parameters**: Both endpoints accept identical query parameters +2. **Same Data Structure**: Both return the same data format +3. **Consistent Behavior**: Both apply the same filters and limits +4. **Fallback Support**: REST endpoint serves as fallback for clients without SSE support + +### Why `/stream`? + +- **Industry Standard**: Used by GitHub, Stripe, and Twitter APIs +- **RESTful**: Treats streaming as a sub-resource +- **Technology Agnostic**: Works for SSE, WebSockets, or any streaming protocol +- **Clear Intent**: Immediately indicates real-time streaming capability + +## Enabling SSE on a Route + +Add the `sse: true` option to any route definition: + +```typescript +server.get('/events', { sse: true }, async (request, reply) => { + // SSE methods available on reply.sse +}) +``` + +For route-specific configuration: + +```typescript +server.get('/events', { + sse: { + heartbeat: false, // Disable heartbeat for this route + serializer: (data) => JSON.stringify(data) // Custom serializer + } +}, handler) +``` + +## Sending Messages + +### Single Message + +```typescript +reply.sse.send({ data: 'Hello world' }) +``` + +### Full SSE Format + +```typescript +reply.sse.send({ + id: '123', + event: 'user_update', + data: { userId: 'abc', status: 'online' }, + retry: 5000 // Client retry interval in ms +}) +``` + +### Streaming with Async Generator + +```typescript +async function* generateUpdates() { + for (let i = 0; i < 10; i++) { + yield { data: { count: i } } + await new Promise(r => setTimeout(r, 1000)) + } +} + +reply.sse.send(generateUpdates()) +``` + +## Connection Management + +### Keep Connection Open + +By default, the connection closes after the handler completes. To keep it open: + +```typescript +reply.sse.keepAlive() +``` + +### Handle Disconnection + +```typescript +reply.sse.onClose(() => { + // Cleanup logic when client disconnects + server.log.info('Client disconnected') +}) +``` + +### Manual Close + +```typescript +reply.sse.close() +``` + +## Client Reconnection + +Handle reconnecting clients using the `Last-Event-ID` header: + +```typescript +reply.sse.replay(async (lastEventId) => { + // Fetch and send missed events since lastEventId + const missedEvents = await getMissedEvents(lastEventId) + for (const event of missedEvents) { + reply.sse.send(event) + } +}) +``` + +Access the last event ID directly: + +```typescript +const lastId = reply.sse.lastEventId +``` + +## Connection State + +```typescript +if (reply.sse.isConnected) { + reply.sse.send({ data: 'still connected' }) +} +``` + +## Complete Route Example + +```typescript +import { type FastifyInstance } from 'fastify' +import { requirePermission } from '../../../middleware/roleMiddleware' + +export default async function sseRoute(server: FastifyInstance) { + server.get('/notifications/stream', { + sse: true, + preValidation: requirePermission('notifications.read'), + schema: { + tags: ['Notifications'], + summary: 'Stream notifications', + description: 'Real-time notification stream via SSE', + security: [{ cookieAuth: [] }] + } + }, async (request, reply) => { + const userId = request.user!.id + + // Handle client reconnection + reply.sse.replay(async (lastEventId) => { + const missed = await notificationService.getMissedNotifications(userId, lastEventId) + for (const notification of missed) { + reply.sse.send({ id: notification.id, event: 'notification', data: notification }) + } + }) + + // Keep connection open + reply.sse.keepAlive() + + // Subscribe to new notifications + const unsubscribe = notificationService.subscribe(userId, (notification) => { + if (reply.sse.isConnected) { + reply.sse.send({ id: notification.id, event: 'notification', data: notification }) + } + }) + + // Cleanup on disconnect + reply.sse.onClose(() => { + unsubscribe() + server.log.debug({ userId }, 'SSE connection closed') + }) + }) +} +``` + +## Frontend Client + +```javascript +const eventSource = new EventSource('/api/notifications/stream', { + withCredentials: true // Include cookies for authentication +}) + +eventSource.addEventListener('notification', (event) => { + const data = JSON.parse(event.data) + console.log('New notification:', data) +}) + +eventSource.onerror = () => { + // Browser automatically reconnects + console.log('Connection lost, reconnecting...') +} +``` + +## TypeScript Types + +Import types from the package: + +```typescript +import type { SSEMessage } from '@fastify/sse' + +const message: SSEMessage = { + id: '123', + event: 'update', + data: { status: 'active' } +} +``` + +## Related Documentation + +- [API Documentation Generation](/development/backend/api) - General API development patterns +- [API Security](/development/backend/api/security) - Authorization middleware for protected SSE endpoints diff --git a/development/backend/cron.mdx b/development/backend/cron.mdx index 86c63d7..08ac3ed 100644 --- a/development/backend/cron.mdx +++ b/development/backend/cron.mdx @@ -33,22 +33,29 @@ Create a new file in `src/cron/jobs/`: ```typescript // src/cron/jobs/dailyCleanup.ts import type { CronJob } from '../cronManager'; -import type { JobQueueService } from '../../services/jobQueueService'; -export function createDailyCleanupJob(jobQueueService: JobQueueService): CronJob { +export function createDailyCleanupJob(): CronJob { return { name: 'daily-cleanup', schedule: '0 2 * * *', // Every day at 2 AM - - task: async () => { - await jobQueueService.createJob('cleanup_old_data', { - daysToKeep: 30 - }); - } + jobType: 'cleanup_old_data', + payload: { + daysToKeep: 30, + }, }; } ``` +The `CronJob` interface has these fields: + +| Field | Required | Description | +|-------|----------|-------------| +| `name` | Yes | Unique identifier for logging and tracking | +| `schedule` | Yes | Cron expression (see syntax below) | +| `jobType` | Yes | Job type that matches a registered worker | +| `payload` | No | Static object or function returning payload data | +| `maxAttempts` | No | Override default retry attempts (default: 3) | + ### Step 2: Create the Worker Create a worker to process the job in `src/workers/`: @@ -136,14 +143,16 @@ export function initializeCronJobs( jobQueueService: JobQueueService, logger: FastifyBaseLogger ): CronManager { - const cronManager = new CronManager(logger); + const cronManager = new CronManager(logger, jobQueueService); - cronManager.register(createDailyCleanupJob(jobQueueService)); + cronManager.register(createDailyCleanupJob()); return cronManager; } ``` +The `CronManager` handles job creation automatically when the schedule fires. You don't need to call `createJob()` yourself - just define the `jobType` and `payload` in your cron job definition. + ## Cron Expression Syntax The system uses standard cron syntax with 5 or 6 fields: @@ -194,24 +203,37 @@ Here's a complete example showing how to create a cron job that sends a daily em ```typescript // src/cron/jobs/dailyDigest.ts import type { CronJob } from '../cronManager'; -import type { JobQueueService } from '../../services/jobQueueService'; -export function createDailyDigestJob(jobQueueService: JobQueueService): CronJob { +export function createDailyDigestJob(): CronJob { return { name: 'daily-digest-email', schedule: '0 8 * * *', // Every day at 8 AM - - task: async () => { - // Create job to send digest email - await jobQueueService.createJob('send_email', { - to: 'admin@example.com', - subject: 'Daily Activity Digest', - template: 'daily_digest', - variables: { - date: new Date().toISOString().split('T')[0] - } - }); - } + jobType: 'send_email', + payload: { + to: 'admin@example.com', + subject: 'Daily Activity Digest', + template: 'daily_digest', + }, + }; +} +``` + +For dynamic payload values computed at runtime, use a function: + +```typescript +export function createDailyDigestJob(): CronJob { + return { + name: 'daily-digest-email', + schedule: '0 8 * * *', + jobType: 'send_email', + payload: () => ({ + to: 'admin@example.com', + subject: 'Daily Activity Digest', + template: 'daily_digest', + variables: { + date: new Date().toISOString().split('T')[0], + }, + }), }; } ``` diff --git a/development/backend/satellite/commands.mdx b/development/backend/satellite/commands.mdx new file mode 100644 index 0000000..0608d69 --- /dev/null +++ b/development/backend/satellite/commands.mdx @@ -0,0 +1,319 @@ +--- +title: Satellite Commands +description: Command queue system for satellite orchestration including command types, priorities, and event payload patterns. +sidebarTitle: Commands +--- + +The backend uses a command queue system to orchestrate satellites, enabling dynamic MCP server management through prioritized commands with automatic retry logic. + +## Command Queue Architecture + +Commands are created by the backend and stored in the `satelliteCommands` table. Satellites poll for pending commands, execute them, and report results back to the backend. + +### Command Flow + +``` +Backend → Creates Command → satelliteCommands Table → Satellite Polls → Executes → Reports Result → Backend Updates Status +``` + +**Key Components:** +- **Command Queue**: PostgreSQL table with priority-based ordering +- **Polling**: Satellites poll every 2-60 seconds based on priority +- **Execution**: Satellite processes command and performs action +- **Result Reporting**: Satellite reports success/failure back to backend + +## Command Types + +The system supports 5 command types defined in the `command_type` enum: + +| Command Type | Purpose | Satellite Action | +|---|---|---| +| `configure` | Trigger configuration refresh | Fetch fresh config, apply changes | +| `spawn` | Start MCP server process | Launch HTTP proxy or stdio process | +| `kill` | Stop MCP server process | Terminate process gracefully | +| `restart` | Restart MCP server | Stop and start process | +| `health_check` | Verify server health | Call tools/list to check connectivity | + +### Configure Commands + +**Most Common**: Used for all MCP installation lifecycle events (create/update/delete). + +**How It Works:** +1. Backend creates `configure` command with event metadata +2. Satellite fetches fresh configuration from backend +3. DynamicConfigManager compares old vs. new config +4. Identifies added/removed/modified servers +5. Takes appropriate action (spawn/restart/terminate) + +**Key Insight**: The satellite doesn't parse individual event types. All `configure` commands trigger a full configuration refresh where changes are detected by comparing configurations. + +## Command Priorities + +Commands are processed based on priority: + +| Priority | Use Case | Polling Interval | +|---|---|---| +| `immediate` | New installations, deletions | 2 seconds | +| `high` | Server recovery, critical updates | 10 seconds | +| `normal` | Configuration updates | 30 seconds | +| `low` | Maintenance tasks | 60 seconds | + +Higher priority commands are retrieved first when satellite polls. + +## Command Payload Structure + +All commands include a JSON payload with event-specific data: + +```typescript +interface CommandPayload { + event?: string; // Event type for logging/tracking + installation_id?: string; // MCP installation identifier + team_id?: string; // Team context + server_name?: string; // Legacy server identifier + // Additional command-specific fields +} +``` + +## Command Event Types + +All `configure` commands include an `event` field in the payload for tracking and logging: + +| Event Type | When Sent | Purpose | +|---|---|---| +| `mcp_installation_created` | New MCP installation | Satellite refreshes config, spawns new servers | +| `mcp_installation_updated` | Config changes (args/env/headers) | Satellite refreshes config, restarts affected servers | +| `mcp_installation_deleted` | Installation removed | Satellite refreshes config, terminates removed servers | +| `mcp_recovery` | Server recovered from failure | Satellite rediscovers tools for recovered server | + +### Event Field Usage + +**Current Implementation**: The satellite does NOT parse the `event` field. All configure commands trigger a full configuration refresh where the satellite: + +1. Fetches fresh config from backend +2. Compares with current running config +3. Identifies added/removed/modified servers +4. Takes appropriate action + +**Purpose of Event Field**: +- Database record keeping +- Structured logging +- Future extensibility +- Developer debugging + +**Example payload** for deletion: +```json +{ + "event": "mcp_installation_deleted", + "installation_id": "U3hCfHenbK5kH2_8ehSGx", + "team_id": "4vj7igb2fcwzmko" +} +``` + +## SatelliteCommandService API + +The backend provides convenience methods for creating commands: + +### notifyMcpInstallation() + +Creates immediate priority configure commands when a new MCP server is installed. + +```typescript +await satelliteCommandService.notifyMcpInstallation( + installationId, + teamId, + userId +); +``` + +**Payload**: `event: 'mcp_installation_created'` + +### notifyMcpUpdate() + +Creates immediate priority configure commands when MCP configuration is updated. + +```typescript +await satelliteCommandService.notifyMcpUpdate( + installationId, + teamId, + userId +); +``` + +**Payload**: `event: 'mcp_installation_updated'` + +### notifyMcpDeletion() + +Creates immediate priority configure commands when an MCP installation is deleted. + +```typescript +await satelliteCommandService.notifyMcpDeletion( + installationId, + teamId, + userId +); +``` + +**Payload**: `event: 'mcp_installation_deleted'` + +### notifyMcpRecovery() + +Creates high priority configure commands when a server recovers from failure. + +```typescript +await satelliteCommandService.notifyMcpRecovery( + installationId, + teamId +); +``` + +**Payload**: `event: 'mcp_recovery'` + +## Critical Pattern + +**ALWAYS use the correct convenience method**: + +- Installation created → `notifyMcpInstallation()` +- Installation updated → `notifyMcpUpdate()` +- Installation deleted → `notifyMcpDeletion()` +- Server recovered → `notifyMcpRecovery()` + +**NEVER** call the wrong method for an operation! For example, don't call `notifyMcpInstallation()` for delete operations. + +## Command Lifecycle + +Commands progress through the following states: + +| Status | Description | +|---|---| +| `pending` | Command created, awaiting satellite poll | +| `acknowledged` | Satellite retrieved command | +| `executing` | Satellite processing command | +| `completed` | Command executed successfully | +| `failed` | Command execution failed | + +### Retry Logic + +Failed commands are automatically retried: + +- **Max Retries**: 3 attempts +- **Retry Delay**: Exponential backoff +- **Expiration**: Commands expire after 5 minutes + +## Database Schema + +**Table**: `satelliteCommands` + +**Key Fields**: +- `id`: Command UUID +- `satellite_id`: Target satellite +- `command_type`: One of 5 command types +- `priority`: Command priority level +- `payload`: JSON command data (includes event type) +- `status`: Current execution state +- `target_team_id`: Team context +- `correlation_id`: Request tracing +- `retry_count`: Retry attempts +- `error_message`: Failure details +- `result`: Execution result + +## Command Processing on Satellite + +When satellites receive commands: + +**For `configure` commands**: +1. Fetch fresh configuration from backend +2. Compare with current running config +3. Identify changes (added/removed/modified servers) +4. Execute appropriate actions: + - **Added**: Spawn new processes + - **Modified**: Restart affected processes + - **Removed**: Terminate processes + +**For `spawn` commands**: +1. Parse server configuration +2. For stdio: Launch subprocess with JSON-RPC +3. For HTTP/SSE: Create proxy tracker entry +4. Discover tools via tools/list call + +**For `kill` commands**: +1. Locate running process +2. Send SIGTERM for graceful shutdown +3. Wait for timeout (10 seconds) +4. Send SIGKILL if needed + +**For `restart` commands**: +1. Execute kill sequence +2. Wait for process termination +3. Execute spawn sequence + +**For `health_check` commands**: +1. Call tools/list on target server +2. Verify response +3. Report health status + +## Example Usage + +### Creating Commands for Installation Deletion + +```typescript +// In route handler +const satelliteCommandService = new SatelliteCommandService(db, logger); +const commands = await satelliteCommandService.notifyMcpDeletion( + installationId, + teamId, + userId +); + +logger.info({ + installationId, + commandsCreated: commands.length, + satelliteIds: commands.map(c => c.satellite_id) +}, 'Satellite commands created for deletion'); +``` + +### Creating Commands for Configuration Update + +```typescript +// After updating environment variables +const satelliteCommandService = new SatelliteCommandService(db, logger); +const commands = await satelliteCommandService.notifyMcpUpdate( + installationId, + teamId, + userId +); +``` + +## Monitoring Commands + +### Check Command Status + +```sql +SELECT id, command_type, status, priority, created_at, updated_at +FROM "satelliteCommands" +WHERE satellite_id = 'sat-123' +ORDER BY created_at DESC +LIMIT 10; +``` + +### View Command Payload + +```sql +SELECT id, command_type, payload::json->>'event' as event_type, payload +FROM "satelliteCommands" +WHERE id = 'cmd-456'; +``` + +### Monitor Failed Commands + +```sql +SELECT id, command_type, status, retry_count, error_message +FROM "satelliteCommands" +WHERE status = 'failed' +AND retry_count >= max_retries; +``` + +## Related Documentation + +- [Satellite Communication](/development/backend/satellite/communication) - Overall communication architecture +- [Satellite Events](/development/backend/satellite/events) - Event system for real-time updates +- [Database Management](/development/backend/database/) - Schema and migrations diff --git a/development/backend/satellite/communication.mdx b/development/backend/satellite/communication.mdx index 909a34c..3eefcce 100644 --- a/development/backend/satellite/communication.mdx +++ b/development/backend/satellite/communication.mdx @@ -46,6 +46,7 @@ The satellite communication system includes: - Satellites register with backend and receive API keys - Initial status set to 'inactive' for security - API keys stored as Argon2 hashes in database +- Satellite URL sent during registration (optional - auto-detected if not provided) **Activation Process**: - Satellites send heartbeat after registration @@ -82,8 +83,8 @@ The system uses three distinct communication patterns: **Heartbeat (Satellite → Backend, Periodic)**: - Satellites report status every 30 seconds -- Contains: System metrics, process counts, resource usage -- Used for: Health monitoring, capacity planning +- Contains: System metrics, process counts, resource usage, satellite URL (first heartbeat only) +- Used for: Health monitoring, capacity planning, satellite URL updates **Events (Satellite → Backend, Immediate)**: - Satellites emit events when actions occur, batched every 3 seconds @@ -149,6 +150,17 @@ The backend maintains a priority-based command queue system: - `normal`: Standard commands processed during regular polling - `low`: Background maintenance commands +### Command Event Types + +Configure commands include an `event` field in the payload for tracking purposes: + +- `mcp_installation_created` - New installation +- `mcp_installation_updated` - Configuration change +- `mcp_installation_deleted` - Installation removed +- `mcp_recovery` - Server recovery + +See [Satellite Commands](/development/backend/satellite/commands) for complete event type documentation and usage patterns. + ### Adaptive Polling Strategy Satellites adjust polling behavior based on backend signals: @@ -191,6 +203,7 @@ Satellites report health and performance metrics: - Disk usage and network connectivity status - Process count and resource utilization - Uptime and stability indicators +- Satellite URL (first heartbeat after startup only) - Updates public URL for satellite discovery **Process Metrics**: - Individual MCP server process status @@ -259,6 +272,7 @@ The satellite system integrates with existing DeployStack schema through 5 speci - Type classification (global/team) and ownership - Capability tracking and status monitoring - API key management and authentication +- Satellite URL tracking - Publicly accessible URL updated during registration and first heartbeat **Command Queue** (`satelliteCommands`): - Priority-based command orchestration diff --git a/development/frontend/api-sse.mdx b/development/frontend/api-sse.mdx new file mode 100644 index 0000000..ec1639f --- /dev/null +++ b/development/frontend/api-sse.mdx @@ -0,0 +1,252 @@ +--- +title: Using Server-Sent Events (SSE) +description: How to consume real-time SSE endpoints from the backend using the useSSE composable +--- + +# Using Server-Sent Events (SSE) + +Server-Sent Events (SSE) provide real-time, unidirectional communication from the backend to the frontend. This is useful for live updates without polling. + +## When to Use SSE + +Use SSE when you need: +- Real-time updates from the server (activity streams, notifications, live stats) +- Automatic reconnection on connection loss +- Lower network overhead compared to polling + + +SSE is unidirectional (server � client). For bidirectional communication, use WebSockets instead. + + +## SSE Endpoint URL Pattern + +All SSE endpoints use the `/stream` suffix: + +```typescript +// REST API endpoint (polling) +const response = await fetch('/api/users/me/mcp/client-activity?team_id=123&limit=20') +const data = await response.json() + +// SSE streaming endpoint (real-time) +const url = '/api/users/me/mcp/client-activity/stream?team_id=123&limit=20' +connect(url) +``` + +### URL Structure + +- **REST:** `/api/{resource}/{action}` - Returns snapshot of current data +- **SSE:** `/api/{resource}/{action}/stream` - Pushes real-time updates + +Both endpoints: +- Accept the same query parameters +- Return the same data structure +- Apply the same filters and limits + +The REST endpoint serves as a fallback for clients that don't support SSE. + +## The useSSE Composable + +The `useSSE` composable handles all SSE logic including connection management, auto-reconnect, and cleanup. + +### Import + +```typescript +import { useSSE } from '@/composables/useSSE' +import type { McpClientActivity } from '@/services/mcpClientActivityService' +``` + +### Basic Usage + + + + Call `useSSE()` with your expected data type and the SSE event name: + + ```typescript + const { + data: activities, + isLoading, + error, + connect, + disconnect + } = useSSE('client_activity') + ``` + + + + Call `connect()` with the full SSE stream URL: + + ```typescript + onMounted(() => { + const url = McpClientActivityService.getStreamUrl(teamId, { + limit: 20, + active_within_minutes: 30 + }) + connect(url) + }) + ``` + + + + The `data` ref will automatically update when the server sends new events: + + ```vue + + ``` + + + +## Complete Example + +Here's the complete implementation from [McpClientConnectionsCard.vue](https://github.com/deploystackio/deploystack/blob/main/services/frontend/src/components/mcp-server/McpClientConnectionsCard.vue): + +```vue + + + +``` + +## API Reference + +### useSSE\() + +```typescript +function useSSE( + eventName: string, + options?: UseSSEOptions +): UseSSEReturn +``` + +**Parameters:** +- `eventName`: The SSE event name to listen for (must match backend event name) +- `options`: Optional configuration + +**Options:** +```typescript +interface UseSSEOptions { + reconnectDelay?: number // Default: 5000ms + withCredentials?: boolean // Default: true +} +``` + +**Returns:** +```typescript +interface UseSSEReturn { + data: Ref // Reactive data from SSE stream + isConnected: Ref // Connection status + isLoading: Ref // Initial loading state + error: Ref // Error message if any + connect: (url: string) => void // Connect to SSE endpoint + disconnect: () => void // Close connection +} +``` + +## Auto-Reconnection + +The composable automatically handles reconnection when the connection is lost: +- Reconnects after 5 seconds by default (configurable via `reconnectDelay`) +- Maintains the last URL for reconnection +- Clears any pending reconnection attempts on manual `disconnect()` + + +The composable automatically disconnects when the component unmounts via `onUnmounted()`. + + +## Backend SSE Event Format + +Your backend SSE endpoint must send events in this format: + +```typescript +// Backend sends: +reply.sse.send({ + event: 'client_activity', // Must match eventName in useSSE() + data: JSON.stringify({ + activities: [...] // Your actual data + }) +}) +``` + +The composable will: +1. Parse the JSON data +2. Extract the first key's value (e.g., `activities` array) +3. Update the `data` ref + + +Make sure the `event` name in your backend matches the `eventName` parameter in `useSSE()`. Mismatched event names will cause the composable to never receive data. + + +## Related Documentation + +- [Backend SSE Plugin Configuration](/development/backend/api/sse) +- [useEventBus Composable](/development/frontend/event-bus) diff --git a/development/frontend/ui/design-card.mdx b/development/frontend/ui/design-card.mdx new file mode 100644 index 0000000..50ebb87 --- /dev/null +++ b/development/frontend/ui/design-card.mdx @@ -0,0 +1,186 @@ +--- +title: Card (DsCard) +description: Vercel-style card component with title, description, content area, and flexible footer. +--- + +The `DsCard` component is a Vercel-style fieldset/card for forms and settings. It features a content area with title and description, plus a footer with flexible slots for status text and action buttons. + +## Component Location + +``` +services/frontend/src/components/ui/ds-card/ + DsCard.vue # Card component + index.ts # Component exports +``` + +## Props + +| Prop | Type | Required | Description | +|------|------|----------|-------------| +| `title` | `string` | No | Card heading (hidden when not provided) | + +## Slots + +| Slot | Description | +|------|-------------| +| `default` | Main content area (description, forms, inputs, etc.) | +| `footer-status` | Left side of footer (text, links, hints) | +| `footer-actions` | Right side of footer (buttons, button groups) | + +## Basic Usage + +```vue + + + +``` + +## Examples + +### Simple Card with Action + +```vue + +

+ Permanently delete this team and all its data. This action cannot be undone. +

+ + +
+``` + +### Card with Status Link + +```vue + +

+ Use this key to authenticate API requests. +

+
+ + +
+ + + + +
+``` + +### Card with Multiple Actions + +```vue + +

+ Manage your subscription and billing settings. +

+
+
+

Pro Plan

+

$20/month

+
+ Active +
+ + + + +
+``` + +### Card Without Footer + +```vue + +

People with access to this team.

+
    +
  • John Doe - Admin
  • +
  • Jane Smith - Member
  • +
+
+``` + +### Card with Only Status (No Actions) + +```vue + +

+ Current resource usage for this billing period. +

+ + + +
+``` + +### Card Without Title + +Use DsCard without a title when the content has its own heading or when wrapping existing components that manage their own titles. + +```vue + + + +``` + +## Styling + +The component uses these key styles: + +- **Container**: `rounded-lg border border-neutral-200 bg-white` +- **Content padding**: `p-6` +- **Title**: `text-xl font-medium leading-8 tracking-tight` +- **Footer**: `bg-neutral-50 border-t border-neutral-200 px-6 py-4` +- **Footer status**: `text-sm text-muted-foreground` + +### Visual Structure + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Title (text-xl, font-medium) │ +│ │ +│ [Default slot - description, forms, inputs, content] │ +│ │ +├─────────────────────────────────────────────────────────────┤ +│ #footer-status (left) #footer-actions (right) │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Related Documentation + +- [Page Heading](/development/frontend/ui/design-page-heading) - Page title component +- [UI Design System](/development/frontend/ui/) - Overall design patterns diff --git a/development/frontend/ui/design-navbar.mdx b/development/frontend/ui/design-navbar.mdx new file mode 100644 index 0000000..eaa89be --- /dev/null +++ b/development/frontend/ui/design-navbar.mdx @@ -0,0 +1,242 @@ +--- +title: Top Navigation Bar +description: Guide for implementing the two-row top navigation bar using the DsNavbar component. +--- + +Use the `DsNavbar` component to display a Vercel-style top navigation bar with two rows: a brand row and a navigation row. This component is designed to be used inside `NavbarLayout.vue`. + +## Component Location + +``` +services/frontend/src/components/ui/ds-navbar/ + - DsNavbar.vue # Main container (orchestrates all sub-components) + - DsNavbarBrand.vue # Logo + Team selector dropdown + - DsNavbarLinks.vue # Desktop navigation links + - DsNavbarTeamsMenu.vue # Teams dropdown menu + - DsNavbarAdminMenu.vue # Admin dropdown (global_admin only) + - DsNavbarUserMenu.vue # User avatar dropdown + - DsNavbarMobileMenu.vue # Mobile hamburger menu (Sheet drawer) +-- index.ts # Exports + CVA variants +``` + +## Layout Structure + +The navbar has two rows: + +``` + --------------------------------------------------------------------- + Row 1: [Logo] / [Team Dropdown] [User Menu] [a]  +---------------------------------------------------------------------$ + Row 2: Dashboard | MCP Server | Client Config | Statistics | Teams | Admin  +--------------------------------------------------------------------- +``` + +- **Row 1** (h-14): Brand with team selector on the left, user menu on the right +- **Row 2** (h-12): Navigation links and dropdown menus (desktop only) +- **Mobile**: Row 2 is hidden, hamburger menu opens a Sheet drawer + +## Usage + +The navbar is typically used via `NavbarLayout`: + +```vue + + + +``` + +For direct usage of just the navbar: + +```vue + + + +``` + +## Sub-Components + +### DsNavbarBrand + +Displays the logo and team selector dropdown. + +| Prop | Type | Description | +|------|------|-------------| +| `teams` | `Team[]` | List of teams available to select | +| `selectedTeam` | `Team \| null` | Currently selected team | +| `teamsLoading` | `boolean` | Shows loading state in dropdown | +| `teamsError` | `string` | Error message to display | + +| Event | Payload | Description | +|-------|---------|-------------| +| `select-team` | `Team` | Emitted when a team is selected | + +### DsNavbarLinks + +Renders the main navigation links (desktop only). + +| Prop | Type | Description | +|------|------|-------------| +| `items` | `NavItem[]` | Array of navigation items | + +```typescript +interface NavItem { + title: string + icon: any // Lucide icon component + url: string +} +``` + +### DsNavbarTeamsMenu + +Dropdown menu with team-related navigation items: My Teams, Manage Team. + +### DsNavbarAdminMenu + +Dropdown menu for admin navigation. Only visible to global admins. + +| Prop | Type | Description | +|------|------|-------------| +| `isVisible` | `boolean` | Controls visibility based on user role | + +Admin menu items: +- Global Settings +- Users +- Teams +- MCP Catalog +- MCP Categories +- Satellites +- Background Jobs + +### DsNavbarUserMenu + +User avatar with dropdown menu for account and logout. + +| Prop | Type | Description | +|------|------|-------------| +| `userName` | `string` | User's display name | +| `userEmail` | `string` | User's email | +| `userLoading` | `boolean` | Shows loading state | + +| Event | Description | +|-------|-------------| +| `go-to-account` | Navigate to account page | +| `logout` | Trigger logout | + +### DsNavbarMobileMenu + +Hamburger button that opens a Sheet drawer with all navigation on mobile. + +| Prop | Type | Description | +|------|------|-------------| +| `navigationItems` | `NavItem[]` | Main navigation items | +| `isGlobalAdmin` | `boolean` | Show admin section | +| `teams` | `Team[]` | Available teams | +| `selectedTeam` | `Team \| null` | Currently selected team | +| `userName` | `string` | User's display name | +| `userEmail` | `string` | User's email | + +## Variants + +The navbar supports style variants via CVA: + +```typescript +import { navbarVariants } from '@/components/ui/ds-navbar' + +// Available variants +navbarVariants({ variant: 'default' }) // Semi-transparent border +navbarVariants({ variant: 'solid' }) // Solid border +``` + +```vue + +``` + +## Active State Detection + +Navigation links show an active state when the current route matches: + +```typescript +const isRouteActive = (url: string) => { + const currentPath = router.currentRoute.value.path + + // Special case: '/teams' matches exactly + if (url === '/teams') { + return currentPath === '/teams' + } + + // Other routes match with startsWith + return currentPath.startsWith(url) +} +``` + +Active links get `bg-accent text-accent-foreground` styling. + +## Team Selection + +The navbar manages team selection state and persists it via EventBus: + +```typescript +const selectTeam = (team: Team) => { + selectedTeam.value = team + eventBus.setState('selected_team_id', team.id) + eventBus.emit('team-selected', { teamId: team.id, teamName: team.name }) +} +``` + +Other components can listen for team changes: + +```typescript +eventBus.on('team-selected', (data) => { + // Handle team change +}) +``` + +## Responsive Behavior + +| Breakpoint | Behavior | +|------------|----------| +| `< md` (768px) | Row 2 hidden, hamburger menu visible | +| `>= md` | Both rows visible, hamburger hidden | +| `>= lg` | User name shown next to avatar | + +## Styling + +Default navbar classes: + +```css +/* Container */ +.sticky.top-0.z-50.w-full.border-b.bg-white + +/* Row 1 */ +.h-14.border-b.border-border/40 + +/* Row 2 */ +.h-12.hidden.md:block + +/* Content max-width */ +.max-w-[1200px].mx-auto.px-4.sm:px-6.lg:px-8 +``` + +## Architecture Notes + +1. **DsNavbar.vue** fetches user and team data on mount +2. Sub-components are presentational and receive data via props +3. Events bubble up from sub-components to DsNavbar for handling +4. EventBus syncs team selection across the application +5. i18n keys follow the existing `sidebar.*` namespace for compatibility + +## Related Documentation + +- [UI Design System](/development/frontend/ui/) - Overall design patterns +- [Page Heading with Breadcrumbs](/development/frontend/ui/design-page-heading) - Breadcrumbs component for pages +- [Custom UI Components](/development/frontend/ui/custom-ui-components) - Component development guide diff --git a/development/frontend/ui/design-page-heading.mdx b/development/frontend/ui/design-page-heading.mdx new file mode 100644 index 0000000..86ee6f9 --- /dev/null +++ b/development/frontend/ui/design-page-heading.mdx @@ -0,0 +1,252 @@ +--- +title: Page Heading +description: Vercel-style page heading component with title, optional content, and action buttons. +--- + +The `DsPageHeading` component displays a page title with optional subtitle content and action buttons. It features a full-width bottom border that spans the entire viewport. + +## Component Location + +``` +services/frontend/src/components/ui/ds-page-heading/ + DsPageHeading.vue # Page heading component + index.ts # Component exports +``` + +## Props + +| Prop | Type | Required | Description | +|------|------|----------|-------------| +| `title` | `string` | Yes | Main page title | +| `description` | `string` | No | Short description below title | + +## Slots + +| Slot | Description | +|------|-------------| +| `default` | Optional subtitle content below title (text-sm, normal text color, gap-4 from title) | +| `actions` | Action buttons displayed on the right side | + +## Basic Usage + +```vue + + + +``` + +## With Description + +```vue + +``` + +## With Action Buttons + +Use the `#actions` slot to add buttons on the right side: + +```vue + + + +``` + +## With Custom Content (Default Slot) + +Use the default slot to add flexible content below the title. This can include text, Vue components, or any custom markup: + +```vue + + All deployments from lasims-projects-93a8104b + +``` + +### With Dynamic Content + +```vue + + + +``` + +### With Vue Components + +```vue + + Active + Last updated 5 minutes ago + +``` + +## With Breadcrumb Navigation + +When adding breadcrumb navigation to a page heading, you **MUST** use `RouterLink` with the `as-child` pattern. Never use the `href` attribute directly on `BreadcrumbLink` as it creates a regular `` element that causes full page reloads. + +```vue + + + +``` + +### Breadcrumb Rules + +| Do | Don't | +|----|-------| +| `` | `` | +| Use `RouterLink` for client-side navigation | Use `href` which causes full page reload | +| Use `to` prop on RouterLink | Use `href` prop on BreadcrumbLink | + +### With Breadcrumb and Actions + +```vue + + + + + + Satellites + + + + + Pairing + + + + + + +``` + +## Full Example + +Combining all features: + +```vue + + + +``` + +## Styling + +The component uses these key styles: + +- **Title**: 32px font size, medium weight, tight letter-spacing (`text-[32px] tracking-[-0.79px] font-medium`) +- **Description prop**: Muted color (`text-muted-foreground`), tight gap from title (`gap-1`) +- **Default slot**: Smaller text (`text-sm`), normal text color, spaced from title (`gap-4`) +- **Full-width border**: Uses CSS pseudo-element (`after:w-screen`) to extend beyond container +- **Responsive layout**: Stacks vertically on mobile, horizontal on `lg` breakpoint + +### Visual Hierarchy + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Title (32px, font-medium) [Actions] │ +│ Description (muted, gap-1 from title) │ +│ │ +│ Default slot content (text-sm, gap-4 from title section) │ +├─────────────────────────────────────────────────────────────┤ ← full-width border +``` + +## Related Documentation + +- [Navbar Component](/development/frontend/ui/design-navbar) - Top navigation bar +- [UI Design System](/development/frontend/ui/) - Overall design patterns diff --git a/development/frontend/ui/design-settings-menu.mdx b/development/frontend/ui/design-settings-menu.mdx index 31949e3..d361b78 100644 Binary files a/development/frontend/ui/design-settings-menu.mdx and b/development/frontend/ui/design-settings-menu.mdx differ diff --git a/development/frontend/ui/design-stepper.mdx b/development/frontend/ui/design-stepper.mdx new file mode 100644 index 0000000..0f3216d --- /dev/null +++ b/development/frontend/ui/design-stepper.mdx @@ -0,0 +1,148 @@ +--- +title: Stepper +description: A vertical step indicator component for multi-step wizards and forms. +--- + +A vertical navigation component that shows progress through a multi-step process. Each step displays an icon and label, with visual states for completed, current, and pending steps. + +## Component Location + +``` +services/frontend/src/components/ui/ds-stepper/ +- DsStepper.vue # Main stepper component +- index.ts # Exports +``` + +## Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `steps` | `WizardStep[]` | required | Array of step objects | +| `interactive` | `boolean` | `false` | Allow clicking completed steps to navigate back | + +### WizardStep Interface + +```typescript +interface WizardStep { + id: string + label: string + status: 'completed' | 'current' | 'pending' +} +``` + +## Events + +| Event | Payload | Description | +|-------|---------|-------------| +| `stepClick` | `[step: WizardStep, index: number]` | Emitted when a completed step is clicked (requires `interactive` prop) | + +## Usage + +Import the component and type from the ds-stepper index: + +```vue + + + +``` + +## Visual States + +Each step renders differently based on its status: + +| Status | Icon | Icon Color | Label Color | +|--------|------|------------|-------------| +| `completed` | CircleCheck | `text-green-600` | `text-muted-foreground` | +| `current` | CircleDashed | `text-primary` | `text-foreground` | +| `pending` | CircleDashed | `text-neutral-400` | `text-neutral-400` | + +## Interactive Mode + +When `interactive` is `true`, completed steps become clickable: + +- Hover state: `bg-neutral-100` +- Cursor changes to pointer +- Clicking emits `stepClick` event + +Only completed steps respond to clicks. Current and pending steps remain non-interactive regardless of the `interactive` prop. + +```vue + +``` + +## Layout + +The stepper is designed to sit in a flex container alongside step content: + +```vue + +``` + +The stepper has a fixed width of `w-56` (224px) and uses `shrink-0` to prevent compression. + +## Styling + +The component uses shadcn-vue design tokens: + +- **Container**: `w-56 shrink-0` +- **List spacing**: `space-y-1` +- **Item padding**: `px-3 py-2` +- **Icon size**: `h-5 w-5` +- **Label**: `text-sm font-medium` diff --git a/development/frontend/ui/design-system-pagination.mdx b/development/frontend/ui/design-system-pagination.mdx index 44bbf9b..858b46a 100644 --- a/development/frontend/ui/design-system-pagination.mdx +++ b/development/frontend/ui/design-system-pagination.mdx @@ -7,6 +7,25 @@ sidebarTitle: Pagination Guide This guide shows developers how to add pagination to any data table in the DeployStack frontend. +## Pagination Style + +The pagination follows the shadcn-vue DataTable pattern: + +**Without selection (default):** +``` + [Rows per page ▼] Page 1 of 7 [<<] [<] [>] [>>] +``` + +**With selection enabled:** +``` +0 of 68 row(s) selected. [Rows per page ▼] Page 1 of 7 [<<] [<] [>] [>>] +``` + +- **Selection info** (left, optional) - "X of Y row(s) selected." Only shown when selection props are provided +- **Rows per page selector** (right) - Dropdown with options: 10, 20, 30, 40, 50 (hidden on mobile) +- **Page indicator** (right) - "Page X of Y" +- **Navigation buttons** (right) - First, Previous, Next, Last (First/Last hidden on mobile) + ## Quick Implementation ### 1. Service Layer @@ -37,14 +56,14 @@ static async getItemsPaginated( pagination?: PaginationParams ): Promise> { const url = new URL(`${this.baseUrl}/api/items`) - + // Add filters and pagination params if (filters) { Object.entries(filters).forEach(([key, value]) => { if (value !== undefined) url.searchParams.append(key, String(value)) }) } - + if (pagination) { if (pagination.limit) url.searchParams.append('limit', String(pagination.limit)) if (pagination.offset) url.searchParams.append('offset', String(pagination.offset)) @@ -57,7 +76,7 @@ static async getItemsPaginated( }) const data = await response.json() - + return { items: data.data.items, pagination: data.data.pagination @@ -89,7 +108,7 @@ async function fetchItems() { filters.value, { limit: pageSize.value, offset } ) - + items.value = response.items totalItems.value = response.pagination.total } finally { @@ -116,7 +135,7 @@ onMounted(() => fetchItems())
- + - - - - -
- {{ t('yourFeature.pagination.showing', { - start: filteredItems.length > 0 ? 1 : 0, - end: filteredItems.length, - total: filteredItems.length - }) }} -
- + ``` +## shadcn-vue Components Used + +The `PaginationControls` component uses these shadcn-vue components: +- `Button` - For navigation buttons (icon-only, outline variant, `size-8`) +- `Label` - For "Rows per page" label +- `Select`, `SelectContent`, `SelectItem`, `SelectTrigger`, `SelectValue` - For page size selector (`w-20`) +- Lucide icons: `ChevronLeft`, `ChevronRight`, `ChevronsLeft`, `ChevronsRight` + +## Responsive Behavior + +The pagination is responsive: + +| Element | Desktop | Mobile | +|---------|---------|--------| +| Selection info (if enabled) | Visible | Visible | +| Rows per page selector | Visible | Hidden | +| Page info | Visible | Visible | +| First page button | Visible | Hidden | +| Previous page button | Visible | Visible | +| Next page button | Visible | Visible | +| Last page button | Visible | Hidden | + +On mobile, the navigation buttons use `ml-auto` to push them to the right edge. + ## Backend Requirements Your backend API must support these query parameters: diff --git a/development/frontend/ui/design-system-table.mdx b/development/frontend/ui/design-system-table.mdx index 981fb8f..9f24146 100644 --- a/development/frontend/ui/design-system-table.mdx +++ b/development/frontend/ui/design-system-table.mdx @@ -24,7 +24,7 @@ import { } from '@/components/ui/table' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' -import { MoreHorizontal, Edit, Trash2 } from 'lucide-vue-next' +import { MoreVertical, Edit, Trash2 } from 'lucide-vue-next' const { t } = useI18n() @@ -62,7 +62,7 @@ const formatDate = (dateString: string) => { {{ t('table.columns.description') }} {{ t('table.columns.status') }} {{ t('table.columns.created') }} - {{ t('table.columns.actions') }} + @@ -139,7 +139,7 @@ import { Column Name Another Column - Actions + ``` @@ -193,10 +193,23 @@ import { ## Action Menu Pattern -For table actions, use DropdownMenu with AlertDialog for destructive actions: +For table actions, use DropdownMenu with AlertDialog for destructive actions. + +### Rules + +| Rule | Do | Don't | +|------|-----|-------| +| Action icon | `MoreVertical` | `MoreHorizontal` | +| Actions header | Empty `` | "Actions" text | +| Column width | `w-[50px]` | `w-[100px]` or wider | +| Dropdown alignment | `align="end"` | `align="start"` | +| Destructive actions | `text-destructive` class | Default styling | + +### Implementation ```vue + + +``` + +Use Skeleton for: +- Page content loading +- Data tables and lists +- Cards and detail views +- Form sections loading from API +- Any content where you can predict the layout + +### Button Actions → Use Spinner + +For button clicks and quick actions, use the built-in button loading state with a spinner. This indicates the action is processing without disrupting the page layout. + +```vue + +``` + +Use Spinner for: +- Form submissions +- Button click actions +- Quick API calls (enable/disable toggles) +- Actions that don't change page structure + +### Why Skeleton Over Spinner for Content + +| Aspect | Skeleton | Spinner | +|--------|----------|---------| +| User expectation | Shows content shape | Shows "something is happening" | +| Layout shift | Minimal (matches content) | Can cause layout shift | +| Perceived speed | Feels faster | Can feel slower | +| Use case | Content loading | Action processing | + ## Button Patterns ### Loading States diff --git a/development/satellite/backend-communication.mdx b/development/satellite/backend-communication.mdx index 90577ed..1f09bcf 100644 --- a/development/satellite/backend-communication.mdx +++ b/development/satellite/backend-communication.mdx @@ -87,6 +87,7 @@ For detailed event system documentation, see [Event System](/development/satelli - Process status for all running MCP servers - Network information and connectivity status - Performance metrics and error counts +- Satellite URL (first heartbeat only) - Updates backend with satellite's public URL **Command Result Reporting:** - Execution status and timing @@ -249,6 +250,7 @@ PORT=3001 # Optional configuration NODE_ENV=development +DEPLOYSTACK_SATELLITE_URL=https://satellite.example.com # Optional - auto-detected if not set ``` **Note:** `DEPLOYSTACK_REGISTRATION_TOKEN` is only required for initial satellite pairing. Once registered, satellites use their permanent API keys for all communication. diff --git a/development/satellite/registration.mdx b/development/satellite/registration.mdx index ea48907..68b5022 100644 --- a/development/satellite/registration.mdx +++ b/development/satellite/registration.mdx @@ -128,8 +128,47 @@ DEPLOYSTACK_BACKEND_URL=http://localhost:3000 # Registration token (required for secure pairing) DEPLOYSTACK_REGISTRATION_TOKEN=deploystack_satellite_global_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... + +# Optional satellite URL configuration +DEPLOYSTACK_SATELLITE_URL=https://satellite.example.com ``` +### Satellite URL Configuration (Optional) + +**DEPLOYSTACK_SATELLITE_URL Environment Variable:** + +This optional environment variable configures the publicly accessible URL of the satellite instance. + +**Behavior:** +- **If set**: Satellite sends explicit URL to backend during registration and first heartbeat +- **If not set**: Backend auto-detects URL from incoming request headers (X-Forwarded-Proto, X-Forwarded-Host) + +**When to Use:** +- Production deployments with known public URLs +- OAuth 2.0 Protected Resource Metadata requirements +- Satellites behind reverse proxies or load balancers + +**Format:** +- Base URL without path suffix +- Example: `https://satellite.example.com` (NOT `https://satellite.example.com/mcp`) + +**Examples:** +```bash +# Production global satellite +DEPLOYSTACK_SATELLITE_URL=https://satellite.deploystack.io + +# Production team satellite +DEPLOYSTACK_SATELLITE_URL=https://satellite-team1.example.com + +# Development (auto-detection) +# DEPLOYSTACK_SATELLITE_URL= # Leave blank or comment out +``` + +**URL Update Behavior:** +- Registration: URL sent during initial satellite registration +- Startup: URL updated on first heartbeat after satellite restart +- Runtime: URL remains static until satellite restarts + ## Registration Token Requirements ### Token Types and Sources @@ -219,6 +258,7 @@ Satellites automatically collect and send system information during registration interface SatelliteRegistrationData { name: string; // From DEPLOYSTACK_SATELLITE_NAME capabilities: string[]; // ['stdio', 'http', 'sse'] + satellite_url?: string; // From DEPLOYSTACK_SATELLITE_URL (optional) system_info: { os: string; // e.g., "darwin arm64" arch: string; // e.g., "arm64" diff --git a/docs.json b/docs.json index 35f5fac..39bcca2 100644 --- a/docs.json +++ b/docs.json @@ -87,7 +87,8 @@ "/development/frontend/internationalization", "/development/frontend/plugins", "/development/frontend/router-optimization", - "/development/frontend/storage" + "/development/frontend/storage", + "/development/frontend/api-sse" ] }, { @@ -103,7 +104,11 @@ "/development/frontend/ui/design-system-table", "/development/frontend/ui/custom-ui-components", "/development/frontend/ui/siteHeader-with-breadcrumbs", - "/development/frontend/ui/design-settings-menu" + "/development/frontend/ui/design-settings-menu", + "/development/frontend/ui/design-stepper", + "/development/frontend/ui/design-page-heading", + "/development/frontend/ui/design-navbar", + "/development/frontend/ui/design-card" ] } ] @@ -140,7 +145,8 @@ "pages": [ "/development/backend/api/index", "/development/backend/api/security", - "/development/backend/api/pagination" + "/development/backend/api/pagination", + "/development/backend/api/sse" ] }, { @@ -163,7 +169,8 @@ "group": "Satellite", "pages": [ "/development/backend/satellite/communication", - "/development/backend/satellite/events" + "/development/backend/satellite/events", + "/development/backend/satellite/commands" ] } ] diff --git a/self-hosted/docker-compose.mdx b/self-hosted/docker-compose.mdx index 7cdbf34..a6d6ead 100644 --- a/self-hosted/docker-compose.mdx +++ b/self-hosted/docker-compose.mdx @@ -163,6 +163,10 @@ The satellite must be deployed separately after completing the setup wizard: The satellite requires a running backend. If the backend is not reachable, the satellite will exit immediately. Make sure `docker-compose ps` shows the backend as healthy before proceeding. + + **Automatic Permission Handling**: The satellite container automatically fixes Docker volume permissions on startup. This ensures credentials can be saved even when volumes have root ownership. You may notice a brief delay (~5 seconds) during first startup while permissions are being fixed. + + **For local development (connecting from same machine):** ```bash docker run -d \ @@ -175,7 +179,7 @@ The satellite must be deployed separately after completing the setup wizard: deploystack/satellite:latest ``` - Replace `` with the network from step 3 (e.g., `docker-compose_deploystack-network`). + Replace `` with the network from step 3. **For remote access (connecting from MCP clients via domain/IP):** ```bash diff --git a/self-hosted/quick-start.mdx b/self-hosted/quick-start.mdx index 928e97c..25ee219 100644 --- a/self-hosted/quick-start.mdx +++ b/self-hosted/quick-start.mdx @@ -228,6 +228,10 @@ After completing the basic backend and frontend setup, deploy at least one satel The satellite requires a running backend. If the backend is not reachable, the satellite will exit immediately. Make sure the backend is running before proceeding. + + **Automatic Permission Handling**: The satellite container automatically fixes Docker volume permissions on startup. This ensures credentials can be saved even when volumes have root ownership. You may notice a brief delay (~5 seconds) during first startup while permissions are being fixed. + + **For local development (connecting from same machine):** ```bash docker run \