Skip to content
Merged
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
225 changes: 225 additions & 0 deletions CONFIG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
# Configuration Management

This document explains how to use the configuration management system in Commad.

## Overview

The configuration system provides:
- Global configuration store using React Context
- Persistent storage in localStorage
- Browser console tools for easy configuration management
- Validation for configuration values
- Real-time updates across the application

## Available Configuration Options

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `couchdbUrl` | string | `/db` | URL for CouchDB server (use `/db` for Vite proxy) |
| `couchdbUsername` | string | `""` | CouchDB username (optional) |
| `couchdbPassword` | string | `""` | CouchDB password (optional) |
| `syncEnabled` | boolean | `false` | Enable/disable synchronization |
| `syncInterval` | number | `30000` | Sync interval in milliseconds |
| `appName` | string | `commad` | Application name |
| `theme` | string | `light` | UI theme preference |

## Using Console Tools

The configuration can be managed through the browser console using the `commad` global object.

### Configuration Commands

```javascript
// Show help
commad.help()
commad.config.help()

// Quick setup (recommended for 401 errors)
commad.config.setup("http://localhost:5984", "username", "password")

// Manual configuration
// Set CouchDB URL with validation
commad.config.setCouchDB('/db') // Use Vite proxy (recommended)
// OR
commad.config.setCouchDB('http://localhost:5984') // Direct connection
commad.config.setAuth('username', 'password')

// View current configuration
commad.config.getAll()

// Get a specific config value
commad.config.get('couchdbUrl')

// Set a configuration value
commad.config.set('syncEnabled', true)

// Clear authentication
commad.config.clearAuth()
```

### Sync Management Commands

```javascript
// Show sync help
commad.sync.help()

// Check sync status
commad.sync.status()
commad.sync.info()

// Manual sync operations
commad.sync.force() // Force a full sync
commad.sync.push() // Push local changes to remote
commad.sync.pull() // Pull changes from remote

// Connection management
commad.sync.start() // Start continuous sync
commad.sync.stop() // Stop sync
commad.sync.reconnect() // Reconnect to remote

// Conflict resolution
commad.sync.conflicts() // List documents with conflicts
commad.sync.resolve(docId, winningRev, losingRevs) // Resolve conflict
```

### Advanced Commands

```javascript
// Export configuration as JSON
commad.config.export()

// Import configuration from JSON
commad.config.import('{"couchdbUrl": "http://example.com:5984", "syncEnabled": true}')

// Reset to default configuration
commad.config.reset()

// Test CouchDB connection
commad.utils.testCouchDB()

// Clear all application data
commad.utils.clearData()
```

## Using in React Components

```jsx
import { useConfig } from '../contexts/ConfigContext';

function MyComponent() {
const { config, setConfig, setCouchDBUrl } = useConfig();

// Access config values
const couchdbUrl = config.couchdbUrl;

// Update configuration
const handleUrlChange = (url) => {
setCouchDBUrl(url);
};

return (
<div>
<p>Current CouchDB URL: {couchdbUrl}</p>
<button onClick={() => handleUrlChange('http://new-url:5984')}>
Update URL
</button>
</div>
);
}
```

## Using ConfigService Directly

```javascript
import configManager from '../services/ConfigService';

// Get configuration
const url = configManager.get('couchdbUrl');

// Set configuration
configManager.set('syncEnabled', true);

// Listen for changes
configManager.addListener((newConfig) => {
console.log('Config changed:', newConfig);
});
```

## Proxy Setup (Recommended)

The application uses Vite's built-in proxy to avoid CORS issues during development. The proxy is configured in `vite.config.js`:

```javascript
server: {
proxy: {
'/db': {
target: process.env.COUCHDB_URL || 'http://localhost:5984',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/db/, '')
}
}
}
```

### Using the Proxy

1. **Default setup**: The application is pre-configured to use `/db` as the CouchDB URL
2. **Custom CouchDB target**: Set the `COUCHDB_URL` environment variable
3. **No CORS configuration needed**: The proxy handles all cross-origin requests

### Environment Variables

Create a `.env` file in the project root:

```bash
# Optional: Custom CouchDB URL for proxy target
COUCHDB_URL=http://localhost:5984
# or for GitHub Codespaces
COUCHDB_URL=https://your-codespace-5984.app.github.dev
```

## Getting Started

1. Open the browser developer console
2. Type `commad.help()` to see available commands
3. **Quick setup with proxy** (recommended): `commad.config.setup("/db", "username", "password")`
4. **Or set URL manually**: `commad.config.setCouchDB("/db")` (for proxy) or `commad.config.setCouchDB("http://your-couchdb-url:5984")` (direct)
5. **If you get a 401 error**: Set authentication: `commad.config.setAuth("username", "password")`
6. Enable sync: `commad.sync.start()`
7. Check sync status: `commad.sync.status()`
8. View the configuration and sync panels in the UI

