Erstelle die Grundstruktur für die Ceph Max Storage Rechner-Anwendung mit Flask und HTMX. Füge Konfigurationsdateien, Routen, Modelle, Templates und statische Dateien hinzu. Implementiere grundlegende Funktionen zur Berechnung der Speichernutzung und zur PDF-Generierung. Integriere CSRF-Schutz und Logging. Stelle sicher, dass die Anwendung modular und wartbar ist.
This commit is contained in:
488
app/templates/index.html
Normal file
488
app/templates/index.html
Normal file
@ -0,0 +1,488 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user