488 lines
29 KiB
HTML
488 lines
29 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de" x-data="{ darkMode: localStorage.getItem('darkMode') === 'true' || (!localStorage.getItem('darkMode') && window.matchMedia('(prefers-color-scheme: dark)').matches) }"
|
|
:class="{ 'dark': darkMode }"
|
|
class="transition-colors duration-500 ease-in-out">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
|
<title>Ceph Max Storage Rechner</title>
|
|
<!-- HTMX für interaktive Elemente -->
|
|
<script src="https://unpkg.com/htmx.org@1.9.4"></script>
|
|
<!-- Tailwind CSS für modernes Design -->
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script>
|
|
tailwind.config = {
|
|
darkMode: 'class',
|
|
theme: {
|
|
extend: {
|
|
colors: {
|
|
dark: {
|
|
bg: '#1a1a1a',
|
|
card: '#2d2d2d',
|
|
border: '#404040',
|
|
text: '#e5e5e5',
|
|
input: '#404040',
|
|
hover: '#505050'
|
|
}
|
|
},
|
|
transitionProperty: {
|
|
'all': 'all',
|
|
'colors': 'color, background-color, border-color, text-decoration-color, fill, stroke',
|
|
'opacity': 'opacity',
|
|
'shadow': 'box-shadow',
|
|
'transform': 'transform',
|
|
},
|
|
transitionDuration: {
|
|
'200': '200ms',
|
|
'300': '300ms',
|
|
'500': '500ms',
|
|
'700': '700ms',
|
|
},
|
|
transitionTimingFunction: {
|
|
'ease-in-out': 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
<!-- Alpine.js für reaktive Benutzeroberfläche -->
|
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.0/dist/cdn.min.js"></script>
|
|
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
|
|
</head>
|
|
<body class="bg-gray-100 dark:bg-dark-bg min-h-screen transition-all duration-500 ease-in-out" x-data="cephCalculator()">
|
|
<div class="w-full px-4 py-8 bg-gray-100 dark:bg-dark-bg min-h-screen transition-all duration-500 ease-in-out">
|
|
<div class="max-w-7xl mx-auto">
|
|
<!-- Theme Toggle -->
|
|
<div class="fixed top-4 right-4 z-50">
|
|
<button @click="darkMode = !darkMode; localStorage.setItem('darkMode', darkMode)"
|
|
class="p-2 rounded-lg bg-white dark:bg-dark-card shadow-lg hover:bg-gray-100 dark:hover:bg-dark-hover transition-all duration-500 ease-in-out transform hover:scale-105">
|
|
<!-- Sonne Icon für Light Mode -->
|
|
<svg x-show="!darkMode" class="w-6 h-6 text-gray-800 transition-opacity duration-500 ease-in-out" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
|
|
</svg>
|
|
<!-- Mond Icon für Dark Mode -->
|
|
<svg x-show="darkMode" class="w-6 h-6 text-gray-200 transition-opacity duration-500 ease-in-out" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<header class="mb-8">
|
|
<h1 class="text-3xl font-bold text-center text-blue-700 dark:text-blue-400">Ceph Max Storage Kapazitätsrechner</h1>
|
|
<p class="text-gray-600 dark:text-gray-400 text-center mt-2">Berechnen Sie die maximale Speichernutzung Ihres Ceph-Clusters</p>
|
|
</header>
|
|
|
|
<main class="bg-white dark:bg-dark-card rounded-lg shadow-lg p-6 lg:p-8 transition-colors duration-200">
|
|
<!-- Replikationstyp -->
|
|
<div class="mb-6">
|
|
<h2 class="text-xl font-semibold mb-3 text-gray-800 dark:text-dark-text">Replikationstyp</h2>
|
|
<div class="flex flex-wrap gap-4">
|
|
<label class="flex items-center">
|
|
<input type="radio" name="replication_type" value="replication"
|
|
x-model="replicationType" class="mr-2 h-5 w-5">
|
|
<span class="text-gray-700 dark:text-dark-text">Replikation</span>
|
|
</label>
|
|
<label class="flex items-center">
|
|
<input type="radio" name="replication_type" value="erasure_coding"
|
|
x-model="replicationType" class="mr-2 h-5 w-5">
|
|
<span class="text-gray-700 dark:text-dark-text">Erasure Coding</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Replikationskonfiguration -->
|
|
<div class="mb-6" x-show="replicationType === 'replication'">
|
|
<h2 class="text-xl font-semibold mb-3 text-gray-800 dark:text-dark-text">Replikationskonfiguration</h2>
|
|
<div class="flex items-center mb-3">
|
|
<label class="w-32 text-gray-700 dark:text-dark-text">Anzahl Replikate:</label>
|
|
<div class="relative group">
|
|
<input type="number" x-model="replicas" min="1" max="10"
|
|
class="border rounded p-2 w-20 text-center bg-white dark:bg-dark-input dark:border-dark-border dark:text-dark-text">
|
|
<div class="absolute hidden group-hover:block bg-gray-800 text-white p-2 rounded text-sm">
|
|
Anzahl der Kopien, die von jedem Datenblock erstellt werden
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center mb-3">
|
|
<label class="w-32 text-gray-700 dark:text-dark-text">Min. Replikate (min_size):</label>
|
|
<input type="number" x-model="minSize" min="1" :max="replicas"
|
|
class="border rounded p-2 w-20 text-center bg-white dark:bg-dark-input dark:border-dark-border dark:text-dark-text">
|
|
<span class="ml-2 text-xs text-gray-500 dark:text-gray-400">Minimale Anzahl von Kopien für I/O-Operationen</span>
|
|
</div>
|
|
<div class="flex items-center mb-3">
|
|
<label class="w-32 text-gray-700 dark:text-dark-text">Speichereinheit:</label>
|
|
<select x-model="storageUnit" class="border rounded p-2 bg-white dark:bg-dark-input dark:border-dark-border dark:text-dark-text">
|
|
<option value="GB">GB</option>
|
|
<option value="TB">TB</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Erasure Coding Konfiguration -->
|
|
<div class="mb-6" x-show="replicationType === 'erasure_coding'">
|
|
<h2 class="text-xl font-semibold mb-3 text-gray-800 dark:text-dark-text">Erasure Coding Konfiguration</h2>
|
|
<div class="flex flex-wrap gap-6">
|
|
<div class="flex items-center">
|
|
<label class="w-32 text-gray-700 dark:text-dark-text">Datenchunks (k):</label>
|
|
<input type="number" x-model="k" min="1" max="20"
|
|
class="border rounded p-2 w-20 text-center bg-white dark:bg-dark-input dark:border-dark-border dark:text-dark-text">
|
|
</div>
|
|
<div class="flex items-center">
|
|
<label class="w-32 text-gray-700 dark:text-dark-text">Codierungschunks (m):</label>
|
|
<input type="number" x-model="m" min="1" max="10"
|
|
class="border rounded p-2 w-20 text-center bg-white dark:bg-dark-input dark:border-dark-border dark:text-dark-text">
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center mt-4">
|
|
<label class="w-32 text-gray-700 dark:text-dark-text">Speichereinheit:</label>
|
|
<select x-model="storageUnit" class="border rounded p-2 bg-white dark:bg-dark-input dark:border-dark-border dark:text-dark-text">
|
|
<option value="GB">GB</option>
|
|
<option value="TB">TB</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Nodes und OSDs -->
|
|
<div class="mb-6">
|
|
<h2 class="text-xl font-semibold mb-3 text-gray-800 dark:text-dark-text">Nodes und OSDs</h2>
|
|
|
|
<template x-for="(node, nodeIndex) in nodes" :key="nodeIndex">
|
|
<div class="border p-4 rounded-lg mb-4 bg-gray-50 dark:bg-dark-input dark:border-dark-border">
|
|
<div class="flex flex-wrap items-center gap-4 mb-3">
|
|
<h3 class="font-medium dark:text-dark-text" x-text="`Node ${nodeIndex + 1}`"></h3>
|
|
<button @click="removeNode(nodeIndex)"
|
|
class="bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600 ml-auto">
|
|
Entfernen
|
|
</button>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div class="form-group">
|
|
<label class="block text-gray-700 dark:text-dark-text mb-2">OSD Anzahl:</label>
|
|
<input type="number" x-model="node.osd_count" min="1"
|
|
class="border rounded p-2 w-full text-center bg-white dark:bg-dark-input dark:border-dark-border dark:text-dark-text">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="block text-gray-700 dark:text-dark-text mb-2">OSD Größe:</label>
|
|
<div class="flex items-center">
|
|
<input type="number" x-model="node.osd_size_gb" min="1"
|
|
class="border rounded p-2 w-full text-center bg-white dark:bg-dark-input dark:border-dark-border dark:text-dark-text">
|
|
<span class="ml-2 text-gray-700 dark:text-dark-text" x-text="storageUnit"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<button @click="addNode()"
|
|
class="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600">
|
|
Node hinzufügen
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Berechnungsbutton -->
|
|
<div class="flex justify-center mt-8 space-x-4">
|
|
<button @click="calculateCapacity()"
|
|
class="bg-blue-600 text-white px-6 py-3 rounded-lg font-semibold
|
|
shadow-md hover:bg-blue-700 transition duration-300"
|
|
:disabled="isCalculating">
|
|
<span x-show="!isCalculating">Kapazität berechnen</span>
|
|
<span x-show="isCalculating" class="flex items-center">
|
|
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
</svg>
|
|
Berechne...
|
|
</span>
|
|
</button>
|
|
<button @click="exportConfig()"
|
|
class="bg-green-600 text-white px-6 py-3 rounded-lg font-semibold
|
|
shadow-md hover:bg-green-700 transition duration-300">
|
|
Konfiguration exportieren
|
|
</button>
|
|
<label class="bg-purple-600 text-white px-6 py-3 rounded-lg font-semibold
|
|
shadow-md hover:bg-purple-700 transition duration-300 cursor-pointer">
|
|
Konfiguration importieren
|
|
<input type="file" @change="importConfig($event)" class="hidden" accept=".json">
|
|
</label>
|
|
<button @click="generatePDF()"
|
|
class="bg-red-600 text-white px-6 py-3 rounded-lg font-semibold
|
|
shadow-md hover:bg-red-700 transition duration-300"
|
|
:disabled="!result.raw_total">
|
|
PDF Report generieren
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Ergebnisbereich -->
|
|
<div class="mt-8" x-show="result.raw_total > 0">
|
|
<h2 class="text-xl font-semibold mb-4 text-gray-800 dark:text-dark-text">Ergebnis</h2>
|
|
<div class="bg-blue-50 dark:bg-dark-input p-6 rounded-lg shadow border border-blue-200 dark:border-dark-border">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div class="p-3">
|
|
<p class="text-gray-600 dark:text-gray-400 mb-1">Gesamt-Rohspeicher:</p>
|
|
<p class="text-2xl font-bold text-gray-800 dark:text-dark-text">
|
|
<span x-text="storageUnit === 'TB' ? `${(result.raw_total / 1024).toFixed(2)} TB` : `${result.raw_total} GB`"></span>
|
|
</p>
|
|
</div>
|
|
<div class="p-3 bg-green-50 dark:bg-dark-input rounded-lg border border-green-100 dark:border-dark-border">
|
|
<p class="text-gray-600 dark:text-gray-400 mb-1">Maximale empfohlene Speichernutzung:</p>
|
|
<p class="text-2xl font-bold text-green-600 dark:text-green-400" x-text="`${result.max_usage_percent}%`"></p>
|
|
<p class="text-xl font-semibold text-gray-800 dark:text-dark-text mt-2">
|
|
<span x-text="storageUnit === 'TB' ? `${result.max_usage_tb} TB` : `${result.max_usage_gb} GB`"></span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Ausfalltoleranz-Informationen -->
|
|
<div class="mt-4 border-t border-blue-200 dark:border-dark-border pt-4">
|
|
<h3 class="text-lg font-semibold mb-3 text-gray-800 dark:text-dark-text">Ausfalltoleranz-Analyse</h3>
|
|
|
|
<!-- Node-Ausfalltoleranz -->
|
|
<div class="p-3 rounded-lg mb-3"
|
|
:class="result.node_failure_tolerance ? 'bg-green-50 dark:bg-dark-input border border-green-200 dark:border-dark-border' : 'bg-red-50 dark:bg-dark-input border border-red-200 dark:border-dark-border'">
|
|
<div class="flex items-center mb-2">
|
|
<span class="font-semibold text-gray-800 dark:text-dark-text mr-2">Node-Ausfalltoleranz:</span>
|
|
<span class="px-2 py-1 rounded text-xs font-semibold"
|
|
:class="result.node_failure_tolerance ? 'bg-green-600 text-white' : 'bg-red-600 text-white'"
|
|
x-text="result.node_failure_tolerance ? 'Vorhanden' : 'Nicht vorhanden'"></span>
|
|
<span x-show="result.multi_failure_tolerance" class="ml-2 px-2 py-1 rounded text-xs font-semibold bg-blue-600 text-white"
|
|
x-text="`Multi-Node (${result.max_failure_nodes})`"></span>
|
|
</div>
|
|
<p class="text-sm dark:text-gray-300" x-text="result.node_failure_info"></p>
|
|
<div class="mt-2 grid grid-cols-1 md:grid-cols-2 gap-2 text-sm">
|
|
<div>
|
|
<span class="text-gray-600 dark:text-gray-400">Größter Node:</span>
|
|
<span class="font-medium ml-1 dark:text-dark-text" x-text="storageUnit === 'TB' ? `${(result.largest_node_gb / 1024).toFixed(2)} TB` : `${result.largest_node_gb} GB`"></span>
|
|
</div>
|
|
<div>
|
|
<span class="text-gray-600 dark:text-gray-400">Rohspeicher nach Nodeausfall:</span>
|
|
<span class="font-medium ml-1 dark:text-dark-text" x-text="storageUnit === 'TB' ? `${(result.raw_after_failure_gb / 1024).toFixed(2)} TB` : `${result.raw_after_failure_gb} GB`"></span>
|
|
</div>
|
|
<div>
|
|
<span class="text-gray-600 dark:text-gray-400">Nutzbarer Speicher nach Ausfall:</span>
|
|
<span class="font-medium ml-1 dark:text-dark-text" x-text="storageUnit === 'TB' ? `${(result.usable_after_failure_gb / 1024).toFixed(2)} TB` : `${result.usable_after_failure_gb} GB`"></span>
|
|
</div>
|
|
<div x-show="result.multi_failure_tolerance || result.max_failure_nodes > 1">
|
|
<span class="text-gray-600 dark:text-gray-400">Nutzbarer Speicher nach max. Ausfällen:</span>
|
|
<span class="font-medium ml-1 dark:text-dark-text" x-text="storageUnit === 'TB' ? `${(result.usable_after_max_failures_gb / 1024).toFixed(2)} TB` : `${result.usable_after_max_failures_gb} GB`"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Min-Size Info -->
|
|
<div class="mt-3 p-2 bg-blue-100 dark:bg-dark-input rounded text-sm" x-show="result.min_size">
|
|
<p class="dark:text-gray-300">
|
|
<span class="font-semibold">Min-Size:</span>
|
|
<span x-text="result.min_size"></span> -
|
|
Diese Einstellung bestimmt die minimale Anzahl von Replikaten für I/O-Operationen.
|
|
Der Cluster kann bis zu <span x-text="result.max_failure_nodes"></span> Nodes verlieren,
|
|
bevor die Daten unzugänglich werden.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- OSD-Ausfalltoleranz -->
|
|
<div class="p-3 rounded-lg"
|
|
:class="result.osd_failure_tolerance ? 'bg-green-50 dark:bg-dark-input border border-green-200 dark:border-dark-border' : 'bg-yellow-50 dark:bg-dark-input border border-yellow-200 dark:border-dark-border'">
|
|
<div class="flex items-center mb-2">
|
|
<span class="font-semibold text-gray-800 dark:text-dark-text mr-2">OSD-Ausfalltoleranz:</span>
|
|
<span class="px-2 py-1 rounded text-xs font-semibold"
|
|
:class="result.osd_failure_tolerance ? 'bg-green-600 text-white' : 'bg-yellow-600 text-white'"
|
|
x-text="result.osd_failure_tolerance ? 'Vorhanden' : 'Eingeschränkt'"></span>
|
|
</div>
|
|
<p class="text-sm dark:text-gray-300" x-text="result.osd_failure_info"></p>
|
|
</div>
|
|
|
|
<!-- Empfehlungen basierend auf Ceph-Dokumentation -->
|
|
<div class="mt-4 p-3 bg-blue-50 dark:bg-dark-input rounded-lg border border-blue-200 dark:border-dark-border">
|
|
<h4 class="font-semibold text-gray-800 dark:text-dark-text mb-2">Empfehlungen gemäß Ceph-Dokumentation:</h4>
|
|
<ul class="list-disc list-inside text-sm space-y-1 dark:text-gray-300">
|
|
<li>Stelle sicher, dass nach Nodeausfällen genügend Kapazität vorhanden ist, um die 'full ratio' nicht zu erreichen.</li>
|
|
<li>Bei Replikation sollte die Anzahl der Nodes größer sein als der Replikationsfaktor.</li>
|
|
<li>Bei Erasure Coding sollte die Anzahl der Nodes größer sein als k+m (Datenchunks + Codierungschunks).</li>
|
|
<li>Die Performance bei der Wiederherstellung hängt von der Netzwerk- und Speichergeschwindigkeit ab.</li>
|
|
<li>Beachte, dass nach einem Nodeausfall das Wiederausbalancieren des Clusters länger dauert, je größer der ausgefallene Node ist.</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<footer class="mt-8 text-center text-gray-500 dark:text-gray-400 text-sm">
|
|
<p>Basierend auf Ceph-Dokumentation | Ergebnisse dienen nur als Richtwert</p>
|
|
</footer>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function cephCalculator() {
|
|
return {
|
|
replicationType: 'replication',
|
|
replicas: 3,
|
|
minSize: 2,
|
|
k: 4,
|
|
m: 2,
|
|
storageUnit: 'TB',
|
|
nodes: [
|
|
{
|
|
osd_count: 4,
|
|
osd_size_gb: 1000
|
|
}
|
|
],
|
|
result: {
|
|
max_usage_percent: 0,
|
|
max_usage_gb: 0,
|
|
max_usage_tb: 0,
|
|
raw_total: 0
|
|
},
|
|
isCalculating: false,
|
|
|
|
init() {
|
|
const savedConfig = localStorage.getItem('lastConfig');
|
|
if (savedConfig) {
|
|
const config = JSON.parse(savedConfig);
|
|
Object.assign(this, config);
|
|
}
|
|
},
|
|
|
|
addNode() {
|
|
this.nodes.push({
|
|
osd_count: 4,
|
|
osd_size_gb: this.storageUnit === 'TB' ? 1000 : 1
|
|
});
|
|
},
|
|
|
|
removeNode(nodeIndex) {
|
|
this.nodes.splice(nodeIndex, 1);
|
|
if (this.nodes.length === 0) {
|
|
this.addNode();
|
|
}
|
|
},
|
|
|
|
getOsdSizeInGB(size) {
|
|
return this.storageUnit === 'TB' ? size * 1024 : size;
|
|
},
|
|
|
|
calculateCapacity() {
|
|
const data = {
|
|
replication_type: this.replicationType,
|
|
replicas: parseInt(this.replicas),
|
|
k: parseInt(this.k),
|
|
m: parseInt(this.m),
|
|
nodes: this.nodes.map(node => ({
|
|
osd_count: parseInt(node.osd_count),
|
|
osd_size_gb: this.getOsdSizeInGB(parseFloat(node.osd_size_gb))
|
|
})),
|
|
min_size: parseInt(this.minSize),
|
|
storage_unit: this.storageUnit
|
|
};
|
|
|
|
this.isCalculating = true;
|
|
|
|
fetch('/calculate', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content')
|
|
},
|
|
body: JSON.stringify(data)
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
this.result = data;
|
|
})
|
|
.catch(error => {
|
|
console.error('Fehler bei der Berechnung:', error);
|
|
alert('Es ist ein Fehler bei der Berechnung aufgetreten. Bitte versuchen Sie es erneut.');
|
|
})
|
|
.finally(() => {
|
|
this.isCalculating = false;
|
|
});
|
|
},
|
|
|
|
exportConfig() {
|
|
const config = {
|
|
replicationType: this.replicationType,
|
|
replicas: this.replicas,
|
|
minSize: this.minSize,
|
|
k: this.k,
|
|
m: this.m,
|
|
storageUnit: this.storageUnit,
|
|
nodes: this.nodes,
|
|
result: this.result
|
|
};
|
|
|
|
const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = 'ceph-config.json';
|
|
a.click();
|
|
URL.revokeObjectURL(url);
|
|
},
|
|
|
|
importConfig(event) {
|
|
const file = event.target.files[0];
|
|
if (!file) return;
|
|
|
|
const reader = new FileReader();
|
|
reader.onload = (e) => {
|
|
try {
|
|
const config = JSON.parse(e.target.result);
|
|
Object.assign(this, config);
|
|
// Berechne automatisch nach dem Import
|
|
this.calculateCapacity();
|
|
} catch (error) {
|
|
alert('Fehler beim Importieren der Konfiguration. Bitte überprüfen Sie das Dateiformat.');
|
|
}
|
|
};
|
|
reader.readAsText(file);
|
|
},
|
|
|
|
generatePDF() {
|
|
if (!this.result.raw_total) {
|
|
alert('Bitte führen Sie zuerst eine Berechnung durch.');
|
|
return;
|
|
}
|
|
|
|
const data = {
|
|
replication_type: this.replicationType,
|
|
replicas: this.replicas,
|
|
min_size: this.minSize,
|
|
k: this.k,
|
|
m: this.m,
|
|
storage_unit: this.storageUnit,
|
|
nodes: this.nodes,
|
|
result: this.result
|
|
};
|
|
|
|
fetch('/generate-pdf', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content')
|
|
},
|
|
body: JSON.stringify(data)
|
|
})
|
|
.then(response => response.blob())
|
|
.then(blob => {
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = 'ceph-report.pdf';
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
document.body.removeChild(a);
|
|
})
|
|
.catch(error => {
|
|
console.error('Fehler bei der PDF-Generierung:', error);
|
|
alert('Es ist ein Fehler bei der PDF-Generierung aufgetreten. Bitte versuchen Sie es erneut.');
|
|
});
|
|
}
|
|
};
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |