mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-03-28 17:42:45 +01:00
feat(bridge): Add pending contacts management API endpoints
Added two new HTTP endpoints to meshcore-bridge for managing pending contacts
(contacts awaiting manual approval when manual_add_contacts mode is enabled):
New endpoints:
- GET /pending_contacts - List all pending contacts awaiting approval
- Parses meshcli output format: "Name: <hex_public_key>"
- Returns JSON array with {name, public_key} objects
- Includes raw_stdout for debugging
- POST /add_pending - Approve and add a pending contact
- Accepts JSON body: {"selector": "<name_or_pubkey>"}
- Validates selector is non-empty string
- Executes meshcli add_pending command via persistent session
Additional changes:
- Added curl to mc-webui Dockerfile for testing endpoints
- Updated README with Testing Bridge API section
- Included example curl commands and expected responses
Implementation notes:
- Uses existing MeshCLISession.execute_command() - no new processes
- Same persistent session and command queue architecture
- Consistent error handling with existing /cli endpoint
Enables future UI for manual contact approval workflow.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,9 @@
|
||||
|
||||
FROM python:3.11-slim
|
||||
|
||||
# Install curl for testing
|
||||
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
46
README.md
46
README.md
@@ -359,6 +359,52 @@ docker compose up -d --build
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
## Testing Bridge API
|
||||
|
||||
The `meshcore-bridge` container exposes HTTP endpoints for pending contact management.
|
||||
|
||||
### Test Pending Contacts Endpoints
|
||||
|
||||
```bash
|
||||
# List pending contacts (from inside mc-webui container or server)
|
||||
curl -s http://meshcore-bridge:5001/pending_contacts | jq
|
||||
|
||||
# Add a pending contact
|
||||
curl -s -X POST http://meshcore-bridge:5001/add_pending \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"selector":"Skyllancer"}' | jq
|
||||
|
||||
# Example response for GET /pending_contacts:
|
||||
# {
|
||||
# "success": true,
|
||||
# "pending": [
|
||||
# {
|
||||
# "name": "Skyllancer",
|
||||
# "public_key": "f9ef..."
|
||||
# },
|
||||
# {
|
||||
# "name": "KRA Reksio mob2🐕",
|
||||
# "public_key": "41d5..."
|
||||
# }
|
||||
# ],
|
||||
# "raw_stdout": "Skyllancer: f9ef...\nKRA Reksio mob2🐕: 41d5..."
|
||||
# }
|
||||
|
||||
# Example response for POST /add_pending:
|
||||
# {
|
||||
# "success": true,
|
||||
# "stdout": "Contact added successfully",
|
||||
# "stderr": "",
|
||||
# "returncode": 0
|
||||
# }
|
||||
```
|
||||
|
||||
**Note:** These endpoints require `manual_add_contacts` mode to be enabled in meshcli:
|
||||
```bash
|
||||
# Enable manual contact approval (run in meshcli interactive mode)
|
||||
set manual_add_contacts on
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Device not found
|
||||
|
||||
@@ -496,6 +496,139 @@ def execute_cli():
|
||||
}), 500
|
||||
|
||||
|
||||
@app.route('/pending_contacts', methods=['GET'])
|
||||
def get_pending_contacts():
|
||||
"""
|
||||
Get list of pending contacts awaiting approval.
|
||||
|
||||
Response JSON:
|
||||
{
|
||||
"success": true,
|
||||
"pending": [
|
||||
{"name": "Skyllancer", "public_key": "f9ef..."},
|
||||
{"name": "KRA Reksio mob2🐕", "public_key": "41d5..."}
|
||||
],
|
||||
"raw_stdout": "..."
|
||||
}
|
||||
"""
|
||||
try:
|
||||
# Check session health
|
||||
if not meshcli_session or not meshcli_session.process:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'meshcli session not initialized',
|
||||
'pending': []
|
||||
}), 503
|
||||
|
||||
# Execute pending_contacts command
|
||||
result = meshcli_session.execute_command(['pending_contacts'], timeout=DEFAULT_TIMEOUT)
|
||||
|
||||
if not result['success']:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': result.get('stderr', 'Command failed'),
|
||||
'pending': [],
|
||||
'raw_stdout': result.get('stdout', '')
|
||||
}), 200
|
||||
|
||||
# Parse stdout
|
||||
stdout = result.get('stdout', '').strip()
|
||||
pending = []
|
||||
|
||||
if stdout:
|
||||
for line in stdout.split('\n'):
|
||||
line = line.strip()
|
||||
# Parse lines with format: "Name: <hex_public_key>"
|
||||
if ':' in line:
|
||||
parts = line.split(':', 1)
|
||||
if len(parts) == 2:
|
||||
name = parts[0].strip()
|
||||
public_key = parts[1].strip().replace(' ', '') # Remove spaces from hex
|
||||
if name and public_key:
|
||||
pending.append({
|
||||
'name': name,
|
||||
'public_key': public_key
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'pending': pending,
|
||||
'raw_stdout': stdout
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"API error in /pending_contacts: {e}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': str(e),
|
||||
'pending': []
|
||||
}), 500
|
||||
|
||||
|
||||
@app.route('/add_pending', methods=['POST'])
|
||||
def add_pending_contact():
|
||||
"""
|
||||
Add a pending contact by name or public key.
|
||||
|
||||
Request JSON:
|
||||
{
|
||||
"selector": "<name_or_pubkey_prefix_or_pubkey>"
|
||||
}
|
||||
|
||||
Response JSON:
|
||||
{
|
||||
"success": true,
|
||||
"stdout": "...",
|
||||
"stderr": "",
|
||||
"returncode": 0
|
||||
}
|
||||
"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
|
||||
if not data or 'selector' not in data:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'stdout': '',
|
||||
'stderr': 'Missing required field: selector',
|
||||
'returncode': -1
|
||||
}), 400
|
||||
|
||||
selector = data['selector']
|
||||
|
||||
# Validate selector is non-empty string
|
||||
if not isinstance(selector, str) or not selector.strip():
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'stdout': '',
|
||||
'stderr': 'selector must be a non-empty string',
|
||||
'returncode': -1
|
||||
}), 400
|
||||
|
||||
# Check session health
|
||||
if not meshcli_session or not meshcli_session.process:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'stdout': '',
|
||||
'stderr': 'meshcli session not initialized',
|
||||
'returncode': -1
|
||||
}), 503
|
||||
|
||||
# Execute add_pending command
|
||||
result = meshcli_session.execute_command(['add_pending', selector.strip()], timeout=DEFAULT_TIMEOUT)
|
||||
|
||||
return jsonify(result), 200
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"API error in /add_pending: {e}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'stdout': '',
|
||||
'stderr': str(e),
|
||||
'returncode': -1
|
||||
}), 500
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger.info(f"Starting MeshCore Bridge on port 5001")
|
||||
logger.info(f"Serial port: {MC_SERIAL_PORT}")
|
||||
|
||||
Reference in New Issue
Block a user