445 lines
18 KiB
HTML
445 lines
18 KiB
HTML
{% extends "marketplace/layout.html" %}
|
|
|
|
{% block title %}Mycelium Marketplace - Compute Resources{% endblock %}
|
|
|
|
{% block marketplace_content %}
|
|
<div class="my-4">
|
|
<h1>Compute Resources (Slices)</h1>
|
|
<p class="lead">Find and acquire the compute resources you need for your workloads.</p>
|
|
|
|
<!-- Enhanced Filter Section -->
|
|
<div class="filter-section p-4 mb-4">
|
|
<h5 class="mb-3">Filter Resources</h5>
|
|
<form id="filterForm" method="GET">
|
|
<div class="enhanced-filters">
|
|
<div>
|
|
<label class="form-label">Location</label>
|
|
<select name="location" class="form-select">
|
|
<option value="">All Locations</option>
|
|
<option value="Belgium">Belgium</option>
|
|
<option value="Austria">Austria</option>
|
|
<option value="Switzerland">Switzerland</option>
|
|
<option value="Netherlands">Netherlands</option>
|
|
<option value="Germany">Germany</option>
|
|
<option value="France">France</option>
|
|
<option value="UK">UK</option>
|
|
<option value="Canada">Canada</option>
|
|
<option value="USA">USA</option>
|
|
<option value="Japan">Japan</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="form-label">Min vCores</label>
|
|
<select name="min_cores" class="form-select">
|
|
<option value="">Any</option>
|
|
<option value="1">1 Core+</option>
|
|
<option value="2">2 Cores+</option>
|
|
<option value="4">4 Cores+</option>
|
|
<option value="8">8 Cores+</option>
|
|
<option value="16">16 Cores+</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="form-label">Min RAM (GB)</label>
|
|
<select name="min_memory" class="form-select">
|
|
<option value="">Any</option>
|
|
<option value="4">4 GB+</option>
|
|
<option value="8">8 GB+</option>
|
|
<option value="16">16 GB+</option>
|
|
<option value="32">32 GB+</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="form-label">Min Storage (GB)</label>
|
|
<select name="min_storage" class="form-select">
|
|
<option value="">Any</option>
|
|
<option value="200">200 GB+</option>
|
|
<option value="500">500 GB+</option>
|
|
<option value="1000">1 TB+</option>
|
|
<option value="2000">2 TB+</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="form-label">Min Uptime (%)</label>
|
|
<select name="min_uptime" class="form-select">
|
|
<option value="">Any</option>
|
|
<option value="95">95%+</option>
|
|
<option value="98">98%+</option>
|
|
<option value="99">99%+</option>
|
|
<option value="99.9">99.9%+</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="form-label">Min Bandwidth (Mbps)</label>
|
|
<select name="min_bandwidth" class="form-select">
|
|
<option value="">Any</option>
|
|
<option value="100">100 Mbps+</option>
|
|
<option value="500">500 Mbps+</option>
|
|
<option value="1000">1 Gbps+</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="form-label">Price Range ($)</label>
|
|
<div class="d-flex gap-2">
|
|
<input type="number" name="min_price" class="form-control" placeholder="Min" style="width: 80px;">
|
|
<input type="number" name="max_price" class="form-control" placeholder="Max" style="width: 80px;">
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<button type="submit" class="btn btn-primary">Apply Filters</button>
|
|
<a href="/marketplace/compute" class="btn btn-outline-secondary ms-2">Clear</a>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Table Section -->
|
|
{% if compute_products and compute_products | length > 0 %}
|
|
<div class="compute-table">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th style="width: 15%;">Type</th>
|
|
<th style="width: 12%;">Provider</th>
|
|
<th style="width: 10%;">Location</th>
|
|
<th style="width: 8%;">vCores</th>
|
|
<th style="width: 8%;">RAM</th>
|
|
<th style="width: 10%;">Storage (SSD)</th>
|
|
<th style="width: 10%;">Uptime SLA</th>
|
|
<th style="width: 10%;">Bandwidth SLA</th>
|
|
<th style="width: 8%;">Price/Hour</th>
|
|
<th style="width: 9%;">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for product_data in compute_products %}
|
|
<tr>
|
|
<td>
|
|
<div class="slice-name">{{ product_data.product.name }}</div>
|
|
<div class="slice-info">
|
|
<small class="text-muted d-block">
|
|
{% if product_data.product.attributes.cpu_cores %}
|
|
{{ product_data.product.attributes.cpu_cores.value }}x Base Unit
|
|
{% else %}
|
|
Compute slice
|
|
{% endif %}
|
|
</small>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="provider-name">
|
|
{% if product_data.product.attributes.resource_provider_email %}
|
|
{{ product_data.product.attributes.resource_provider_email.value | truncate(length=15) }}
|
|
{% else %}
|
|
{% if product_data.product.provider %}{{ product_data.product.provider }}{% else %}Unknown{% endif %}
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
<td>
|
|
{% if product_data.product.metadata.location %}
|
|
<span class="location-badge node-location"
|
|
data-location="{{ product_data.product.metadata.location }}">
|
|
{{ product_data.product.metadata.location }}
|
|
</span>
|
|
{% else %}
|
|
<span class="text-muted">-</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if product_data.product.attributes.cpu_cores %}
|
|
<span class="spec-value">{{ product_data.product.attributes.cpu_cores.value }}</span>
|
|
<span class="spec-unit">cores</span>
|
|
{% else %}
|
|
<span class="text-muted">-</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if product_data.product.attributes.memory_gb %}
|
|
<span class="spec-value">{{ product_data.product.attributes.memory_gb.value }}</span>
|
|
<span class="spec-unit">GB</span>
|
|
{% else %}
|
|
<span class="text-muted">-</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if product_data.product.attributes.storage_gb %}
|
|
<span class="spec-value">{{ product_data.product.attributes.storage_gb.value }}</span>
|
|
<span class="spec-unit">GB</span>
|
|
{% else %}
|
|
<span class="text-muted">-</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if product_data.product.attributes.uptime_percentage %}
|
|
<div class="sla-indicator sla-good">
|
|
<i class="bi bi-check-circle-fill"></i>
|
|
<span>{{ product_data.product.attributes.uptime_percentage.value | format_decimal(precision=1) }}%</span>
|
|
</div>
|
|
{% else %}
|
|
<span class="text-muted">-</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if product_data.product.attributes.bandwidth_mbps %}
|
|
<div class="sla-indicator sla-good">
|
|
<i class="bi bi-speedometer2"></i>
|
|
<span>{{ product_data.product.attributes.bandwidth_mbps.value }} Mbps</span>
|
|
</div>
|
|
{% else %}
|
|
<div class="sla-indicator sla-good">
|
|
<i class="bi bi-speedometer2"></i>
|
|
<span>1000 Mbps</span>
|
|
</div>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="price-display">{{ product_data.formatted_price }}</div>
|
|
</td>
|
|
<td>
|
|
<div class="action-buttons">
|
|
<button class="btn btn-success btn-sm-custom buy-now-btn"
|
|
data-product-id="{{ product_data.product.id }}"
|
|
data-product-name="{{ product_data.product.name }}"
|
|
data-unit-price="{{ product_data.price.display_amount }}"
|
|
data-currency="{{ product_data.price.display_currency }}"
|
|
data-category="compute">
|
|
<i class="bi bi-lightning-charge"></i> Buy Now
|
|
</button>
|
|
<button class="btn btn-primary btn-sm-custom add-to-cart-btn"
|
|
data-product-id="{{ product_data.product.id }}"
|
|
data-product-name="{{ product_data.product.name }}"
|
|
data-unit-price="{{ product_data.price.display_amount }}"
|
|
data-currency="{{ product_data.price.display_currency }}">
|
|
<i class="bi bi-cart-plus"></i> Add to Cart
|
|
</button>
|
|
<a class="btn btn-outline-secondary btn-sm-custom" href="/products/{{ product_data.product.id }}">
|
|
<i class="bi bi-eye"></i> View
|
|
</a>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-5">
|
|
<i class="bi bi-inbox display-1 text-muted"></i>
|
|
<h4 class="mt-3">No Compute Resources Available</h4>
|
|
<p class="text-muted">Check back later for new compute resources.</p>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Pagination -->
|
|
{% if pagination and pagination.total_pages > 1 %}
|
|
<nav aria-label="Resource pages" class="mt-4">
|
|
<ul class="pagination justify-content-center">
|
|
<!-- Previous Page -->
|
|
<li class="page-item {% if not pagination.has_previous %}disabled{% endif %}">
|
|
{% if pagination.has_previous %}
|
|
<a class="page-link" href="?page={{ pagination.previous_page }}">Previous</a>
|
|
{% else %}
|
|
<span class="page-link" tabindex="-1" aria-disabled="true">Previous</span>
|
|
{% endif %}
|
|
</li>
|
|
|
|
<!-- Page 1 -->
|
|
<li class="page-item {% if pagination.current_page == 0 %}active{% endif %}">
|
|
{% if pagination.current_page == 0 %}
|
|
<span class="page-link">1</span>
|
|
{% else %}
|
|
<a class="page-link" href="?page=0">1</a>
|
|
{% endif %}
|
|
</li>
|
|
|
|
<!-- Page 2 (if exists) -->
|
|
{% if pagination.total_pages > 1 %}
|
|
<li class="page-item {% if pagination.current_page == 1 %}active{% endif %}">
|
|
{% if pagination.current_page == 1 %}
|
|
<span class="page-link">2</span>
|
|
{% else %}
|
|
<a class="page-link" href="?page=1">2</a>
|
|
{% endif %}
|
|
</li>
|
|
{% endif %}
|
|
|
|
<!-- Page 3 (if exists) -->
|
|
{% if pagination.total_pages > 2 %}
|
|
<li class="page-item {% if pagination.current_page == 2 %}active{% endif %}">
|
|
{% if pagination.current_page == 2 %}
|
|
<span class="page-link">3</span>
|
|
{% else %}
|
|
<a class="page-link" href="?page=2">3</a>
|
|
{% endif %}
|
|
</li>
|
|
{% endif %}
|
|
|
|
<!-- Page 4 (if exists) -->
|
|
{% if pagination.total_pages > 3 %}
|
|
<li class="page-item {% if pagination.current_page == 3 %}active{% endif %}">
|
|
{% if pagination.current_page == 3 %}
|
|
<span class="page-link">4</span>
|
|
{% else %}
|
|
<a class="page-link" href="?page=3">4</a>
|
|
{% endif %}
|
|
</li>
|
|
{% endif %}
|
|
|
|
<!-- Page 5 (if exists) -->
|
|
{% if pagination.total_pages > 4 %}
|
|
<li class="page-item {% if pagination.current_page == 4 %}active{% endif %}">
|
|
{% if pagination.current_page == 4 %}
|
|
<span class="page-link">5</span>
|
|
{% else %}
|
|
<a class="page-link" href="?page=4">5</a>
|
|
{% endif %}
|
|
</li>
|
|
{% endif %}
|
|
|
|
<!-- Next Page -->
|
|
<li class="page-item {% if not pagination.has_next %}disabled{% endif %}">
|
|
{% if pagination.has_next %}
|
|
<a class="page-link" href="?page={{ pagination.next_page }}">Next</a>
|
|
{% else %}
|
|
<span class="page-link" tabindex="-1" aria-disabled="true">Next</span>
|
|
{% endif %}
|
|
</li>
|
|
</ul>
|
|
</nav>
|
|
|
|
<!-- Results Info -->
|
|
<div class="text-center text-muted mt-2">
|
|
Showing page {{ pagination.current_page + 1 }} of {{ pagination.total_pages }}
|
|
({{ pagination.total_count }} total compute resources)
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<style>
|
|
.compute-table {
|
|
background: white;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.compute-table th {
|
|
background-color: #f8f9fa;
|
|
border-bottom: 2px solid #dee2e6;
|
|
font-weight: 600;
|
|
color: #495057;
|
|
padding: 12px 8px;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.compute-table td {
|
|
padding: 12px 8px;
|
|
vertical-align: middle;
|
|
border-bottom: 1px solid #f1f3f4;
|
|
}
|
|
|
|
.compute-table tbody tr:hover {
|
|
background-color: #f8f9fa;
|
|
}
|
|
|
|
.provider-name {
|
|
font-weight: 600;
|
|
color: #0d6efd;
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.slice-info {
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.slice-name {
|
|
font-weight: 500;
|
|
color: #212529;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.spec-value {
|
|
font-weight: 500;
|
|
color: #212529;
|
|
}
|
|
|
|
.spec-unit {
|
|
color: #6c757d;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.location-badge {
|
|
background-color: #e3f2fd;
|
|
color: #1976d2;
|
|
padding: 4px 8px;
|
|
border-radius: 12px;
|
|
font-size: 0.8rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.sla-indicator {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
|
|
.sla-good { color: #28a745; }
|
|
.sla-medium { color: #ffc107; }
|
|
.sla-low { color: #dc3545; }
|
|
|
|
.price-display {
|
|
font-weight: 600;
|
|
color: #0d6efd;
|
|
font-size: 1.1rem;
|
|
}
|
|
|
|
.action-buttons {
|
|
display: flex;
|
|
gap: 4px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.btn-sm-custom {
|
|
padding: 4px 8px;
|
|
font-size: 0.8rem;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.compute-table {
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.compute-table th,
|
|
.compute-table td {
|
|
padding: 8px 4px;
|
|
}
|
|
|
|
.action-buttons {
|
|
flex-direction: column;
|
|
}
|
|
}
|
|
|
|
.filter-section {
|
|
background: white;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.enhanced-filters {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 15px;
|
|
align-items: end;
|
|
}
|
|
</style>
|
|
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script type="application/json" id="compute-hydration">{}</script>
|
|
<script src="/static/js/marketplace-compute.js"></script>
|
|
{% endblock %} |