Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Thank you for your interest in contributing to Spacepad! This document provides
### Coding Standards

- Follow the [PSR-12](https://www.php-fig.org/psr/psr-12/) coding style guide for PHP
- Always use import statements instead of inline fully qualified class names - See [Coding Standards](backend/docs/CODING_STANDARDS.md) for details
- Use ESLint and Prettier for JavaScript/TypeScript
- Write meaningful commit messages
- Add comments for complex logic
Expand Down
219 changes: 219 additions & 0 deletions backend/FARO_SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
# Grafana Faro Frontend Monitoring Setup

This document explains how to configure Grafana Faro Real User Monitoring (RUM) for the Spacepad frontend.

## Overview

Grafana Faro collects frontend telemetry data including:
- **Web Vitals** (LCP, FID, CLS, FCP, TTFB)
- **Page load timing** (DOMContentLoaded, Load, etc.)
- **User interactions** (clicks, form submissions)
- **All fetch/XHR requests** with full trace context
- **Frontend → Backend trace correlation**
- **Long tasks** (performance monitoring)
- **Errors and exceptions** (sent to Loki)
- **Console logs** (errors/warnings sent to Loki)
- **Session tracking**

## Configuration

### 1. Enable Faro

Add to your `.env` file:

```env
FARO_ENABLED=true
FARO_COLLECTOR_URL=http://localhost:12347/collect
FARO_API_KEY=faro-secret-key
FARO_APP_NAME=spacepad
FARO_APP_VERSION=1.0.0
FARO_APP_ENV=local
```

### 2. Grafana Alloy Configuration

Ensure Grafana Alloy is running with a FARO receiver configured. The receiver should:
- Listen on port `12347` at `/collect` endpoint
- Use the same `api_key` as configured in `FARO_API_KEY`
- Forward logs to Loki
- Forward traces to Tempo
- Forward metrics to Prometheus

Example Alloy configuration:

```river
faro.receiver "faro_receiver" {
server {
listen_address = "0.0.0.0"
listen_port = 12347
cors_allowed_origins = ["*"] // Allow all origins for development
api_key = "faro-secret-key" // Must match FARO_API_KEY
max_allowed_payload_size = "10MiB"

rate_limiting {
rate = 100
}
}

sourcemaps { }

output {
logs = [loki.process.faro_logs.receiver]
traces = [otelcol.processor.batch.batch_processor.input]
}
}
```

### 3. Docker Environment

If running in Docker, use `host.docker.internal` to reach Grafana Alloy on the host:

```env
FARO_COLLECTOR_URL=http://host.docker.internal:12347/collect
```

## How It Works

1. **Frontend Application** → Sends RUM data via FARO SDK → **Grafana Alloy** (port 12347 `/collect`)
2. **Grafana Alloy** → Processes FARO data → Forwards to:
- **Prometheus** (metrics)
- **Loki** (logs)
- **Tempo** (traces)

## Features

The Faro integration automatically captures:

- ✅ **Web Vitals** (LCP, FID, CLS, FCP, TTFB)
- ✅ **Page load timing** (DOMContentLoaded, Load, etc.)
- ✅ **User interactions** (clicks, form submissions)
- ✅ **All fetch/XHR requests** with full trace context
- ✅ **Frontend → Backend trace correlation**
- ✅ **Long tasks** (performance monitoring)
- ✅ **Errors and exceptions** (sent to Loki)
- ✅ **Console logs** (errors/warnings sent to Loki)
- ✅ **Session tracking**

## Viewing Data

### Grafana Dashboard

Import the Grafana Faro Frontend Monitoring dashboard (ID: `17766`):

1. Open Grafana at http://localhost:3000
2. Go to Dashboards → Import
3. Enter dashboard ID: `17766`
4. Select Prometheus as the datasource
5. Click "Import"

### Prometheus Queries

Query FARO metrics in Prometheus:

```promql
# Frontend errors
faro_errors_total

# Page load metrics
faro_page_load_duration_seconds

# Web Vitals
faro_web_vitals_lcp_seconds
faro_web_vitals_fid_seconds
faro_web_vitals_cls
```

### Loki Logs

Search for frontend logs in Loki:

```logql
{service_name="spacepad"} |= "error"
```

### Tempo Traces

View frontend traces in Tempo:
- Search for traces from `spacepad` service
- Filter by route or operation
- View trace details and spans

## Troubleshooting

### Faro Not Initializing

1. **Check browser console** for initialization errors
2. **Verify configuration** in `.env` file
3. **Check network tab** for requests to `/collect` endpoint
4. **Verify CORS** settings in Grafana Alloy config

### No Data in Grafana

1. **Check Grafana Alloy logs:**
```bash
docker logs grafana-alloy
```

2. **Verify API key matches:**
- `FARO_API_KEY` in `.env` must match `api_key` in Alloy config

3. **Check Prometheus targets:**
- Visit http://localhost:9090/targets
- Verify `grafana-alloy` target is UP

4. **Verify CORS:**
- Ensure `cors_allowed_origins` in Alloy config includes your frontend origin

### CORS Errors

If you see CORS errors in the browser console:

1. Add your frontend origin to `cors_allowed_origins` in Alloy config
2. For development: `cors_allowed_origins = ["*"]`
3. For production: `cors_allowed_origins = ["https://yourdomain.com"]`

## Security

**Important:** The default API key `faro-secret-key` is for development only. In production:

1. Generate a secure random API key
2. Update `FARO_API_KEY` in `.env`
3. Update `api_key` in Grafana Alloy config
4. Consider using environment variables or secrets management

## Advanced Configuration

### Custom Instrumentations

To add custom instrumentations, modify `resources/views/components/scripts/faro.blade.php`:

```javascript
import { TracingInstrumentation } from '@grafana/faro-web-tracing';

const faroInstance = initializeFaro({
// ... existing config
instrumentations: [
...getWebInstrumentations(),
new TracingInstrumentation(),
],
});
```

### Disable Specific Features

You can disable specific features via environment variables:

```env
FARO_PERFORMANCE_ENABLED=false
FARO_ERRORS_ENABLED=false
FARO_CONSOLE_ENABLED=false
FARO_INTERACTIONS_ENABLED=false
FARO_SESSION_TRACKING=false
```

## References

- [Grafana Faro Documentation](https://github.com/grafana/faro-web-sdk)
- [Grafana Faro Quick Start](https://github.com/grafana/faro-web-sdk/blob/main/docs/sources/tutorials/quick-start-browser.md)
- [Grafana Alloy FARO Receiver](https://grafana.com/docs/alloy/latest/reference/components/faro.receiver/)

111 changes: 111 additions & 0 deletions backend/WORKSPACE_SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Workspace System Documentation

## Overview

The workspace system allows multiple users to collaborate on managing displays, devices, calendars, and rooms. Each user automatically gets their own workspace, and Pro users can invite colleagues to join their workspace.

## Architecture

### Models

1. **Workspace** - Represents a team/workspace
- Has an `owner` (User)
- Has many `members` (Users with roles)
- Contains displays, devices, calendars, rooms

2. **WorkspaceMember** - Pivot table linking users to workspaces
- Roles: `owner`, `admin`, `member`
- `owner` role is implicit for the workspace owner

### Relationships

- **User** → **Workspace** (one-to-many: owned workspaces)
- **User** ↔ **Workspace** (many-to-many: member workspaces)
- **Workspace** → **Display** (one-to-many)
- **Workspace** → **Device** (one-to-many)
- **Workspace** → **Calendar** (one-to-many)
- **Workspace** → **Room** (one-to-many)

## Migration Strategy

1. **Existing Users**: Each user automatically gets a workspace created with their name
2. **Existing Data**: All displays, devices, calendars, and rooms are migrated to the user's workspace
3. **Backward Compatibility**: The `user_id` field is kept for backward compatibility

## Permissions

### Workspace Roles

- **Owner**: Full control (can delete workspace, manage all members)
- **Admin**: Can manage members and workspace settings
- **Member**: Can view and use workspace resources

### Display Access

- Users can access displays they own directly (`user_id`)
- Users can access displays in workspaces they're members of (`workspace_id`)
- Device authentication checks workspace membership

## Usage

### Adding a Colleague

1. Navigate to workspace settings (requires Pro)
2. Enter colleague's email address
3. Select role (admin or member)
4. Colleague receives access to all workspace resources

### Managing Members

- **Add Member**: Only owners/admins can add members
- **Update Role**: Change member role between admin/member
- **Remove Member**: Remove access from workspace

## API Changes

### DisplayController

- `index()` now returns displays from user's workspace(s)
- Access checks include workspace membership

### DisplayService

- `validateDisplayPermission()` checks workspace membership
- Pro features check workspace owner's Pro status

## Frontend Changes Needed

1. **Workspace Management UI**
- List workspaces
- View workspace members
- Add/remove members
- Update member roles

2. **Display Creation**
- Automatically assign to user's primary workspace
- Allow selecting workspace (if user has multiple)

3. **Device Connection**
- Connect code should work with workspace
- Devices inherit workspace from user

## Migration Commands

Run migrations in order:

```bash
php artisan migrate
```

The migration `2025_12_30_000003_create_workspaces_for_existing_users.php` will:
1. Create a workspace for each existing user
2. Migrate all user's displays, devices, calendars, and rooms to their workspace
3. Add the user as an owner member

## Notes

- Pro subscription is required to add team members
- Workspace owner cannot be removed
- All existing functionality remains backward compatible
- `user_id` fields are kept for direct ownership tracking

Loading
Loading