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:
230
app/static/js/script.js
Normal file
230
app/static/js/script.js
Normal file
@ -0,0 +1,230 @@
|
||||
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;
|
||||
}
|
||||
|
||||
// Validiere die Daten vor dem Senden
|
||||
if (!this.nodes || this.nodes.length === 0) {
|
||||
alert('Bitte fügen Sie mindestens einen Node hinzu.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validiere die Node-Daten
|
||||
for (let i = 0; i < this.nodes.length; i++) {
|
||||
const node = this.nodes[i];
|
||||
if (!node.osd_count || node.osd_count < 1) {
|
||||
alert(`Bitte geben Sie eine gültige Anzahl von OSDs für Node ${i + 1} ein.`);
|
||||
return;
|
||||
}
|
||||
if (!node.osd_size_gb || node.osd_size_gb <= 0) {
|
||||
alert(`Bitte geben Sie eine gültige OSD-Größe für Node ${i + 1} ein.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const data = {
|
||||
replication_type: this.replicationType,
|
||||
replicas: parseInt(this.replicas),
|
||||
min_size: parseInt(this.minSize),
|
||||
k: parseInt(this.k),
|
||||
m: parseInt(this.m),
|
||||
storage_unit: this.storageUnit,
|
||||
nodes: this.nodes.map(node => ({
|
||||
osd_count: parseInt(node.osd_count),
|
||||
osd_size_gb: parseFloat(this.getOsdSizeInGB(parseFloat(node.osd_size_gb)))
|
||||
})),
|
||||
result: {
|
||||
raw_total: parseFloat(this.result.raw_total),
|
||||
max_usage_percent: parseFloat(this.result.max_usage_percent),
|
||||
max_usage_gb: parseFloat(this.result.max_usage_gb),
|
||||
max_usage_tb: parseFloat(this.result.max_usage_tb),
|
||||
node_failure_tolerance: Boolean(this.result.node_failure_tolerance),
|
||||
node_failure_info: String(this.result.node_failure_info || ''),
|
||||
multi_failure_tolerance: Boolean(this.result.multi_failure_tolerance),
|
||||
max_failure_nodes: parseInt(this.result.max_failure_nodes || 0),
|
||||
osd_failure_tolerance: Boolean(this.result.osd_failure_tolerance),
|
||||
osd_failure_info: String(this.result.osd_failure_info || '')
|
||||
}
|
||||
};
|
||||
|
||||
// Zeige Ladeindikator
|
||||
const button = document.querySelector('button[onclick="generatePDF()"]');
|
||||
const originalText = button.innerHTML;
|
||||
button.innerHTML = '<span 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>Generiere PDF...</span>';
|
||||
button.disabled = true;
|
||||
|
||||
// Hole CSRF-Token
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
||||
if (!csrfToken) {
|
||||
alert('CSRF-Token nicht gefunden. Bitte laden Sie die Seite neu.');
|
||||
button.innerHTML = originalText;
|
||||
button.disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/generate-pdf', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': csrfToken
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
return response.json().then(err => {
|
||||
throw new Error(err.error || `HTTP error! status: ${response.status}`);
|
||||
});
|
||||
}
|
||||
return response.blob();
|
||||
})
|
||||
.then(blob => {
|
||||
if (!blob || blob.size === 0) {
|
||||
throw new Error('Die generierte PDF-Datei ist leer');
|
||||
}
|
||||
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(`Fehler bei der PDF-Generierung: ${error.message}`);
|
||||
})
|
||||
.finally(() => {
|
||||
// Stelle den Button wieder her
|
||||
button.innerHTML = originalText;
|
||||
button.disabled = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user