mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-08-08 09:42:55 +02: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
|
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
|
# Set working directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|||||||
@@ -359,6 +359,52 @@ docker compose up -d --build
|
|||||||
docker compose ps
|
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
|
## Troubleshooting
|
||||||
|
|
||||||
### Device not found
|
### Device not found
|
||||||
|
|||||||
@@ -496,6 +496,139 @@ def execute_cli():
|
|||||||
}), 500
|
}), 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__':
|
if __name__ == '__main__':
|
||||||
logger.info(f"Starting MeshCore Bridge on port 5001")
|
logger.info(f"Starting MeshCore Bridge on port 5001")
|
||||||
logger.info(f"Serial port: {MC_SERIAL_PORT}")
|
logger.info(f"Serial port: {MC_SERIAL_PORT}")
|
||||||
|
|||||||
Reference in New Issue
Block a user