feat: fully editable Settings page with service configs, backup/restore, export/import
- Rewrote Settings.jsx: Cell Identity editor, per-service config sections (network, wireguard, email, calendar, files, routing, vault) with collapsible cards, appropriate input types, and per-section Save buttons - Added Backup & Restore panel with create/restore/delete actions - Added Export (download JSON) and Import (upload JSON) panel - Added PUT /api/config identity field persistence (_identity key in cell_config.json) so cell_name/domain/ip_range/wireguard_port survive restarts - GET /api/config now returns service_configs separately and prefers stored identity - Added DELETE /api/config/backups/<id> endpoint - Extended cellAPI in api.js with createBackup, listBackups, restoreBackup, deleteBackup, exportConfig, importConfig Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+31
-9
@@ -373,13 +373,14 @@ def get_config():
|
||||
"""Get cell configuration."""
|
||||
try:
|
||||
service_configs = config_manager.get_all_configs()
|
||||
identity = service_configs.pop('_identity', {})
|
||||
config = {
|
||||
'cell_name': os.environ.get('CELL_NAME', 'mycell'),
|
||||
'domain': os.environ.get('CELL_DOMAIN', 'cell'),
|
||||
'ip_range': os.environ.get('CELL_IP_RANGE', '172.20.0.0/16'),
|
||||
'wireguard_port': int(os.environ.get('WG_PORT', '51820')),
|
||||
'cell_name': identity.get('cell_name', os.environ.get('CELL_NAME', 'mycell')),
|
||||
'domain': identity.get('domain', os.environ.get('CELL_DOMAIN', 'cell')),
|
||||
'ip_range': identity.get('ip_range', os.environ.get('CELL_IP_RANGE', '172.20.0.0/16')),
|
||||
'wireguard_port': identity.get('wireguard_port', int(os.environ.get('WG_PORT', '51820'))),
|
||||
}
|
||||
config.update(service_configs)
|
||||
config['service_configs'] = service_configs
|
||||
return jsonify(config)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting config: {e}")
|
||||
@@ -392,18 +393,26 @@ def update_config():
|
||||
data = request.get_json(silent=True)
|
||||
if data is None:
|
||||
return jsonify({"error": "No data provided"}), 400
|
||||
|
||||
# Update configuration using config manager
|
||||
|
||||
# Handle identity fields (cell_name, domain, ip_range, wireguard_port)
|
||||
identity_keys = {'cell_name', 'domain', 'ip_range', 'wireguard_port'}
|
||||
identity_updates = {k: v for k, v in data.items() if k in identity_keys}
|
||||
if identity_updates:
|
||||
stored = config_manager.configs.get('_identity', {})
|
||||
stored.update(identity_updates)
|
||||
config_manager.configs['_identity'] = stored
|
||||
config_manager._save_all_configs()
|
||||
|
||||
# Update service configurations
|
||||
for service, config in data.items():
|
||||
if service in config_manager.service_schemas:
|
||||
success = config_manager.update_service_config(service, config)
|
||||
if success:
|
||||
# Publish config change event
|
||||
service_bus.publish_event(EventType.CONFIG_CHANGED, service, {
|
||||
'service': service,
|
||||
'config': config
|
||||
})
|
||||
|
||||
|
||||
logger.info(f"Updated config: {data}")
|
||||
return jsonify({"message": "Configuration updated successfully"})
|
||||
except Exception as e:
|
||||
@@ -483,6 +492,19 @@ def import_config():
|
||||
logger.error(f"Error importing config: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route('/api/config/backups/<backup_id>', methods=['DELETE'])
|
||||
def delete_config_backup(backup_id):
|
||||
"""Delete a configuration backup."""
|
||||
try:
|
||||
success = config_manager.delete_backup(backup_id)
|
||||
if success:
|
||||
return jsonify({"message": f"Backup {backup_id} deleted"})
|
||||
else:
|
||||
return jsonify({"error": f"Failed to delete backup {backup_id}"}), 500
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting backup: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
# Service bus endpoints
|
||||
@app.route('/api/services/bus/status', methods=['GET'])
|
||||
def get_service_bus_status():
|
||||
|
||||
Reference in New Issue
Block a user