447 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			447 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
{% extends "marketplace/layout.html" %}
 | 
						|
 | 
						|
{% block title %}Project Mycelium - Rent Slice{% endblock %}
 | 
						|
 | 
						|
{% block marketplace_content %}
 | 
						|
<div class="my-4">
 | 
						|
    <div class="d-flex align-items-center mb-4">
 | 
						|
        <a href="/marketplace/compute" class="btn btn-outline-secondary me-3">
 | 
						|
            <i class="bi bi-arrow-left"></i> Back to Compute Resources
 | 
						|
        </a>
 | 
						|
        <h1 class="mb-0">Rent Compute Slice</h1>
 | 
						|
    </div>
 | 
						|
 | 
						|
    {% if slice %}
 | 
						|
    <div class="row">
 | 
						|
        <!-- Slice Information -->
 | 
						|
        <div class="col-lg-4 mb-4">
 | 
						|
            <div class="card">
 | 
						|
                <div class="card-header">
 | 
						|
                    <h5 class="mb-0">Slice Details</h5>
 | 
						|
                </div>
 | 
						|
                <div class="card-body">
 | 
						|
                    <div class="mb-3">
 | 
						|
                        <strong>Node:</strong> {{ slice.node_id }}
 | 
						|
                    </div>
 | 
						|
                    <div class="mb-3">
 | 
						|
                        <strong>Farmer:</strong> {{ slice.farmer_email }}
 | 
						|
                    </div>
 | 
						|
                    <div class="mb-3">
 | 
						|
                        <strong>Specifications:</strong>
 | 
						|
                        <ul class="list-unstyled mt-2">
 | 
						|
                            <li><i class="bi bi-cpu me-2"></i>{{ slice.cpu_cores }} CPU Cores</li>
 | 
						|
                            <li><i class="bi bi-memory me-2"></i>{{ slice.memory_gb }} GB RAM</li>
 | 
						|
                            <li><i class="bi bi-hdd me-2"></i>{{ slice.storage_gb }} GB Storage</li>
 | 
						|
                        </ul>
 | 
						|
                    </div>
 | 
						|
                    <div class="mb-3">
 | 
						|
                        <strong>Price:</strong> ${{ slice.price_per_hour }}/hour
 | 
						|
                    </div>
 | 
						|
                    <div class="mb-3">
 | 
						|
                        <strong>Available:</strong> 
 | 
						|
                        <span class="badge bg-success">{{ slice.available_quantity }} slices</span>
 | 
						|
                    </div>
 | 
						|
                </div>
 | 
						|
            </div>
 | 
						|
        </div>
 | 
						|
 | 
						|
        <!-- Rental Configuration Form -->
 | 
						|
        <div class="col-lg-8">
 | 
						|
            <div class="card">
 | 
						|
                <div class="card-header">
 | 
						|
                    <h5 class="mb-0">Deployment Configuration</h5>
 | 
						|
                </div>
 | 
						|
                <div class="card-body">
 | 
						|
                    <form id="sliceRentalForm" method="POST" action="/marketplace/slice/rent">
 | 
						|
                        <!-- Hidden fields -->
 | 
						|
                        <input type="hidden" name="farmer_email" value="{{ farmer_email }}">
 | 
						|
                        <input type="hidden" name="node_id" value="{{ node_id }}">
 | 
						|
                        <input type="hidden" name="combination_id" value="{{ combination_id }}">
 | 
						|
 | 
						|
                        <!-- Basic Configuration -->
 | 
						|
                        <div class="row mb-4">
 | 
						|
                            <div class="col-md-6">
 | 
						|
                                <label for="quantity" class="form-label">Quantity</label>
 | 
						|
                                <input type="number" class="form-control" id="quantity" name="quantity" 
 | 
						|
                                       min="1" max="{{ slice.available_quantity }}" value="1" required>
 | 
						|
                                <div class="form-text">Number of slices to rent</div>
 | 
						|
                            </div>
 | 
						|
                            <div class="col-md-6">
 | 
						|
                                <label for="rental_duration_hours" class="form-label">Duration (Hours)</label>
 | 
						|
                                <select class="form-select" id="rental_duration_hours" name="rental_duration_hours" required>
 | 
						|
                                    <option value="24">1 Day (24 hours)</option>
 | 
						|
                                    <option value="168">1 Week (168 hours)</option>
 | 
						|
                                    <option value="720" selected>1 Month (720 hours)</option>
 | 
						|
                                    <option value="2160">3 Months (2160 hours)</option>
 | 
						|
                                    <option value="4320">6 Months (4320 hours)</option>
 | 
						|
                                    <option value="8760">1 Year (8760 hours)</option>
 | 
						|
                                </select>
 | 
						|
                            </div>
 | 
						|
                        </div>
 | 
						|
 | 
						|
                        <!-- Deployment Type Selection -->
 | 
						|
                        <div class="mb-4">
 | 
						|
                            <label class="form-label">Deployment Type</label>
 | 
						|
                            <div class="row">
 | 
						|
                                <div class="col-md-6">
 | 
						|
                                    <div class="card deployment-option" data-deployment="vm">
 | 
						|
                                        <div class="card-body text-center">
 | 
						|
                                            <input type="radio" class="btn-check" name="deployment_type" id="vm_deployment" value="vm" checked>
 | 
						|
                                            <label class="btn btn-outline-primary w-100" for="vm_deployment">
 | 
						|
                                                <i class="bi bi-pc-display-horizontal display-6 d-block mb-2"></i>
 | 
						|
                                                <h5>Virtual Machine</h5>
 | 
						|
                                                <p class="text-muted small mb-0">Deploy a single VM with your chosen OS</p>
 | 
						|
                                            </label>
 | 
						|
                                        </div>
 | 
						|
                                    </div>
 | 
						|
                                </div>
 | 
						|
                                <div class="col-md-6">
 | 
						|
                                    <div class="card deployment-option" data-deployment="kubernetes">
 | 
						|
                                        <div class="card-body text-center">
 | 
						|
                                            <input type="radio" class="btn-check" name="deployment_type" id="k8s_deployment" value="kubernetes">
 | 
						|
                                            <label class="btn btn-outline-primary w-100" for="k8s_deployment">
 | 
						|
                                                <i class="bi bi-diagram-3 display-6 d-block mb-2"></i>
 | 
						|
                                                <h5>Kubernetes Cluster</h5>
 | 
						|
                                                <p class="text-muted small mb-0">Deploy a K8s cluster with configurable nodes</p>
 | 
						|
                                            </label>
 | 
						|
                                        </div>
 | 
						|
                                    </div>
 | 
						|
                                </div>
 | 
						|
                            </div>
 | 
						|
                        </div>
 | 
						|
 | 
						|
                        <!-- VM Configuration -->
 | 
						|
                        <div id="vm_config" class="deployment-config">
 | 
						|
                            <h6 class="mb-3">VM Configuration</h6>
 | 
						|
                            <div class="row mb-3">
 | 
						|
                                <div class="col-md-6">
 | 
						|
                                    <label for="vm_name" class="form-label">VM Name</label>
 | 
						|
                                    <input type="text" class="form-control" id="vm_name" name="vm_name" 
 | 
						|
                                           placeholder="my-vm" pattern="[a-zA-Z0-9-]+" required>
 | 
						|
                                    <div class="form-text">Alphanumeric characters and hyphens only</div>
 | 
						|
                                </div>
 | 
						|
                                <div class="col-md-6">
 | 
						|
                                    <label for="vm_os" class="form-label">Operating System</label>
 | 
						|
                                    <select class="form-select" id="vm_os" name="vm_os">
 | 
						|
                                        <option value="ubuntu" selected>Ubuntu 22.04 LTS</option>
 | 
						|
                                        <option value="debian">Debian 12</option>
 | 
						|
                                        <option value="centos">CentOS Stream 9</option>
 | 
						|
                                        <option value="alpine">Alpine Linux</option>
 | 
						|
                                    </select>
 | 
						|
                                </div>
 | 
						|
                            </div>
 | 
						|
                            <div class="mb-3">
 | 
						|
                                <label for="vm_ssh_key" class="form-label">SSH Public Key (Optional)</label>
 | 
						|
                                <textarea class="form-control" id="vm_ssh_key" name="vm_ssh_key" rows="3" 
 | 
						|
                                          placeholder="ssh-rsa AAAAB3NzaC1yc2E..."></textarea>
 | 
						|
                                <div class="form-text">Paste your SSH public key for secure access</div>
 | 
						|
                            </div>
 | 
						|
                        </div>
 | 
						|
 | 
						|
                        <!-- Kubernetes Configuration -->
 | 
						|
                        <div id="k8s_config" class="deployment-config" style="display: none;">
 | 
						|
                            <h6 class="mb-3">Kubernetes Configuration</h6>
 | 
						|
                            <div class="row mb-3">
 | 
						|
                                <div class="col-md-6">
 | 
						|
                                    <label for="cluster_name" class="form-label">Cluster Name</label>
 | 
						|
                                    <input type="text" class="form-control" id="cluster_name" name="cluster_name" 
 | 
						|
                                           placeholder="my-k8s-cluster" pattern="[a-zA-Z0-9-]+">
 | 
						|
                                    <div class="form-text">Alphanumeric characters and hyphens only</div>
 | 
						|
                                </div>
 | 
						|
                                <div class="col-md-6">
 | 
						|
                                    <label for="k8s_version" class="form-label">Kubernetes Version</label>
 | 
						|
                                    <select class="form-select" id="k8s_version" name="k8s_version">
 | 
						|
                                        <option value="1.29" selected>1.29 (Recommended)</option>
 | 
						|
                                        <option value="1.28">1.28</option>
 | 
						|
                                        <option value="1.30">1.30 (Latest)</option>
 | 
						|
                                    </select>
 | 
						|
                                </div>
 | 
						|
                            </div>
 | 
						|
                            <div class="row mb-3">
 | 
						|
                                <div class="col-md-4">
 | 
						|
                                    <label for="k8s_masters" class="form-label">Master Nodes</label>
 | 
						|
                                    <select class="form-select" id="k8s_masters" name="k8s_masters">
 | 
						|
                                        <option value="1" selected>1 Master</option>
 | 
						|
                                        <option value="3">3 Masters (HA)</option>
 | 
						|
                                    </select>
 | 
						|
                                </div>
 | 
						|
                                <div class="col-md-4">
 | 
						|
                                    <label for="k8s_workers" class="form-label">Worker Nodes</label>
 | 
						|
                                    <select class="form-select" id="k8s_workers" name="k8s_workers">
 | 
						|
                                        <option value="1" selected>1 Worker</option>
 | 
						|
                                        <option value="2">2 Workers</option>
 | 
						|
                                        <option value="3">3 Workers</option>
 | 
						|
                                        <option value="5">5 Workers</option>
 | 
						|
                                    </select>
 | 
						|
                                </div>
 | 
						|
                                <div class="col-md-4">
 | 
						|
                                    <label for="k8s_network_plugin" class="form-label">Network Plugin</label>
 | 
						|
                                    <select class="form-select" id="k8s_network_plugin" name="k8s_network_plugin">
 | 
						|
                                        <option value="flannel" selected>Flannel</option>
 | 
						|
                                        <option value="calico">Calico</option>
 | 
						|
                                        <option value="weave">Weave Net</option>
 | 
						|
                                    </select>
 | 
						|
                                </div>
 | 
						|
                            </div>
 | 
						|
                        </div>
 | 
						|
 | 
						|
                        <!-- Additional Options -->
 | 
						|
                        <div class="mb-4">
 | 
						|
                            <h6 class="mb-3">Additional Options</h6>
 | 
						|
                            <div class="row">
 | 
						|
                                <div class="col-md-4">
 | 
						|
                                    <div class="form-check">
 | 
						|
                                        <input class="form-check-input" type="checkbox" id="auto_scaling" name="auto_scaling">
 | 
						|
                                        <label class="form-check-label" for="auto_scaling">
 | 
						|
                                            Auto Scaling
 | 
						|
                                        </label>
 | 
						|
                                    </div>
 | 
						|
                                </div>
 | 
						|
                                <div class="col-md-4">
 | 
						|
                                    <div class="form-check">
 | 
						|
                                        <input class="form-check-input" type="checkbox" id="backup_enabled" name="backup_enabled">
 | 
						|
                                        <label class="form-check-label" for="backup_enabled">
 | 
						|
                                            Enable Backups
 | 
						|
                                        </label>
 | 
						|
                                    </div>
 | 
						|
                                </div>
 | 
						|
                                <div class="col-md-4">
 | 
						|
                                    <div class="form-check">
 | 
						|
                                        <input class="form-check-input" type="checkbox" id="monitoring_enabled" name="monitoring_enabled" checked>
 | 
						|
                                        <label class="form-check-label" for="monitoring_enabled">
 | 
						|
                                            Enable Monitoring
 | 
						|
                                        </label>
 | 
						|
                                    </div>
 | 
						|
                                </div>
 | 
						|
                            </div>
 | 
						|
                        </div>
 | 
						|
 | 
						|
                        <!-- Cost Calculation -->
 | 
						|
                        <div class="card bg-light mb-4">
 | 
						|
                            <div class="card-body">
 | 
						|
                                <h6 class="mb-3">Cost Calculation</h6>
 | 
						|
                                <div class="row">
 | 
						|
                                    <div class="col-md-6">
 | 
						|
                                        <div class="d-flex justify-content-between">
 | 
						|
                                            <span>Base Price per Hour:</span>
 | 
						|
                                            <span id="base_price">${{ slice.price_per_hour }}</span>
 | 
						|
                                        </div>
 | 
						|
                                        <div class="d-flex justify-content-between">
 | 
						|
                                            <span>Quantity:</span>
 | 
						|
                                            <span id="quantity_display">1</span>
 | 
						|
                                        </div>
 | 
						|
                                        <div class="d-flex justify-content-between">
 | 
						|
                                            <span>Duration:</span>
 | 
						|
                                            <span id="duration_display">720 hours</span>
 | 
						|
                                        </div>
 | 
						|
                                    </div>
 | 
						|
                                    <div class="col-md-6">
 | 
						|
                                        <div class="d-flex justify-content-between">
 | 
						|
                                            <span>Subtotal:</span>
 | 
						|
                                            <span id="subtotal">${{ slice.price_per_hour * 720 }}</span>
 | 
						|
                                        </div>
 | 
						|
                                        <div class="d-flex justify-content-between">
 | 
						|
                                            <span>K8s Multiplier:</span>
 | 
						|
                                            <span id="k8s_multiplier">1x</span>
 | 
						|
                                        </div>
 | 
						|
                                        <hr>
 | 
						|
                                        <div class="d-flex justify-content-between fw-bold">
 | 
						|
                                            <span>Total Cost:</span>
 | 
						|
                                            <span id="total_cost">${{ slice.price_per_hour * 720 }}</span>
 | 
						|
                                        </div>
 | 
						|
                                    </div>
 | 
						|
                                </div>
 | 
						|
                            </div>
 | 
						|
                        </div>
 | 
						|
 | 
						|
                        <!-- Submit Button -->
 | 
						|
                        <div class="d-grid">
 | 
						|
                            <button type="submit" class="btn btn-primary btn-lg" id="submitBtn">
 | 
						|
                                <i class="bi bi-rocket me-2"></i>Deploy Now
 | 
						|
                            </button>
 | 
						|
                        </div>
 | 
						|
                    </form>
 | 
						|
                </div>
 | 
						|
            </div>
 | 
						|
        </div>
 | 
						|
    </div>
 | 
						|
    {% else %}
 | 
						|
    <div class="text-center py-5">
 | 
						|
        <i class="bi bi-exclamation-triangle display-1 text-warning"></i>
 | 
						|
        <h4 class="mt-3">Slice Not Found</h4>
 | 
						|
        <p class="text-muted">The requested slice combination could not be found.</p>
 | 
						|
        <a href="/marketplace/compute" class="btn btn-primary">Browse Available Slices</a>
 | 
						|
    </div>
 | 
						|
    {% endif %}
 | 
						|
