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>Resource Provider:</strong> {{ slice.resource_provider_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="resource_provider_email" value="{{ resource_provider_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 %} |