1234 lines
45 KiB
JavaScript
1234 lines
45 KiB
JavaScript
// Debug-Dashboard JavaScript
|
|
|
|
// Globale Variablen für Charts
|
|
let cpuChart = null;
|
|
let memoryChart = null;
|
|
let diskChart = null;
|
|
let containerStatusChart = null;
|
|
|
|
// Speicher für historische Daten
|
|
const cpuData = {
|
|
labels: Array(20).fill(''),
|
|
datasets: [{
|
|
label: 'CPU-Auslastung (%)',
|
|
data: Array(20).fill(0),
|
|
borderColor: '#3498db',
|
|
backgroundColor: 'rgba(52, 152, 219, 0.2)',
|
|
tension: 0.4,
|
|
fill: true
|
|
}]
|
|
};
|
|
|
|
const memoryData = {
|
|
labels: ['Verwendet', 'Frei'],
|
|
datasets: [{
|
|
data: [0, 100],
|
|
backgroundColor: ['#e74c3c', '#2ecc71'],
|
|
hoverBackgroundColor: ['#c0392b', '#27ae60']
|
|
}]
|
|
};
|
|
|
|
// Gemeinsame Chart-Optionen
|
|
const lineChartOptions = {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
animation: {
|
|
duration: 500
|
|
},
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
max: 100,
|
|
ticks: {
|
|
callback: value => `${value}%`
|
|
}
|
|
}
|
|
},
|
|
plugins: {
|
|
legend: {
|
|
display: true,
|
|
position: 'top'
|
|
}
|
|
}
|
|
};
|
|
|
|
const pieChartOptions = {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: 'right'
|
|
}
|
|
}
|
|
};
|
|
|
|
// Helper-Funktionen
|
|
function showMessage(message, isError = false) {
|
|
const messageEl = document.getElementById('message');
|
|
messageEl.textContent = message;
|
|
messageEl.className = isError ? 'message message-error' : 'message message-success';
|
|
messageEl.style.display = 'block';
|
|
|
|
// Verstecke Nachricht nach 5 Sekunden
|
|
setTimeout(() => {
|
|
messageEl.style.display = 'none';
|
|
}, 5000);
|
|
}
|
|
|
|
function formatBytes(bytes, decimals = 2) {
|
|
if (bytes === 0) return '0 Bytes';
|
|
|
|
const k = 1024;
|
|
const dm = decimals < 0 ? 0 : decimals;
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
|
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
|
}
|
|
|
|
function formatUptime(seconds) {
|
|
const days = Math.floor(seconds / 86400);
|
|
const hours = Math.floor((seconds % 86400) / 3600);
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
|
|
return `${days}d ${hours}h ${minutes}m`;
|
|
}
|
|
|
|
// Dashboard-Initialisierung
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Tab-Wechsel einrichten
|
|
setupTabs();
|
|
|
|
// Charts initialisieren
|
|
initCharts();
|
|
|
|
// Daten laden
|
|
loadSystemMetrics();
|
|
loadDockerStatus();
|
|
refreshNetworkInterfaces();
|
|
refreshActiveConnections();
|
|
loadRouteTable();
|
|
|
|
// Regelmäßige Aktualisierungen
|
|
setInterval(loadSystemMetrics, 5000);
|
|
setInterval(loadDockerStatus, 10000);
|
|
|
|
console.log('Debug-Dashboard initialisiert');
|
|
});
|
|
|
|
// Tab-Funktionalität
|
|
function setupTabs() {
|
|
document.querySelectorAll('.tab').forEach(tab => {
|
|
tab.addEventListener('click', function() {
|
|
// Aktiven Tab-Status wechseln
|
|
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
this.classList.add('active');
|
|
|
|
// Tab-Inhalte wechseln
|
|
const tabId = this.getAttribute('data-tab');
|
|
document.querySelectorAll('.tab-content').forEach(content => {
|
|
content.classList.remove('active');
|
|
});
|
|
document.getElementById(tabId + '-tab').classList.add('active');
|
|
});
|
|
});
|
|
}
|
|
|
|
// Chart-Initialisierung
|
|
function initCharts() {
|
|
// CPU-Chart
|
|
const cpuCtx = document.getElementById('cpu-usage-chart').getContext('2d');
|
|
cpuChart = new Chart(cpuCtx, {
|
|
type: 'line',
|
|
data: cpuData,
|
|
options: lineChartOptions
|
|
});
|
|
|
|
// Memory-Chart
|
|
const memoryCtx = document.getElementById('memory-usage-chart').getContext('2d');
|
|
memoryChart = new Chart(memoryCtx, {
|
|
type: 'doughnut',
|
|
data: memoryData,
|
|
options: pieChartOptions
|
|
});
|
|
|
|
// Disk-Chart (Wird später initialisiert, wenn Daten verfügbar sind)
|
|
|
|
// Container-Status-Chart (Wird später initialisiert, wenn Daten verfügbar sind)
|
|
}
|
|
|
|
// Daten-Lade-Funktionen
|
|
function loadSystemMetrics() {
|
|
fetch('/api/system/metrics')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
updateSystemMetrics(data);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Fehler beim Laden der Systemmetriken:', error);
|
|
});
|
|
}
|
|
|
|
function updateSystemMetrics(data) {
|
|
// CPU-Auslastung aktualisieren
|
|
document.getElementById('cpu-percent').textContent = `${data.cpu_percent.toFixed(1)}%`;
|
|
|
|
// CPU-Chart aktualisieren
|
|
cpuData.datasets[0].data.shift();
|
|
cpuData.datasets[0].data.push(data.cpu_percent);
|
|
cpuChart.update();
|
|
|
|
// RAM-Auslastung aktualisieren
|
|
document.getElementById('memory-percent').textContent = `${data.memory.percent.toFixed(1)}%`;
|
|
document.getElementById('memory-used').textContent = formatBytes(data.memory.used);
|
|
document.getElementById('memory-available').textContent = formatBytes(data.memory.available);
|
|
document.getElementById('memory-total').textContent = formatBytes(data.memory.total);
|
|
|
|
// Memory-Chart aktualisieren
|
|
memoryData.datasets[0].data = [data.memory.percent, 100 - data.memory.percent];
|
|
memoryChart.update();
|
|
|
|
// Disk-Chart aktualisieren oder initialisieren, wenn noch nicht vorhanden
|
|
if (data.disk_usage && data.disk_usage.length > 0) {
|
|
updateDiskChart(data.disk_usage);
|
|
}
|
|
}
|
|
|
|
function updateDiskChart(diskData) {
|
|
if (!diskChart) {
|
|
// Chart initialisieren, wenn noch nicht vorhanden
|
|
const labels = diskData.map(disk => disk.mountpoint);
|
|
const usedData = diskData.map(disk => disk.used);
|
|
const freeData = diskData.map(disk => disk.free);
|
|
|
|
const data = {
|
|
labels: labels,
|
|
datasets: [
|
|
{
|
|
label: 'Verwendet',
|
|
data: usedData,
|
|
backgroundColor: 'rgba(231, 76, 60, 0.7)'
|
|
},
|
|
{
|
|
label: 'Frei',
|
|
data: freeData,
|
|
backgroundColor: 'rgba(46, 204, 113, 0.7)'
|
|
}
|
|
]
|
|
};
|
|
|
|
const options = {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
x: {
|
|
stacked: true
|
|
},
|
|
y: {
|
|
stacked: true,
|
|
ticks: {
|
|
callback: value => formatBytes(value)
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
const diskCtx = document.getElementById('disk-usage-chart').getContext('2d');
|
|
diskChart = new Chart(diskCtx, {
|
|
type: 'bar',
|
|
data: data,
|
|
options: options
|
|
});
|
|
} else {
|
|
// Chart aktualisieren
|
|
diskChart.data.labels = diskData.map(disk => disk.mountpoint);
|
|
diskChart.data.datasets[0].data = diskData.map(disk => disk.used);
|
|
diskChart.data.datasets[1].data = diskData.map(disk => disk.free);
|
|
diskChart.update();
|
|
}
|
|
}
|
|
|
|
// Docker-Informationen laden
|
|
function loadDockerStatus() {
|
|
fetch('/api/docker/status')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
updateDockerStatus(data);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Fehler beim Laden des Docker-Status:', error);
|
|
});
|
|
}
|
|
|
|
function updateDockerStatus(data) {
|
|
// Docker-Version und Info aktualisieren
|
|
document.getElementById('docker-version').textContent = data.info.version || 'Nicht verfügbar';
|
|
document.getElementById('docker-api-version').textContent = data.info.api_version || 'Nicht verfügbar';
|
|
document.getElementById('docker-os').textContent = data.info.os || 'Nicht verfügbar';
|
|
document.getElementById('docker-status').textContent = data.info.status || 'Nicht verfügbar';
|
|
|
|
// Anzahl aktiver Container aktualisieren
|
|
const activeContainers = data.containers.filter(c => c.state === 'running').length;
|
|
document.getElementById('active-containers').textContent = activeContainers;
|
|
|
|
// Container-Status-Chart aktualisieren oder initialisieren
|
|
updateContainerStatusChart(data.containers);
|
|
|
|
// Container-Tabelle aktualisieren
|
|
updateContainerTable(data.containers);
|
|
|
|
// Container-Select für Logs aktualisieren
|
|
updateContainerSelect(data.containers);
|
|
}
|
|
|
|
function updateContainerStatusChart(containers) {
|
|
// Zähle Container nach Status
|
|
const statusCounts = {
|
|
running: 0,
|
|
exited: 0,
|
|
created: 0,
|
|
other: 0
|
|
};
|
|
|
|
containers.forEach(container => {
|
|
if (container.state === 'running') {
|
|
statusCounts.running++;
|
|
} else if (container.state === 'exited') {
|
|
statusCounts.exited++;
|
|
} else if (container.state === 'created') {
|
|
statusCounts.created++;
|
|
} else {
|
|
statusCounts.other++;
|
|
}
|
|
});
|
|
|
|
if (!containerStatusChart) {
|
|
// Chart initialisieren
|
|
const data = {
|
|
labels: ['Laufend', 'Beendet', 'Erstellt', 'Andere'],
|
|
datasets: [{
|
|
data: [
|
|
statusCounts.running,
|
|
statusCounts.exited,
|
|
statusCounts.created,
|
|
statusCounts.other
|
|
],
|
|
backgroundColor: [
|
|
'#2ecc71', // Grün für laufende Container
|
|
'#e74c3c', // Rot für beendete Container
|
|
'#3498db', // Blau für erstellte Container
|
|
'#95a5a6' // Grau für andere Status
|
|
]
|
|
}]
|
|
};
|
|
|
|
const ctx = document.getElementById('container-status-chart').getContext('2d');
|
|
containerStatusChart = new Chart(ctx, {
|
|
type: 'doughnut',
|
|
data: data,
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: 'right'
|
|
}
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
// Chart aktualisieren
|
|
containerStatusChart.data.datasets[0].data = [
|
|
statusCounts.running,
|
|
statusCounts.exited,
|
|
statusCounts.created,
|
|
statusCounts.other
|
|
];
|
|
containerStatusChart.update();
|
|
}
|
|
}
|
|
|
|
function updateContainerTable(containers) {
|
|
const tableBody = document.querySelector('#container-table tbody');
|
|
|
|
if (containers.length === 0) {
|
|
tableBody.innerHTML = '<tr><td colspan="6">Keine Container gefunden</td></tr>';
|
|
return;
|
|
}
|
|
|
|
tableBody.innerHTML = '';
|
|
|
|
containers.forEach(container => {
|
|
const row = document.createElement('tr');
|
|
row.className = `container-row ${container.state}`;
|
|
row.setAttribute('data-id', container.id);
|
|
|
|
// Container-Status-Klasse
|
|
let statusClass = '';
|
|
if (container.state === 'running') {
|
|
statusClass = 'status-good';
|
|
} else if (container.state === 'exited') {
|
|
statusClass = 'status-error';
|
|
} else {
|
|
statusClass = 'status-warning';
|
|
}
|
|
|
|
// Container-Name und Info
|
|
row.innerHTML = `
|
|
<td>
|
|
<div class="container-name">${container.name}</div>
|
|
<div class="container-image">${container.image}</div>
|
|
</td>
|
|
<td><span class="status ${statusClass}">${container.state}</span></td>
|
|
<td>${container.cpu || 'N/A'}</td>
|
|
<td>${container.memory ? formatBytes(container.memory) : 'N/A'}</td>
|
|
<td>${container.ports || 'N/A'}</td>
|
|
<td class="actions">
|
|
<button class="btn btn-sm" onclick="inspectContainer('${container.id}')"><i class="fas fa-info-circle"></i></button>
|
|
<button class="btn btn-sm" onclick="restartContainer('${container.id}')"><i class="fas fa-sync-alt"></i></button>
|
|
<button class="btn btn-sm" onclick="viewContainerLogs('${container.id}')"><i class="fas fa-file-alt"></i></button>
|
|
</td>
|
|
`;
|
|
|
|
tableBody.appendChild(row);
|
|
});
|
|
}
|
|
|
|
function updateContainerSelect(containers) {
|
|
const select = document.getElementById('log-container-select');
|
|
|
|
// Alle Einträge außer dem ersten leeren löschen
|
|
while (select.options.length > 1) {
|
|
select.remove(1);
|
|
}
|
|
|
|
// Container sortieren (laufende zuerst)
|
|
const sortedContainers = [...containers].sort((a, b) => {
|
|
if (a.state === 'running' && b.state !== 'running') return -1;
|
|
if (a.state !== 'running' && b.state === 'running') return 1;
|
|
return a.name.localeCompare(b.name);
|
|
});
|
|
|
|
// Container zum Select hinzufügen
|
|
sortedContainers.forEach(container => {
|
|
const option = document.createElement('option');
|
|
option.value = container.id;
|
|
option.textContent = `${container.name} (${container.state})`;
|
|
|
|
// Laufende Container hervorheben
|
|
if (container.state === 'running') {
|
|
option.className = 'container-running';
|
|
}
|
|
|
|
select.appendChild(option);
|
|
});
|
|
}
|
|
|
|
// Container-Aktionen
|
|
function inspectContainer(containerId) {
|
|
fetch(`/api/docker/inspect/${containerId}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showContainerInspect(data.data);
|
|
} else {
|
|
showMessage(`Fehler: ${data.error}`, true);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showMessage(`Fehler bei der Container-Inspektion: ${error}`, true);
|
|
});
|
|
}
|
|
|
|
function showContainerInspect(inspectData) {
|
|
// Hier können wir ein Modal oder einen anderen Bereich für die Anzeige der Inspektionsdaten implementieren
|
|
const detailsHTML = `
|
|
<div class="inspect-header">
|
|
<h3>${inspectData.name}</h3>
|
|
<div class="inspect-id">${inspectData.id.substring(0, 12)}</div>
|
|
</div>
|
|
<div class="inspect-section">
|
|
<h4>Allgemein</h4>
|
|
<table>
|
|
<tr><th>Image</th><td>${inspectData.image}</td></tr>
|
|
<tr><th>Erstellt</th><td>${new Date(inspectData.created).toLocaleString()}</td></tr>
|
|
<tr><th>Status</th><td>${inspectData.state.Status}</td></tr>
|
|
<tr><th>Gestartet</th><td>${inspectData.state.StartedAt ? new Date(inspectData.state.StartedAt).toLocaleString() : 'Nicht gestartet'}</td></tr>
|
|
</table>
|
|
</div>
|
|
<div class="inspect-section">
|
|
<h4>Netzwerk</h4>
|
|
<div class="inspect-networks">
|
|
${Object.keys(inspectData.networks).map(net => `
|
|
<div class="network-item">
|
|
<div class="network-name">${net}</div>
|
|
<div>IP: ${inspectData.networks[net].IPAddress}</div>
|
|
<div>Gateway: ${inspectData.networks[net].Gateway}</div>
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
</div>
|
|
<div class="inspect-section">
|
|
<h4>Ports</h4>
|
|
<div class="inspect-ports">
|
|
${inspectData.ports ? Object.keys(inspectData.ports).map(port => `
|
|
<div class="port-item">
|
|
${port} → ${inspectData.ports[port] ? inspectData.ports[port].map(p => `${p.HostIp}:${p.HostPort}`).join(', ') : 'Nicht gemappt'}
|
|
</div>
|
|
`).join('') : 'Keine Ports'}
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Hier könnten wir ein Modal anzeigen oder die Daten in einen bestimmten Bereich einfügen
|
|
// Für dieses Beispiel verwenden wir ein einfaches Alert, aber in Produktion sollte ein ordentliches Modal verwendet werden
|
|
alert(`Container-Details:\n${inspectData.name}\n${inspectData.image}\nStatus: ${inspectData.state.Status}`);
|
|
}
|
|
|
|
function restartContainer(containerId) {
|
|
if (confirm('Möchten Sie diesen Container wirklich neu starten?')) {
|
|
fetch(`/api/docker/restart/${containerId}`, {
|
|
method: 'POST'
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showMessage(`Container erfolgreich neu gestartet`, false);
|
|
setTimeout(() => loadDockerStatus(), 2000);
|
|
} else {
|
|
showMessage(`Fehler: ${data.error}`, true);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showMessage(`Fehler beim Neustart des Containers: ${error}`, true);
|
|
});
|
|
}
|
|
}
|
|
|
|
function viewContainerLogs(containerId) {
|
|
// Container im Log-Select auswählen
|
|
document.getElementById('log-container-select').value = containerId;
|
|
|
|
// Logs laden
|
|
loadContainerLogs();
|
|
}
|
|
|
|
function loadContainerLogs() {
|
|
const containerId = document.getElementById('log-container-select').value;
|
|
const filter = document.getElementById('log-filter').value;
|
|
|
|
if (!containerId) {
|
|
showMessage('Bitte wählen Sie einen Container aus', true);
|
|
return;
|
|
}
|
|
|
|
const logsContainer = document.getElementById('container-logs');
|
|
logsContainer.innerHTML = '<div class="loading">Lade Logs...</div>';
|
|
|
|
fetch(`/api/docker/logs/${containerId}${filter ? `?filter=${filter}` : ''}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
if (data.logs.length === 0) {
|
|
logsContainer.innerHTML = '<div class="log-placeholder">Keine Logs gefunden</div>';
|
|
} else {
|
|
const logLines = data.logs.split('\n');
|
|
|
|
// Logs mit Syntax-Highlighting und Fehlerhervorhebung anzeigen
|
|
logsContainer.innerHTML = `
|
|
<div class="log-header">
|
|
<div>Logs für Container: <strong>${data.container_name}</strong></div>
|
|
<div>Zeilen: ${logLines.length}</div>
|
|
</div>
|
|
<pre class="logs">${formatLogs(logLines)}</pre>
|
|
`;
|
|
}
|
|
} else {
|
|
logsContainer.innerHTML = `<div class="error">Fehler: ${data.error}</div>`;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
logsContainer.innerHTML = `<div class="error">Fehler beim Laden der Logs: ${error}</div>`;
|
|
});
|
|
}
|
|
|
|
function formatLogs(logLines) {
|
|
return logLines.map(line => {
|
|
// Zeitstempel hervorheben
|
|
line = line.replace(/(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d+Z)/g, '<span class="log-timestamp">$1</span>');
|
|
|
|
// Fehler hervorheben
|
|
if (/error|exception|fail|critical/i.test(line)) {
|
|
return `<div class="log-line log-error">${line}</div>`;
|
|
}
|
|
|
|
// Warnungen hervorheben
|
|
if (/warning|warn/i.test(line)) {
|
|
return `<div class="log-line log-warning">${line}</div>`;
|
|
}
|
|
|
|
// Info-Meldungen hervorheben
|
|
if (/info|information/i.test(line)) {
|
|
return `<div class="log-line log-info">${line}</div>`;
|
|
}
|
|
|
|
return `<div class="log-line">${line}</div>`;
|
|
}).join('');
|
|
}
|
|
|
|
function downloadContainerLogs() {
|
|
const containerId = document.getElementById('log-container-select').value;
|
|
|
|
if (!containerId) {
|
|
showMessage('Bitte wählen Sie einen Container aus', true);
|
|
return;
|
|
}
|
|
|
|
// Direkter Download über einen Link
|
|
window.location.href = `/api/docker/logs/download/${containerId}`;
|
|
}
|
|
|
|
// Netzwerk-Funktionen
|
|
function refreshNetworkInterfaces() {
|
|
const container = document.getElementById('network-interfaces');
|
|
container.innerHTML = '<div class="loading">Lade Netzwerkschnittstellen...</div>';
|
|
|
|
fetch('/api/network/interfaces')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
renderNetworkInterfaces(data.interfaces);
|
|
} else {
|
|
container.innerHTML = `<div class="error">Fehler: ${data.error}</div>`;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
container.innerHTML = `<div class="error">Fehler beim Laden der Netzwerkschnittstellen: ${error}</div>`;
|
|
});
|
|
}
|
|
|
|
function renderNetworkInterfaces(interfaces) {
|
|
const container = document.getElementById('network-interfaces');
|
|
|
|
if (interfaces.length === 0) {
|
|
container.innerHTML = '<div class="empty">Keine Netzwerkschnittstellen gefunden</div>';
|
|
return;
|
|
}
|
|
|
|
let html = '';
|
|
|
|
interfaces.forEach(iface => {
|
|
html += `
|
|
<div class="interface-item">
|
|
<div class="interface-header">
|
|
<strong>${iface.name}</strong>
|
|
<span class="interface-mac">${iface.mac}</span>
|
|
</div>
|
|
|
|
<div class="interface-ips">
|
|
<h4>IPv4-Adressen</h4>
|
|
${iface.ipv4.length > 0 ? iface.ipv4.map(ip => `
|
|
<div class="ip-item">
|
|
<div><strong>IP:</strong> ${ip.addr}</div>
|
|
<div><strong>Netzmaske:</strong> ${ip.netmask}</div>
|
|
<div><strong>Broadcast:</strong> ${ip.broadcast || 'N/A'}</div>
|
|
</div>
|
|
`).join('') : '<div>Keine IPv4-Adressen</div>'}
|
|
</div>
|
|
|
|
${iface.stats !== 'Nicht verfügbar' ? `
|
|
<div class="interface-stats">
|
|
<h4>Statistiken</h4>
|
|
<div class="stats-row">
|
|
<div><strong>Gesendet:</strong> ${formatBytes(iface.stats.bytes_sent)}</div>
|
|
<div><strong>Empfangen:</strong> ${formatBytes(iface.stats.bytes_recv)}</div>
|
|
</div>
|
|
<div class="stats-row">
|
|
<div><strong>Pakete gesendet:</strong> ${iface.stats.packets_sent}</div>
|
|
<div><strong>Pakete empfangen:</strong> ${iface.stats.packets_recv}</div>
|
|
</div>
|
|
<div class="stats-row">
|
|
<div><strong>Fehler (Ein):</strong> ${iface.stats.errin}</div>
|
|
<div><strong>Fehler (Aus):</strong> ${iface.stats.errout}</div>
|
|
</div>
|
|
</div>
|
|
` : ''}
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
function refreshActiveConnections() {
|
|
const tableBody = document.querySelector('#connections-table tbody');
|
|
tableBody.innerHTML = '<tr><td colspan="4">Lade aktive Verbindungen...</td></tr>';
|
|
|
|
fetch('/api/network/active-connections')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
renderActiveConnections(data.connections);
|
|
} else {
|
|
tableBody.innerHTML = `<tr><td colspan="4">Fehler: ${data.error}</td></tr>`;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
tableBody.innerHTML = `<tr><td colspan="4">Fehler beim Laden der aktiven Verbindungen: ${error}</td></tr>`;
|
|
});
|
|
}
|
|
|
|
function renderActiveConnections(connections) {
|
|
const tableBody = document.querySelector('#connections-table tbody');
|
|
|
|
if (connections.length === 0) {
|
|
tableBody.innerHTML = '<tr><td colspan="4">Keine aktiven Verbindungen gefunden</td></tr>';
|
|
return;
|
|
}
|
|
|
|
tableBody.innerHTML = '';
|
|
|
|
connections.forEach(conn => {
|
|
const row = document.createElement('tr');
|
|
|
|
row.innerHTML = `
|
|
<td>${conn.local_address}</td>
|
|
<td>${conn.remote_address}</td>
|
|
<td>${conn.status}</td>
|
|
<td>${conn.process ? `${conn.process.name} (PID: ${conn.pid})` : `PID: ${conn.pid || 'N/A'}`}</td>
|
|
`;
|
|
|
|
tableBody.appendChild(row);
|
|
});
|
|
}
|
|
|
|
function loadRouteTable() {
|
|
const container = document.getElementById('route-table');
|
|
container.textContent = 'Lade Routing-Tabelle...';
|
|
|
|
fetch('/api/network/route-table')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
container.textContent = data.route_table;
|
|
} else {
|
|
container.textContent = `Fehler: ${data.error}`;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
container.textContent = `Fehler beim Laden der Routing-Tabelle: ${error}`;
|
|
});
|
|
}
|
|
|
|
// Diagnose-Funktionen
|
|
function pingHost() {
|
|
const host = document.getElementById('ping-host').value;
|
|
if (!host) {
|
|
showMessage('Bitte geben Sie einen Host ein', true);
|
|
return;
|
|
}
|
|
|
|
document.getElementById('ping-result').innerHTML = '<div class="loading">Ping wird durchgeführt...</div>';
|
|
document.getElementById('ping-result').className = 'status';
|
|
|
|
fetch(`/ping/${host}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
let html = `<h4>Ping-Ergebnisse für ${host}</h4>`;
|
|
html += `<pre class="ping-output">${data.output}</pre>`;
|
|
|
|
document.getElementById('ping-result').innerHTML = html;
|
|
document.getElementById('ping-result').className = data.success ? 'status status-good' : 'status status-error';
|
|
})
|
|
.catch(error => {
|
|
document.getElementById('ping-result').innerHTML = `<div class="error">Fehler beim Durchführen des Ping-Tests: ${error}</div>`;
|
|
document.getElementById('ping-result').className = 'status status-error';
|
|
});
|
|
}
|
|
|
|
function tracerouteHost() {
|
|
const host = document.getElementById('traceroute-host').value;
|
|
if (!host) {
|
|
showMessage('Bitte geben Sie einen Host ein', true);
|
|
return;
|
|
}
|
|
|
|
document.getElementById('traceroute-result').innerHTML = '<div class="loading">Traceroute wird durchgeführt...</div>';
|
|
document.getElementById('traceroute-result').className = 'status';
|
|
|
|
fetch(`/traceroute/${host}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
let html = `<h4>Traceroute-Ergebnisse für ${host}</h4>`;
|
|
html += `<pre class="traceroute-output">${data.output}</pre>`;
|
|
|
|
if (data.error) {
|
|
html += `<div class="error">Fehler: ${data.error}</div>`;
|
|
}
|
|
|
|
document.getElementById('traceroute-result').innerHTML = html;
|
|
document.getElementById('traceroute-result').className = 'status';
|
|
})
|
|
.catch(error => {
|
|
document.getElementById('traceroute-result').innerHTML = `<div class="error">Fehler beim Durchführen des Traceroute: ${error}</div>`;
|
|
document.getElementById('traceroute-result').className = 'status status-error';
|
|
});
|
|
}
|
|
|
|
function dnsLookup() {
|
|
const host = document.getElementById('dns-host').value;
|
|
if (!host) {
|
|
showMessage('Bitte geben Sie einen Hostnamen ein', true);
|
|
return;
|
|
}
|
|
|
|
document.getElementById('dns-result').innerHTML = '<div class="loading">DNS-Abfrage wird durchgeführt...</div>';
|
|
document.getElementById('dns-result').className = 'status';
|
|
|
|
fetch(`/nslookup/${host}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
let html = `<h4>DNS-Abfrageergebnisse für ${host}</h4>`;
|
|
html += `<pre class="dns-output">${data.output}</pre>`;
|
|
|
|
if (data.error) {
|
|
html += `<div class="error">Fehler: ${data.error}</div>`;
|
|
}
|
|
|
|
document.getElementById('dns-result').innerHTML = html;
|
|
document.getElementById('dns-result').className = 'status';
|
|
})
|
|
.catch(error => {
|
|
document.getElementById('dns-result').innerHTML = `<div class="error">Fehler bei der DNS-Abfrage: ${error}</div>`;
|
|
document.getElementById('dns-result').className = 'status status-error';
|
|
});
|
|
}
|
|
|
|
function startPortScan() {
|
|
const host = document.getElementById('port-scan-host').value;
|
|
const portRange = document.getElementById('port-scan-range').value;
|
|
|
|
if (!host) {
|
|
showMessage('Bitte geben Sie einen Host ein', true);
|
|
return;
|
|
}
|
|
|
|
document.getElementById('port-scan-status').innerHTML = '<div class="loading">Port-Scan wird gestartet...</div>';
|
|
document.getElementById('port-scan-status').className = 'status';
|
|
|
|
// Tabelle leeren
|
|
document.querySelector('#port-scan-table tbody').innerHTML = '';
|
|
|
|
fetch('/api/network/scan-ports', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
host: host,
|
|
port_range: portRange
|
|
})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
document.getElementById('port-scan-status').innerHTML = data.message;
|
|
document.getElementById('port-scan-status').className = 'status status-good';
|
|
|
|
// Status-Polling starten
|
|
pollPortScanStatus();
|
|
} else {
|
|
document.getElementById('port-scan-status').innerHTML = `<div class="error">Fehler: ${data.error}</div>`;
|
|
document.getElementById('port-scan-status').className = 'status status-error';
|
|
}
|
|
})
|
|
.catch(error => {
|
|
document.getElementById('port-scan-status').innerHTML = `<div class="error">Fehler beim Starten des Port-Scans: ${error}</div>`;
|
|
document.getElementById('port-scan-status').className = 'status status-error';
|
|
});
|
|
}
|
|
|
|
function checkPortScanStatus() {
|
|
fetch('/api/network/scan-status')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
if (data.status === 'running') {
|
|
document.getElementById('port-scan-status').innerHTML = '<div class="loading">Port-Scan läuft...</div>';
|
|
document.getElementById('port-scan-status').className = 'status status-warning';
|
|
} else if (data.status === 'completed' && data.results) {
|
|
renderPortScanResults(data.results);
|
|
} else {
|
|
document.getElementById('port-scan-status').innerHTML = data.message;
|
|
document.getElementById('port-scan-status').className = 'status';
|
|
}
|
|
} else {
|
|
document.getElementById('port-scan-status').innerHTML = `<div class="error">Fehler: ${data.error}</div>`;
|
|
document.getElementById('port-scan-status').className = 'status status-error';
|
|
}
|
|
})
|
|
.catch(error => {
|
|
document.getElementById('port-scan-status').innerHTML = `<div class="error">Fehler beim Abrufen des Port-Scan-Status: ${error}</div>`;
|
|
document.getElementById('port-scan-status').className = 'status status-error';
|
|
});
|
|
}
|
|
|
|
function pollPortScanStatus() {
|
|
const intervalId = setInterval(() => {
|
|
fetch('/api/network/scan-status')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
if (data.status === 'running') {
|
|
document.getElementById('port-scan-status').innerHTML = '<div class="loading">Port-Scan läuft...</div>';
|
|
document.getElementById('port-scan-status').className = 'status status-warning';
|
|
} else {
|
|
// Scan ist abgeschlossen oder anderweitig beendet
|
|
clearInterval(intervalId);
|
|
|
|
if (data.status === 'completed' && data.results) {
|
|
renderPortScanResults(data.results);
|
|
} else {
|
|
document.getElementById('port-scan-status').innerHTML = data.message;
|
|
document.getElementById('port-scan-status').className = 'status';
|
|
}
|
|
}
|
|
} else {
|
|
clearInterval(intervalId);
|
|
document.getElementById('port-scan-status').innerHTML = `<div class="error">Fehler: ${data.error}</div>`;
|
|
document.getElementById('port-scan-status').className = 'status status-error';
|
|
}
|
|
})
|
|
.catch(error => {
|
|
clearInterval(intervalId);
|
|
document.getElementById('port-scan-status').innerHTML = `<div class="error">Fehler beim Abrufen des Port-Scan-Status: ${error}</div>`;
|
|
document.getElementById('port-scan-status').className = 'status status-error';
|
|
});
|
|
}, 1000);
|
|
}
|
|
|
|
function renderPortScanResults(results) {
|
|
const tableBody = document.querySelector('#port-scan-table tbody');
|
|
tableBody.innerHTML = '';
|
|
|
|
document.getElementById('port-scan-status').innerHTML = `
|
|
<div>Scan für ${results.host} abgeschlossen</div>
|
|
<div>Ports: ${results.start_port}-${results.end_port}</div>
|
|
<div>Zeit: ${new Date(results.timestamp).toLocaleString()}</div>
|
|
<div>Offene Ports: ${results.open_ports.length}</div>
|
|
`;
|
|
document.getElementById('port-scan-status').className = 'status status-good';
|
|
|
|
if (results.open_ports.length === 0) {
|
|
tableBody.innerHTML = '<tr><td colspan="3">Keine offenen Ports gefunden</td></tr>';
|
|
return;
|
|
}
|
|
|
|
results.open_ports.forEach(port => {
|
|
const row = document.createElement('tr');
|
|
|
|
row.innerHTML = `
|
|
<td>${port.port}</td>
|
|
<td>${port.status}</td>
|
|
<td>${port.service}</td>
|
|
`;
|
|
|
|
tableBody.appendChild(row);
|
|
});
|
|
}
|
|
|
|
function loadLogs() {
|
|
const logType = document.getElementById('log-type').value;
|
|
const lines = document.getElementById('log-lines').value;
|
|
|
|
const container = document.getElementById('log-content');
|
|
container.innerHTML = '<div class="loading">Lade Logs...</div>';
|
|
|
|
fetch(`/api/logs/tail/${logType}?lines=${lines}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
if (data.entries.length === 0) {
|
|
container.innerHTML = '<div class="log-placeholder">Keine Logs gefunden</div>';
|
|
} else {
|
|
container.innerHTML = `
|
|
<div class="log-header">
|
|
<div>Logs für: <strong>${logType}</strong></div>
|
|
<div>Zeilen: ${data.count}</div>
|
|
</div>
|
|
<pre class="logs">${formatLogEntries(data.entries)}</pre>
|
|
`;
|
|
}
|
|
} else {
|
|
container.innerHTML = `<div class="error">Fehler: ${data.error}</div>`;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
container.innerHTML = `<div class="error">Fehler beim Laden der Logs: ${error}</div>`;
|
|
});
|
|
}
|
|
|
|
function formatLogEntries(entries) {
|
|
return entries.map(line => {
|
|
// Zeitstempel hervorheben
|
|
line = line.replace(/(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(,\d+)?)/g, '<span class="log-timestamp">$1</span>');
|
|
|
|
// Fehler hervorheben
|
|
if (/error|exception|fail|critical/i.test(line)) {
|
|
return `<div class="log-line log-error">${line}</div>`;
|
|
}
|
|
|
|
// Warnungen hervorheben
|
|
if (/warning|warn/i.test(line)) {
|
|
return `<div class="log-line log-warning">${line}</div>`;
|
|
}
|
|
|
|
// Info-Meldungen hervorheben
|
|
if (/info|information/i.test(line)) {
|
|
return `<div class="log-line log-info">${line}</div>`;
|
|
}
|
|
|
|
// Debug-Meldungen hervorheben
|
|
if (/debug/i.test(line)) {
|
|
return `<div class="log-line log-debug">${line}</div>`;
|
|
}
|
|
|
|
return `<div class="log-line">${line}</div>`;
|
|
}).join('');
|
|
}
|
|
|
|
function analyzeLogs() {
|
|
const logType = document.getElementById('log-type').value;
|
|
|
|
const container = document.getElementById('log-content');
|
|
container.innerHTML = '<div class="loading">Analysiere Logs...</div>';
|
|
|
|
fetch(`/api/logs/analyze?type=${logType}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
if (data.analysis.error_count === 0 && data.analysis.warning_count === 0) {
|
|
container.innerHTML = '<div class="log-placeholder">Keine Fehler oder Warnungen in den Logs gefunden</div>';
|
|
} else {
|
|
let html = `
|
|
<div class="log-header">
|
|
<div>Log-Analyse für: <strong>${logType}</strong></div>
|
|
<div>Fehler: ${data.analysis.error_count}, Warnungen: ${data.analysis.warning_count}</div>
|
|
</div>
|
|
`;
|
|
|
|
if (data.analysis.errors.length > 0) {
|
|
html += '<h4>Fehler</h4>';
|
|
html += '<div class="error-list">';
|
|
data.analysis.errors.forEach(error => {
|
|
html += `
|
|
<div class="error-item">
|
|
<div class="error-time">${error.timestamp}</div>
|
|
<div class="error-message">${error.message}</div>
|
|
</div>
|
|
`;
|
|
});
|
|
html += '</div>';
|
|
}
|
|
|
|
if (data.analysis.warnings.length > 0) {
|
|
html += '<h4>Warnungen</h4>';
|
|
html += '<div class="warning-list">';
|
|
data.analysis.warnings.forEach(warning => {
|
|
html += `
|
|
<div class="warning-item">
|
|
<div class="warning-time">${warning.timestamp}</div>
|
|
<div class="warning-message">${warning.message}</div>
|
|
</div>
|
|
`;
|
|
});
|
|
html += '</div>';
|
|
}
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
} else {
|
|
container.innerHTML = `<div class="error">Fehler: ${data.error}</div>`;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
container.innerHTML = `<div class="error">Fehler bei der Log-Analyse: ${error}</div>`;
|
|
});
|
|
}
|
|
|
|
function checkHealth() {
|
|
const banner = document.getElementById('systemHealthBanner');
|
|
banner.style.display = 'flex';
|
|
banner.className = 'system-health-banner checking';
|
|
banner.innerHTML = `
|
|
<div class="health-icon"><i class="fas fa-spinner fa-spin"></i></div>
|
|
<div class="health-status">Systemstatus wird geprüft...</div>
|
|
`;
|
|
|
|
fetch('/healthcheck')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
let statusClass = '';
|
|
let icon = '';
|
|
|
|
if (data.status === 'healthy') {
|
|
statusClass = 'healthy';
|
|
icon = '<i class="fas fa-check-circle"></i>';
|
|
} else if (data.status === 'warning') {
|
|
statusClass = 'warning';
|
|
icon = '<i class="fas fa-exclamation-triangle"></i>';
|
|
} else {
|
|
statusClass = 'critical';
|
|
icon = '<i class="fas fa-times-circle"></i>';
|
|
}
|
|
|
|
let statusHTML = `
|
|
<div class="health-icon">${icon}</div>
|
|
<div class="health-status">
|
|
<div class="health-status-title">Systemstatus: ${data.status.toUpperCase()}</div>
|
|
<div class="health-details">
|
|
`;
|
|
|
|
// Einzelne Komponenten hinzufügen
|
|
Object.keys(data.checks).forEach(component => {
|
|
const check = data.checks[component];
|
|
let componentIcon = '';
|
|
|
|
if (check.overall === 'healthy') {
|
|
componentIcon = '<i class="fas fa-check-circle health-good"></i>';
|
|
} else if (check.overall === 'warning') {
|
|
componentIcon = '<i class="fas fa-exclamation-triangle health-warning"></i>';
|
|
} else {
|
|
componentIcon = '<i class="fas fa-times-circle health-critical"></i>';
|
|
}
|
|
|
|
statusHTML += `<div>${componentIcon} ${component}: ${check.status}</div>`;
|
|
});
|
|
|
|
statusHTML += `
|
|
</div>
|
|
</div>
|
|
<button class="btn btn-sm" onclick="document.getElementById('systemHealthBanner').style.display = 'none'">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
`;
|
|
|
|
banner.className = `system-health-banner ${statusClass}`;
|
|
banner.innerHTML = statusHTML;
|
|
|
|
// Banner nach 10 Sekunden ausblenden, wenn es nicht kritisch ist
|
|
if (data.status !== 'critical') {
|
|
setTimeout(() => {
|
|
if (banner.className.includes(statusClass)) {
|
|
banner.style.display = 'none';
|
|
}
|
|
}, 10000);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
banner.className = 'system-health-banner critical';
|
|
banner.innerHTML = `
|
|
<div class="health-icon"><i class="fas fa-times-circle"></i></div>
|
|
<div class="health-status">Fehler bei der Systemstatus-Prüfung: ${error}</div>
|
|
<button class="btn btn-sm" onclick="document.getElementById('systemHealthBanner').style.display = 'none'">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
`;
|
|
});
|
|
}
|
|
|
|
function refreshPage() {
|
|
window.location.reload();
|
|
}
|
|
|
|
// Konfiguration
|
|
function saveConfig() {
|
|
const config = {
|
|
backend_hostname: document.getElementById('backend_hostname').value,
|
|
backend_port: parseInt(document.getElementById('backend_port').value, 10),
|
|
frontend_hostname: document.getElementById('frontend_hostname').value,
|
|
frontend_port: parseInt(document.getElementById('frontend_port').value, 10)
|
|
};
|
|
|
|
fetch('/save-config', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(config)
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showMessage('Konfiguration erfolgreich gespeichert', false);
|
|
|
|
// Nach kurzer Verzögerung die Seite neu laden
|
|
setTimeout(() => {
|
|
window.location.reload();
|
|
}, 1500);
|
|
} else {
|
|
showMessage(`Fehler: ${data.message}`, true);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showMessage(`Fehler: ${error}`, true);
|
|
});
|
|
}
|
|
|
|
function testConnection() {
|
|
const config = {
|
|
backend_hostname: document.getElementById('backend_hostname').value,
|
|
backend_port: parseInt(document.getElementById('backend_port').value, 10),
|
|
frontend_hostname: document.getElementById('frontend_hostname').value,
|
|
frontend_port: parseInt(document.getElementById('frontend_port').value, 10)
|
|
};
|
|
|
|
fetch('/test-connection', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(config)
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
let message = 'Backend: ';
|
|
message += data.results.backend.ping ? 'Ping erfolgreich' : 'Ping fehlgeschlagen';
|
|
message += ', ';
|
|
message += data.results.backend.connection ? 'Verbindung erfolgreich' : 'Verbindung fehlgeschlagen';
|
|
message += ' | Frontend: ';
|
|
message += data.results.frontend.ping ? 'Ping erfolgreich' : 'Ping fehlgeschlagen';
|
|
message += ', ';
|
|
message += data.results.frontend.connection ? 'Verbindung erfolgreich' : 'Verbindung fehlgeschlagen';
|
|
|
|
showMessage(message, false);
|
|
} else {
|
|
showMessage(`Fehler: ${data.message}`, true);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showMessage(`Fehler: ${error}`, true);
|
|
});
|
|
}
|
|
|
|
function syncFrontend() {
|
|
fetch('/sync-frontend', {
|
|
method: 'POST'
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showMessage('Frontend erfolgreich synchronisiert', false);
|
|
} else {
|
|
showMessage(`Fehler: ${data.message}`, true);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showMessage(`Fehler: ${error}`, true);
|
|
});
|
|
}
|