### Troubleshooting 401 Errors

If you encounter 401 Unauthorized errors:

1. **Check if CouchDB requires authentication**:
```javascript
commad.utils.testCouchDB()
```

2. **Set your credentials**:
```javascript
commad.config.setAuth("your-username", "your-password")
```

3. **Test the connection again**:
```javascript
commad.utils.testCouchDB()
```

4. **Enable sync**:
```javascript
commad.sync.start()
```

The configuration is automatically saved to localStorage and will persist between browser sessions. Sync runs continuously in the background when enabled.

## Sync Features

- **Continuous sync**: Real-time bidirectional synchronization with CouchDB
- **Offline support**: Works offline and syncs when connection is restored
- **Conflict resolution**: Automatic conflict detection with manual resolution tools
- **Manual sync**: Force sync, push-only, or pull-only operations
- **Status monitoring**: Real-time sync status in the UI and console
- **Error handling**: Comprehensive error reporting and recovery
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"homepage": "https://sofadb.github.io/commad",
"scripts": {
"dev": "vite",
"proxy": "node proxy-server.js",
"dev:proxy": "concurrently \"npm run proxy\" \"npm run dev\"",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview",
Expand Down Expand Up @@ -36,11 +38,15 @@
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.6.0",
"concurrently": "^8.2.2",
"cors": "^2.8.5",
"eslint": "^9.30.1",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"express": "^4.18.2",
"gh-pages": "^6.3.0",
"globals": "^16.3.0",
"http-proxy-middleware": "^2.0.6",
"vite": "^7.0.4"
}
}
85 changes: 85 additions & 0 deletions proxy-server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* Development proxy server for CouchDB
* This proxy helps avoid CORS issues during development
*/
import { createProxyMiddleware } from 'http-proxy-middleware';
import express from 'express';
import cors from 'cors';

const app = express();
const PORT = process.env.PROXY_PORT || 3001;
const COUCHDB_URL = process.env.COUCHDB_URL || 'http://localhost:5984';

// Enable CORS for all routes
app.use(cors({
origin: true,
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'Accept', 'Origin', 'X-Requested-With']
}));

// Proxy middleware configuration
const proxyOptions = {
target: COUCHDB_URL,
changeOrigin: true,
logLevel: 'debug',
onProxyReq: (proxyReq, req, res) => {
console.log(`[PROXY] ${req.method} ${req.url} -> ${COUCHDB_URL}${req.url}`);
},
onProxyRes: (proxyRes, req, res) => {
// Ensure CORS headers are set
proxyRes.headers['Access-Control-Allow-Origin'] = req.headers.origin || '*';
proxyRes.headers['Access-Control-Allow-Credentials'] = 'true';
proxyRes.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, HEAD, OPTIONS';
proxyRes.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, Accept, Origin, X-Requested-With';
},
onError: (err, req, res) => {
console.error('[PROXY ERROR]', err.message);
res.status(500).json({
error: 'Proxy Error',
message: err.message,
target: COUCHDB_URL
});
}
};

// Create proxy middleware
const proxy = createProxyMiddleware(proxyOptions);

// Health check endpoint
app.get('/health', (req, res) => {
res.json({
status: 'ok',
proxy: 'CouchDB Development Proxy',
target: COUCHDB_URL,
timestamp: new Date().toISOString()
});
});

// Proxy all other requests to CouchDB
app.use('/', proxy);

// Start the proxy server
app.listen(PORT, () => {
console.log(`
🚀 CouchDB Development Proxy Server
===================================
Proxy URL: http://localhost:${PORT}
Target: ${COUCHDB_URL}
Health Check: http://localhost:${PORT}/health

Use this proxy URL in your application configuration:
commad.config.setCouchDB("http://localhost:${PORT}")
`);
});

// Handle graceful shutdown
process.on('SIGTERM', () => {
console.log('Shutting down proxy server...');
process.exit(0);
});

process.on('SIGINT', () => {
console.log('Shutting down proxy server...');
process.exit(0);
});
11 changes: 8 additions & 3 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { useState } from 'react'
import './App.css'
import CodeEditor from './components/CodeEditor'
import React, { useState } from 'react';
import './App.css';
import CodeEditor from './components/CodeEditor';
import CommandPalette from './components/CommandPalette';
import ConfigDisplay from './components/ConfigDisplay';
import SyncStatus from './components/SyncStatus';

function App() {
const [code, setCode] = useState(`# Markdown Editor
Expand Down Expand Up @@ -69,6 +72,8 @@ Just pure markdown

return (
<div className="editor-container">
<ConfigDisplay />
<SyncStatus />
<CodeEditor
initialValue={code}
onChange={handleCodeChange}
Expand Down
Loading
Loading