440 lines
No EOL
29 KiB
HTML
440 lines
No EOL
29 KiB
HTML
{% extends 'base.html' %}
|
||
{% load static %}
|
||
|
||
{% block title %}Complete Price List{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
<script src="{% static "js/chart.js" %}"></script>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="container-fluid mt-4">
|
||
<div class="row">
|
||
<div class="col-12">
|
||
<h1 class="mb-4">Complete Price List - All Service Variants</h1>
|
||
|
||
<!-- Filter Form -->
|
||
<div class="card mb-4">
|
||
<div class="card-header">
|
||
<h5 class="mb-0">Filters</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<form method="get" class="row g-3" id="filter-form">
|
||
<div class="col-md-3">
|
||
<label for="cloud_provider" class="form-label">Cloud Provider</label>
|
||
<select name="cloud_provider" id="cloud_provider" class="form-select filter-select">
|
||
<option value="">All Providers</option>
|
||
{% for provider in all_cloud_providers %}
|
||
<option value="{{ provider }}" {% if provider == filter_cloud_provider %}selected{% endif %}>
|
||
{{ provider }}
|
||
</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<label for="service" class="form-label">Service</label>
|
||
<select name="service" id="service" class="form-select filter-select">
|
||
<option value="">All Services</option>
|
||
{% for service in all_services %}
|
||
<option value="{{ service }}" {% if service == filter_service %}selected{% endif %}>
|
||
{{ service }}
|
||
</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<label for="compute_plan_group" class="form-label">Compute Plan Group</label>
|
||
<select name="compute_plan_group" id="compute_plan_group" class="form-select filter-select">
|
||
<option value="">All Groups</option>
|
||
{% for group in all_compute_plan_groups %}
|
||
<option value="{{ group }}" {% if group == filter_compute_plan_group %}selected{% endif %}>
|
||
{{ group }}
|
||
</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<label for="service_level" class="form-label">Service Level</label>
|
||
<select name="service_level" id="service_level" class="form-select filter-select">
|
||
<option value="">All Service Levels</option>
|
||
{% for level in all_service_levels %}
|
||
<option value="{{ level }}" {% if level == filter_service_level %}selected{% endif %}>
|
||
{{ level }}
|
||
</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-12">
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" name="discount_details" value="true" id="discount_details" {% if show_discount_details %}checked{% endif %}>
|
||
<label class="form-check-label" for="discount_details">
|
||
Show discount details
|
||
</label>
|
||
</div>
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" name="price_comparison" value="true" id="price_comparison" {% if show_price_comparison %}checked{% endif %}>
|
||
<label class="form-check-label" for="price_comparison">
|
||
Show external price comparisons
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="col-12">
|
||
<button type="submit" class="btn btn-primary">Apply Filters</button>
|
||
<a href="{% url 'services:pricelist' %}" class="btn btn-secondary">Clear Filters</a>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Active Filters Display -->
|
||
{% if filter_cloud_provider or filter_service or filter_compute_plan_group or filter_service_level %}
|
||
<div class="alert alert-info">
|
||
<strong>Active Filters:</strong>
|
||
{% if filter_cloud_provider %}<span class="badge me-1">Cloud Provider: {{ filter_cloud_provider }}</span>{% endif %}
|
||
{% if filter_service %}<span class="badge me-1">Service: {{ filter_service }}</span>{% endif %}
|
||
{% if filter_compute_plan_group %}<span class="badge me-1">Group: {{ filter_compute_plan_group }}</span>{% endif %}
|
||
{% if filter_service_level %}<span class="badge me-1">Service Level: {{ filter_service_level }}</span>{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
|
||
{% if pricing_data_by_group_and_service_level %}
|
||
{% for group_name, service_levels in pricing_data_by_group_and_service_level.items %}
|
||
<div class="mb-5 border rounded p-3">
|
||
<h2 class="mb-3 text-primary">{{ group_name }}</h2>
|
||
|
||
{# Display group description and node_label from first available plan #}
|
||
{% for service_level, pricing_data in service_levels.items %}
|
||
{% if pricing_data and forloop.first %}
|
||
{% with pricing_data.0 as representative_plan %}
|
||
{% if representative_plan.compute_plan_group_description %}
|
||
<p class="text-muted mb-2"><strong>Description:</strong> {{ representative_plan.compute_plan_group_description }}</p>
|
||
{% endif %}
|
||
{% if representative_plan.compute_plan_group_node_label %}
|
||
<p class="text-muted mb-3"><strong>Node Label:</strong> <code>{{ representative_plan.compute_plan_group_node_label }}</code></p>
|
||
{% endif %}
|
||
|
||
{# Display storage pricing for this cloud provider #}
|
||
{% if representative_plan.storage_plans %}
|
||
<div class="mb-3">
|
||
<p class="text-muted mb-2"><strong>Storage Options:</strong></p>
|
||
<div class="table-responsive">
|
||
<table class="table table-sm table-bordered">
|
||
<thead class="table-secondary">
|
||
<tr>
|
||
<th>Storage Plan</th>
|
||
<th>Term</th>
|
||
<th>Unit</th>
|
||
<th>Prices</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for storage_plan in representative_plan.storage_plans %}
|
||
<tr>
|
||
<td>{{ storage_plan.name }}</td>
|
||
<td>{{ storage_plan.get_term_display }}</td>
|
||
<td>{{ storage_plan.get_unit_display }}</td>
|
||
<td>
|
||
{% for price in storage_plan.prices.all %}
|
||
<span class="badge bg-light text-dark me-1">{{ price.amount }} {{ price.currency }}</span>
|
||
{% empty %}
|
||
<span class="text-muted">No prices</span>
|
||
{% endfor %}
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
{% endwith %}
|
||
{% endif %}
|
||
{% endfor %}
|
||
|
||
{% for service_level, pricing_data in service_levels.items %}
|
||
<div class="mb-4">
|
||
<h3 class="mb-3">SLA: {{ service_level }}</h3>
|
||
{% if pricing_data %}
|
||
{# Display common values for this service level #}
|
||
{% with pricing_data.0 as first_row %}
|
||
<div class="row mb-3">
|
||
<div class="col-md-2">
|
||
<strong>Cloud Provider:</strong> {{ first_row.cloud_provider }}
|
||
</div>
|
||
<div class="col-md-2">
|
||
<strong>Service:</strong> {{ first_row.service }}
|
||
</div>
|
||
<div class="col-md-2">
|
||
<strong>CPU/Memory Ratio:</strong> {{ first_row.cpu_mem_ratio }}
|
||
</div>
|
||
<div class="col-md-2">
|
||
<strong>Variable Unit:</strong> {{ first_row.variable_unit }}
|
||
</div>
|
||
<div class="col-md-2">
|
||
<strong>Replica Enforce:</strong> {{ first_row.replica_enforce }}
|
||
</div>
|
||
</div>
|
||
{% endwith %}
|
||
|
||
<div class="table-responsive">
|
||
<table class="table table-striped table-bordered table-sm pricing-table">
|
||
<thead class="table-dark">
|
||
<tr>
|
||
<th>Compute Plan</th>
|
||
<th>Cloud Provider</th>
|
||
<th>vCPUs</th>
|
||
<th>RAM (GB)</th>
|
||
<th>Term</th>
|
||
<th>Currency</th>
|
||
<th>Compute Plan Price</th>
|
||
<th>Units</th>
|
||
<th>SLA Base</th>
|
||
<th>SLA Per Unit</th>
|
||
<th>SLA Price</th>
|
||
{% if show_discount_details %}
|
||
<th>Discount Model</th>
|
||
<th>Discount Details</th>
|
||
{% endif %}
|
||
{% if show_price_comparison %}
|
||
<th>External Comparisons</th>
|
||
{% endif %}
|
||
<th class="final-price-header">Final Price</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for row in pricing_data %}
|
||
<tr class="servala-row">
|
||
<td>{{ row.compute_plan }}</td>
|
||
<td>{{ row.cloud_provider }}</td>
|
||
<td>{{ row.vcpus }}</td>
|
||
<td>{{ row.ram }}</td>
|
||
<td>{{ row.term }}</td>
|
||
<td>{{ row.currency }}</td>
|
||
<td>{{ row.compute_plan_price|floatformat:2 }}</td>
|
||
<td>{{ row.units }}</td>
|
||
<td>{{ row.sla_base|floatformat:2 }}</td>
|
||
<td>{{ row.sla_per_unit|floatformat:4 }}</td>
|
||
<td>{{ row.sla_price|floatformat:2 }}</td>
|
||
{% if show_discount_details %}
|
||
<td>
|
||
{% if row.has_discount %}
|
||
{{ row.discount_model }}
|
||
{% else %}
|
||
None
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
{% if row.has_discount %}
|
||
<small class="text-muted">
|
||
<strong>Total Units:</strong> {{ row.total_units }}<br>
|
||
<strong>Standard Price:</strong> {{ row.standard_sla_price|floatformat:2 }}<br>
|
||
<strong>Discounted Price:</strong> {{ row.discounted_sla_price|floatformat:2 }}<br>
|
||
<strong>Savings:</strong> {{ row.discount_savings|floatformat:2 }} ({{ row.discount_percentage|floatformat:1 }}%)<br>
|
||
{% if row.discount_breakdown %}
|
||
<strong>Breakdown:</strong><br>
|
||
{% for tier in row.discount_breakdown %}
|
||
{{ tier.tier_range }} units: {{ tier.units }} × {{ tier.rate|floatformat:4 }} = {{ tier.subtotal|floatformat:2 }}<br>
|
||
{% endfor %}
|
||
{% endif %}
|
||
</small>
|
||
{% else %}
|
||
<small class="text-muted">No discount applied</small>
|
||
{% endif %}
|
||
</td>
|
||
{% endif %}
|
||
{% if show_price_comparison %}
|
||
<td>
|
||
<span class="badge">-</span>
|
||
</td>
|
||
{% endif %}
|
||
<td class="final-price-cell fw-bold">{{ row.final_price|floatformat:2 }}</td>
|
||
</tr>
|
||
{% if show_price_comparison and row.external_comparisons %}
|
||
{% for comparison in row.external_comparisons %}
|
||
<tr class="table-light comparison-row">
|
||
<td class="text-muted">{{ comparison.plan_name }}</td>
|
||
<td class="text-muted">{{ comparison.provider }}</td>
|
||
<td class="text-muted">
|
||
{% if comparison.vcpus %}{{ comparison.vcpus }}{% else %}-{% endif %}
|
||
</td>
|
||
<td class="text-muted">
|
||
{% if comparison.ram %}{{ comparison.ram }}{% else %}-{% endif %}
|
||
</td>
|
||
<td class="text-muted">{{ row.term }}</td>
|
||
<td class="text-muted">{{ comparison.currency }}</td>
|
||
<td class="text-muted">-</td>
|
||
<td class="text-muted">-</td>
|
||
<td class="text-muted">-</td>
|
||
<td class="text-muted">-</td>
|
||
<td class="text-muted">-</td>
|
||
{% if show_discount_details %}
|
||
<td class="text-muted">-</td>
|
||
<td class="text-muted">-</td>
|
||
{% endif %}
|
||
<td>
|
||
<small>
|
||
<span class="badge bg-secondary">{% if comparison.source %}<span class="text-muted"><a href="{{ comparison.source }}" target="_blank">{{ comparison.provider }}</a></span>{% else %}{{ comparison.provider }}{% endif %}</span><br>
|
||
{% if comparison.description %}
|
||
<span class="text-muted">{{ comparison.description }}</span><br>
|
||
{% endif %}
|
||
{% if comparison.storage %}
|
||
<span class="text-muted">Storage: {{ comparison.storage }} GB</span><br>
|
||
{% endif %}
|
||
{% if comparison.replicas %}
|
||
<span class="text-muted">Replicas: {{ comparison.replicas }}</span><br>
|
||
{% endif %}
|
||
{% if comparison.ratio %}
|
||
<span class="text-muted">Price ratio: {{ comparison.ratio|floatformat:2 }}x</span><br>
|
||
{% endif %}
|
||
</small>
|
||
</td>
|
||
<td class="fw-bold">
|
||
{{ comparison.amount|floatformat:2 }} {{ comparison.currency }}
|
||
{% if comparison.difference > 0 %}
|
||
<span class="badge bg-success ms-1">+{{ comparison.difference|floatformat:2 }}</span>
|
||
{% elif comparison.difference < 0 %}
|
||
<span class="badge bg-danger ms-1">{{ comparison.difference|floatformat:2 }}</span>
|
||
{% endif %}
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
{% endif %}
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
{# Price Chart #}
|
||
<div class="price-chart mt-3">
|
||
<h5 class="text-muted">Price Chart - Units vs Final Price</h5>
|
||
<div style="height: 400px;">
|
||
<canvas id="chart-{{ group_name|slugify }}-{{ service_level|slugify }}" width="400" height="200"></canvas>
|
||
</div>
|
||
</div>
|
||
|
||
<p class="text-muted"><strong>{{ pricing_data|length }}</strong> variants for {{ service_level }} in {{ group_name }}</p>
|
||
{% else %}
|
||
<p class="text-muted">No pricing variants available for {{ service_level }} in {{ group_name }}.</p>
|
||
{% endif %}
|
||
</div>
|
||
{% empty %}
|
||
<p class="text-muted">No service levels with pricing data found for group: {{ group_name }}.</p>
|
||
{% endfor %}
|
||
</div>
|
||
{% endfor %}
|
||
{% else %}
|
||
<div class="alert alert-info">
|
||
<h4>No pricing data available</h4>
|
||
<p>{% if filter_cloud_provider or filter_service or filter_compute_plan_group or filter_service_level %}No data matches the selected filters. Try adjusting your filter criteria.{% else %}Please ensure you have active compute plans with prices and VSHNAppCat price configurations.{% endif %}</p>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// Auto-submit form when filter dropdowns change
|
||
const filterForm = document.getElementById('filter-form');
|
||
const filterSelects = document.querySelectorAll('.filter-select');
|
||
const discountCheckbox = document.getElementById('discount_details');
|
||
|
||
// Add change event listeners to all filter dropdowns
|
||
filterSelects.forEach(function(select) {
|
||
select.addEventListener('change', function() {
|
||
filterForm.submit();
|
||
});
|
||
});
|
||
|
||
// Add change event listener to discount details checkbox
|
||
discountCheckbox.addEventListener('change', function() {
|
||
filterForm.submit();
|
||
});
|
||
|
||
// Add change event listener to price comparison checkbox
|
||
const priceComparisonCheckbox = document.getElementById('price_comparison');
|
||
priceComparisonCheckbox.addEventListener('change', function() {
|
||
filterForm.submit();
|
||
});
|
||
|
||
// Chart data for each service level
|
||
{% for group_name, service_levels in pricing_data_by_group_and_service_level.items %}
|
||
{% for service_level, pricing_data in service_levels.items %}
|
||
{% if pricing_data %}
|
||
// Prepare data for {{ group_name }} - {{ service_level }}
|
||
const chartData{{ forloop.parentloop.counter }}{{ forloop.counter }} = {
|
||
labels: [{% for row in pricing_data %}{{ row.units }}{% if not forloop.last %}, {% endif %}{% endfor %}],
|
||
datasets: [
|
||
{
|
||
label: 'Final Price',
|
||
data: [{% for row in pricing_data %}{{ row.final_price|floatformat:2 }}{% if not forloop.last %}, {% endif %}{% endfor %}],
|
||
borderColor: 'rgb(75, 192, 192)',
|
||
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
||
tension: 0.1,
|
||
fill: false
|
||
},
|
||
{
|
||
label: 'SLA Price',
|
||
data: [{% for row in pricing_data %}{{ row.sla_price|floatformat:2 }}{% if not forloop.last %}, {% endif %}{% endfor %}],
|
||
borderColor: 'rgb(255, 99, 132)',
|
||
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||
tension: 0.1,
|
||
fill: false
|
||
},
|
||
{
|
||
label: 'Compute Plan Price',
|
||
data: [{% for row in pricing_data %}{{ row.compute_plan_price|floatformat:2 }}{% if not forloop.last %}, {% endif %}{% endfor %}],
|
||
borderColor: 'rgb(54, 162, 235)',
|
||
backgroundColor: 'rgba(54, 162, 235, 0.2)',
|
||
tension: 0.1,
|
||
fill: false
|
||
}
|
||
]
|
||
};
|
||
|
||
// Create chart for {{ group_name }} - {{ service_level }}
|
||
const ctx{{ forloop.parentloop.counter }}{{ forloop.counter }} = document.getElementById('chart-{{ group_name|slugify }}-{{ service_level|slugify }}').getContext('2d');
|
||
new Chart(ctx{{ forloop.parentloop.counter }}{{ forloop.counter }}, {
|
||
type: 'line',
|
||
data: chartData{{ forloop.parentloop.counter }}{{ forloop.counter }},
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
scales: {
|
||
x: {
|
||
title: {
|
||
display: true,
|
||
text: 'Units'
|
||
}
|
||
},
|
||
y: {
|
||
title: {
|
||
display: true,
|
||
text: 'Price ({{ pricing_data.0.currency|default:"CHF" }})'
|
||
},
|
||
beginAtZero: true
|
||
}
|
||
},
|
||
plugins: {
|
||
title: {
|
||
display: true,
|
||
text: '{{ group_name }} - {{ service_level }} Pricing'
|
||
},
|
||
legend: {
|
||
display: true
|
||
}
|
||
},
|
||
elements: {
|
||
point: {
|
||
radius: 4,
|
||
hoverRadius: 6
|
||
}
|
||
}
|
||
}
|
||
});
|
||
{% endif %}
|
||
{% endfor %}
|
||
{% endfor %}
|
||
});
|
||
</script>
|
||
{% endblock %} |