</div>
 | 
						|
 | 
						|
<!-- Pass data to JavaScript -->
 | 
						|
<script type="application/json" id="slice-data">
 | 
						|
{
 | 
						|
    "basePrice": {% if slice %}{{ slice.price_per_hour }}{% else %}0{% endif %},
 | 
						|
    "availableQuantity": {% if slice %}{{ slice.available_quantity }}{% else %}1{% endif %}
 | 
						|
}
 | 
						|
</script>
 | 
						|
 | 
						|
<script>
 | 
						|
document.addEventListener('DOMContentLoaded', function() {
 | 
						|
    const vmRadio = document.getElementById('vm_deployment');
 | 
						|
    const k8sRadio = document.getElementById('k8s_deployment');
 | 
						|
    const vmConfig = document.getElementById('vm_config');
 | 
						|
    const k8sConfig = document.getElementById('k8s_config');
 | 
						|
    const form = document.getElementById('sliceRentalForm');
 | 
						|
    
 | 
						|
    // Get data from JSON script tag
 | 
						|
    const sliceData = JSON.parse(document.getElementById('slice-data').textContent);
 | 
						|
    const basePrice = sliceData.basePrice;
 | 
						|
    
 | 
						|
    // Toggle deployment configuration
 | 
						|
    function toggleDeploymentConfig() {
 | 
						|
        if (vmRadio.checked) {
 | 
						|
            vmConfig.style.display = 'block';
 | 
						|
            k8sConfig.style.display = 'none';
 | 
						|
            // Make VM fields required
 | 
						|
            document.getElementById('vm_name').required = true;
 | 
						|
            document.getElementById('cluster_name').required = false;
 | 
						|
        } else {
 | 
						|
            vmConfig.style.display = 'none';
 | 
						|
            k8sConfig.style.display = 'block';
 | 
						|
            // Make K8s fields required
 | 
						|
            document.getElementById('vm_name').required = false;
 | 
						|
            document.getElementById('cluster_name').required = true;
 | 
						|
        }
 | 
						|
        updateCostCalculation();
 | 
						|
    }
 | 
						|
    
 | 
						|
    // Update cost calculation
 | 
						|
    function updateCostCalculation() {
 | 
						|
        const quantity = parseInt(document.getElementById('quantity').value) || 1;
 | 
						|
        const duration = parseInt(document.getElementById('rental_duration_hours').value) || 720;
 | 
						|
        const masters = parseInt(document.getElementById('k8s_masters').value) || 1;
 | 
						|
        const workers = parseInt(document.getElementById('k8s_workers').value) || 1;
 | 
						|
        
 | 
						|
        let multiplier = 1;
 | 
						|
        if (k8sRadio.checked) {
 | 
						|
            multiplier = masters + workers;
 | 
						|
        }
 | 
						|
        
 | 
						|
        const subtotal = basePrice * quantity * duration;
 | 
						|
        const total = subtotal * multiplier;
 | 
						|
        
 | 
						|
        document.getElementById('quantity_display').textContent = quantity;
 | 
						|
        document.getElementById('duration_display').textContent = duration + ' hours';
 | 
						|
        document.getElementById('subtotal').textContent = '$' + subtotal.toFixed(2);
 | 
						|
        document.getElementById('k8s_multiplier').textContent = multiplier + 'x';
 | 
						|
        document.getElementById('total_cost').textContent = '$' + total.toFixed(2);
 | 
						|
    }
 | 
						|
    
 | 
						|
    // Event listeners
 | 
						|
    vmRadio.addEventListener('change', toggleDeploymentConfig);
 | 
						|
    k8sRadio.addEventListener('change', toggleDeploymentConfig);
 | 
						|
    document.getElementById('quantity').addEventListener('input', updateCostCalculation);
 | 
						|
    document.getElementById('rental_duration_hours').addEventListener('change', updateCostCalculation);
 | 
						|
    document.getElementById('k8s_masters').addEventListener('change', updateCostCalculation);
 | 
						|
    document.getElementById('k8s_workers').addEventListener('change', updateCostCalculation);
 | 
						|
    
 | 
						|
    // Form submission
 | 
						|
    form.addEventListener('submit', function(e) {
 | 
						|
        e.preventDefault();
 | 
						|
        
 | 
						|
        const submitBtn = document.getElementById('submitBtn');
 | 
						|
        const originalText = submitBtn.innerHTML;
 | 
						|
        submitBtn.innerHTML = '<i class="bi bi-hourglass-split me-2"></i>Deploying...';
 | 
						|
        submitBtn.disabled = true;
 | 
						|
        
 | 
						|
        const formData = new FormData(form);
 | 
						|
        
 | 
						|
        window.apiJson('/marketplace/slice/rent', {
 | 
						|
            method: 'POST',
 | 
						|
            body: formData
 | 
						|
        })
 | 
						|
        .then(response => response)
 | 
						|
        .then(data => {
 | 
						|
            if (data.success) {
 | 
						|
                // Show success message
 | 
						|
                const alertDiv = document.createElement('div');
 | 
						|
                alertDiv.className = 'alert alert-success alert-dismissible fade show';
 | 
						|
                alertDiv.innerHTML = `
 | 
						|
                    <i class="bi bi-check-circle me-2"></i>
 | 
						|
                    ${data.message}
 | 
						|
                    <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
 | 
						|
                `;
 | 
						|
                form.parentNode.insertBefore(alertDiv, form);
 | 
						|
                
 | 
						|
                // Redirect after 3 seconds
 | 
						|
                setTimeout(() => {
 | 
						|
                    if (data.redirect_url) {
 | 
						|
                        window.location.href = data.redirect_url;
 | 
						|
                    }
 | 
						|
                }, 3000);
 | 
						|
            } else {
 | 
						|
                // Show error message
 | 
						|
                const alertDiv = document.createElement('div');
 | 
						|
                alertDiv.className = 'alert alert-danger alert-dismissible fade show';
 | 
						|
                alertDiv.innerHTML = `
 | 
						|
                    <i class="bi bi-exclamation-triangle me-2"></i>
 | 
						|
                    ${data.message}
 | 
						|
                    <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
 | 
						|
                `;
 | 
						|
                form.parentNode.insertBefore(alertDiv, form);
 | 
						|
                
 | 
						|
                submitBtn.innerHTML = originalText;
 | 
						|
                submitBtn.disabled = false;
 | 
						|
            }
 | 
						|
        })
 | 
						|
        .catch(error => {
 | 
						|
            console.error('Error:', error);
 | 
						|
            const alertDiv = document.createElement('div');
 | 
						|
            alertDiv.className = 'alert alert-danger alert-dismissible fade show';
 | 
						|
            alertDiv.innerHTML = `
 | 
						|
                <i class="bi bi-exclamation-triangle me-2"></i>
 | 
						|
                An error occurred while processing your request.
 | 
						|
                <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
 | 
						|
            `;
 | 
						|
            form.parentNode.insertBefore(alertDiv, form);
 | 
						|
            
 | 
						|
            submitBtn.innerHTML = originalText;
 | 
						|
            submitBtn.disabled = false;
 | 
						|
        });
 | 
						|
    });
 | 
						|
    
 | 
						|
    // Initialize
 | 
						|
    toggleDeploymentConfig();
 | 
						|
    updateCostCalculation();
 | 
						|
});
 | 
						|
</script>
 | 
						|
 | 
						|
<style>
 | 
						|
.deployment-option {
 | 
						|
    transition: all 0.3s ease;
 | 
						|
    cursor: pointer;
 | 
						|
}
 | 
						|
 | 
						|
.deployment-option:hover {
 | 
						|
    transform: translateY(-2px);
 | 
						|
    box-shadow: 0 4px 8px rgba(0,0,0,0.1);
 | 
						|
}
 | 
						|
 | 
						|
.deployment-config {
 | 
						|
    border: 1px solid #dee2e6;
 | 
						|
    border-radius: 0.375rem;
 | 
						|
    padding: 1rem;
 | 
						|
    background-color: #f8f9fa;
 | 
						|
}
 | 
						|
 | 
						|
.btn-check:checked + .btn {
 | 
						|
    background-color: var(--bs-primary);
 | 
						|
    border-color: var(--bs-primary);
 | 
						|
    color: white;
 | 
						|
}
 | 
						|
 | 
						|
.spec-item {
 | 
						|
    display: flex;
 | 
						|
    align-items: center;
 | 
						|
    margin-bottom: 0.5rem;
 | 
						|
}
 | 
						|
</style>
 | 
						|
{% endblock %} |