Files
ServerKit/backend/app/api/cloud_provisioning.py
T
Juan Denis 6c41e88701 Add auth checks, logging, DNS API & UI
Enforce admin and workspace permissions, improve logging, and add DNS frontend/API support.

- Backend: added admin checks to cloud_provisioning and marketplace endpoints; added require_workspace_access helper and applied to multiple workspace endpoints to enforce membership/admin access; persist push subscription data in mobile API (commit to DB); replaced print() debugging with structured logger calls across agent_registry and added logging/exception handling in monitoring_service.
- Frontend: added a DNS API client (frontend/src/services/api/dns.js) and registered it in the API index; extended DNSZones UI to collect provider zone ID and API token, updated create payload and form reset; added optional modal footer prop to Modal component; moved rollup native binding to optionalDependencies in package.json.
- Misc: added '*.png' to .gitignore and a minor spacing change in install.sh.

These changes improve security checks, observability, and add DNS management features in the UI/API.
2026-03-24 06:10:58 -04:00

163 lines
5.5 KiB
Python

from flask import Blueprint, request, jsonify
from flask_jwt_extended import jwt_required
from app.services.cloud_provisioning_service import CloudProvisioningService
cloud_provisioning_bp = Blueprint('cloud_provisioning', __name__)
def get_current_user():
from flask_jwt_extended import get_jwt_identity
from app.models.user import User
return User.query.get(get_jwt_identity())
# --- Providers ---
@cloud_provisioning_bp.route('/providers', methods=['GET'])
@jwt_required()
def list_providers():
providers = CloudProvisioningService.list_providers()
return jsonify({'providers': [p.to_dict() for p in providers]})
@cloud_provisioning_bp.route('/providers', methods=['POST'])
@jwt_required()
def create_provider():
user = get_current_user()
if not user or not user.is_admin:
return jsonify({'error': 'Admin access required'}), 403
data = request.get_json()
try:
provider = CloudProvisioningService.create_provider(data, user.id)
return jsonify(provider.to_dict()), 201
except ValueError as e:
return jsonify({'error': str(e)}), 400
@cloud_provisioning_bp.route('/providers/<int:provider_id>', methods=['DELETE'])
@jwt_required()
def delete_provider(provider_id):
user = get_current_user()
if not user or not user.is_admin:
return jsonify({'error': 'Admin access required'}), 403
if not CloudProvisioningService.delete_provider(provider_id):
return jsonify({'error': 'Not found'}), 404
return jsonify({'message': 'Provider removed'})
@cloud_provisioning_bp.route('/providers/<provider_type>/options', methods=['GET'])
@jwt_required()
def get_options(provider_type):
options = CloudProvisioningService.get_provider_options(provider_type)
if not options:
return jsonify({'error': 'Unknown provider'}), 404
return jsonify(options)
# --- Servers ---
@cloud_provisioning_bp.route('/servers', methods=['GET'])
@jwt_required()
def list_servers():
provider_id = request.args.get('provider_id', type=int)
servers = CloudProvisioningService.list_servers(provider_id)
return jsonify({'servers': [s.to_dict() for s in servers]})
@cloud_provisioning_bp.route('/servers/<int:server_id>', methods=['GET'])
@jwt_required()
def get_server(server_id):
server = CloudProvisioningService.get_server(server_id)
if not server:
return jsonify({'error': 'Not found'}), 404
return jsonify(server.to_dict())
@cloud_provisioning_bp.route('/servers', methods=['POST'])
@jwt_required()
def create_server():
user = get_current_user()
if not user or not user.is_admin:
return jsonify({'error': 'Admin access required'}), 403
data = request.get_json()
if not data or 'name' not in data or 'provider_id' not in data:
return jsonify({'error': 'name and provider_id required'}), 400
try:
server = CloudProvisioningService.create_server(data, user.id)
return jsonify(server.to_dict()), 201
except (ValueError, Exception) as e:
return jsonify({'error': str(e)}), 400
@cloud_provisioning_bp.route('/servers/<int:server_id>', methods=['DELETE'])
@jwt_required()
def destroy_server(server_id):
user = get_current_user()
if not user or not user.is_admin:
return jsonify({'error': 'Admin access required'}), 403
if not CloudProvisioningService.destroy_server(server_id):
return jsonify({'error': 'Not found'}), 404
return jsonify({'message': 'Server destroyed'})
@cloud_provisioning_bp.route('/servers/<int:server_id>/resize', methods=['POST'])
@jwt_required()
def resize_server(server_id):
user = get_current_user()
if not user or not user.is_admin:
return jsonify({'error': 'Admin access required'}), 403
data = request.get_json() or {}
new_size = data.get('size')
if not new_size:
return jsonify({'error': 'size required'}), 400
try:
server = CloudProvisioningService.resize_server(server_id, new_size)
if not server:
return jsonify({'error': 'Not found'}), 404
return jsonify(server.to_dict())
except ValueError as e:
return jsonify({'error': str(e)}), 400
# --- Snapshots ---
@cloud_provisioning_bp.route('/servers/<int:server_id>/snapshots', methods=['GET'])
@jwt_required()
def list_snapshots(server_id):
snapshots = CloudProvisioningService.get_snapshots(server_id)
return jsonify({'snapshots': [s.to_dict() for s in snapshots]})
@cloud_provisioning_bp.route('/servers/<int:server_id>/snapshots', methods=['POST'])
@jwt_required()
def create_snapshot(server_id):
user = get_current_user()
if not user or not user.is_admin:
return jsonify({'error': 'Admin access required'}), 403
data = request.get_json() or {}
name = data.get('name', f'snapshot-{server_id}')
try:
snapshot = CloudProvisioningService.create_snapshot(server_id, name)
return jsonify(snapshot.to_dict()), 201
except ValueError as e:
return jsonify({'error': str(e)}), 400
@cloud_provisioning_bp.route('/snapshots/<int:snapshot_id>', methods=['DELETE'])
@jwt_required()
def delete_snapshot(snapshot_id):
user = get_current_user()
if not user or not user.is_admin:
return jsonify({'error': 'Admin access required'}), 403
if not CloudProvisioningService.delete_snapshot(snapshot_id):
return jsonify({'error': 'Not found'}), 404
return jsonify({'message': 'Snapshot deleted'})
# --- Cost ---
@cloud_provisioning_bp.route('/costs', methods=['GET'])
@jwt_required()
def cost_summary():
return jsonify(CloudProvisioningService.get_cost_summary())