mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-07-05 17:31:39 +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:
@@ -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