"feat: Added debug server and related components for improved development experience"
This commit is contained in:
18
frontend/debug-server/package.json
Normal file
18
frontend/debug-server/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "myp-frontend-debug-server",
|
||||
"version": "1.0.0",
|
||||
"description": "Debug-Server für das MYP Frontend",
|
||||
"main": "src/app.js",
|
||||
"scripts": {
|
||||
"start": "node src/app.js",
|
||||
"dev": "nodemon src/app.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.6.2",
|
||||
"ejs": "^3.1.9",
|
||||
"express": "^4.18.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.1"
|
||||
}
|
||||
}
|
417
frontend/debug-server/public/css/style.css
Normal file
417
frontend/debug-server/public/css/style.css
Normal file
@@ -0,0 +1,417 @@
|
||||
/* Variablen */
|
||||
:root {
|
||||
--primary-color: #3f51b5;
|
||||
--secondary-color: #283593;
|
||||
--accent-color: #ff4081;
|
||||
--background-color: #f5f5f5;
|
||||
--card-color: #ffffff;
|
||||
--text-color: #333333;
|
||||
--text-light: #757575;
|
||||
--border-color: #dddddd;
|
||||
--success-color: #4caf50;
|
||||
--warning-color: #ff9800;
|
||||
--danger-color: #f44336;
|
||||
--shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
--card-shadow: 0 3px 6px rgba(0, 0, 0, 0.16);
|
||||
}
|
||||
|
||||
/* Grundlegende Stile */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: var(--text-color);
|
||||
background-color: var(--background-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
header {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
padding: 1rem 2rem;
|
||||
box-shadow: var(--shadow);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.server-info {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
nav {
|
||||
background-color: var(--secondary-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 0.5rem 1rem;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 99;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.nav-button {
|
||||
background: none;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
border: none;
|
||||
padding: 0.75rem 1.25rem;
|
||||
margin: 0 0.25rem;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.nav-button:hover {
|
||||
color: white;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.nav-button.active {
|
||||
color: white;
|
||||
background-color: var(--primary-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
main {
|
||||
flex: 1;
|
||||
padding: 2rem;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.panel {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.panel.active {
|
||||
display: block;
|
||||
animation: fadeIn 0.3s;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 1.5rem;
|
||||
color: var(--secondary-color);
|
||||
border-bottom: 2px solid var(--primary-color);
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.card-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--card-color);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: var(--card-shadow);
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.card.full-width {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-bottom: 1rem;
|
||||
color: var(--primary-color);
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
/* Loader */
|
||||
.loader {
|
||||
text-align: center;
|
||||
margin: 2rem 0;
|
||||
color: var(--text-light);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Fortschrittsbalken */
|
||||
.progress-container {
|
||||
margin-top: 1rem;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 4px;
|
||||
height: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background-color: var(--primary-color);
|
||||
width: 0%;
|
||||
transition: width 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
/* Tool-Karten */
|
||||
.tool-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tool-input {
|
||||
display: flex;
|
||||
margin-bottom: 1rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.tool-input input {
|
||||
flex: 1;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.tool-input button {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.tool-input button:hover {
|
||||
background-color: var(--secondary-color);
|
||||
}
|
||||
|
||||
.result-box {
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
padding: 1rem;
|
||||
overflow: auto;
|
||||
min-height: 150px;
|
||||
max-height: 300px;
|
||||
font-family: 'Consolas', 'Courier New', monospace;
|
||||
font-size: 0.9rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Tabellen */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 0.75rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
font-weight: bold;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
/* Status-Anzeigen */
|
||||
.status {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-online {
|
||||
background-color: var(--success-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-offline {
|
||||
background-color: var(--danger-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-warning {
|
||||
background-color: var(--warning-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Echtzeit-Monitor */
|
||||
.gauge-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.gauge {
|
||||
position: relative;
|
||||
width: 150px;
|
||||
height: 75px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.gauge-body {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-top-left-radius: 100px;
|
||||
border-top-right-radius: 100px;
|
||||
background-color: #e0e0e0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.gauge-fill {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 0%;
|
||||
background-color: var(--primary-color);
|
||||
transition: height 0.5s;
|
||||
}
|
||||
|
||||
.gauge-cover {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
margin-left: 5%;
|
||||
margin-bottom: 5%;
|
||||
background-color: var(--card-color);
|
||||
border-top-left-radius: 80px;
|
||||
border-top-right-radius: 80px;
|
||||
}
|
||||
|
||||
.gauge-value {
|
||||
position: absolute;
|
||||
bottom: -25px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* CPU-Cores */
|
||||
.cpu-core {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.cpu-core-label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.cpu-core-bar {
|
||||
height: 6px;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.cpu-core-fill {
|
||||
height: 100%;
|
||||
background-color: var(--primary-color);
|
||||
border-radius: 3px;
|
||||
transition: width 0.5s;
|
||||
}
|
||||
|
||||
/* Network Chart */
|
||||
canvas {
|
||||
margin-top: 1rem;
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
footer {
|
||||
background-color: var(--secondary-color);
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
margin-top: auto;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
header {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.server-info {
|
||||
margin-top: 1rem;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.card-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.nav-button {
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.gauge {
|
||||
width: 120px;
|
||||
height: 60px;
|
||||
}
|
||||
}
|
505
frontend/debug-server/public/js/script.js
Normal file
505
frontend/debug-server/public/js/script.js
Normal file
@@ -0,0 +1,505 @@
|
||||
// DOM-Element-Referenzen
|
||||
const navButtons = document.querySelectorAll('.nav-button');
|
||||
const panels = document.querySelectorAll('.panel');
|
||||
|
||||
// Panel-Navigation
|
||||
navButtons.forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
const panelId = button.getAttribute('data-panel');
|
||||
|
||||
// Aktiven Button und Panel wechseln
|
||||
navButtons.forEach(btn => btn.classList.remove('active'));
|
||||
panels.forEach(panel => panel.classList.remove('active'));
|
||||
|
||||
button.classList.add('active');
|
||||
document.getElementById(panelId).classList.add('active');
|
||||
|
||||
// Lade Panel-Daten, wenn sie noch nicht geladen wurden
|
||||
if (panelId === 'system' && document.getElementById('system-container').style.display === 'none') {
|
||||
loadSystemInfo();
|
||||
} else if (panelId === 'network' && document.getElementById('network-container').style.display === 'none') {
|
||||
loadNetworkInfo();
|
||||
} else if (panelId === 'services' && document.getElementById('services-container').style.display === 'none') {
|
||||
loadServicesInfo();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// API-Anfragen
|
||||
async function fetchData(endpoint) {
|
||||
try {
|
||||
const response = await fetch(endpoint);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP-Fehler: ${response.status}`);
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error(`Fehler beim Abrufen von ${endpoint}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// System-Informationen laden
|
||||
async function loadSystemInfo() {
|
||||
const loader = document.getElementById('system-loader');
|
||||
const container = document.getElementById('system-container');
|
||||
|
||||
loader.style.display = 'block';
|
||||
container.style.display = 'none';
|
||||
|
||||
const data = await fetchData('/api/system');
|
||||
if (!data) {
|
||||
loader.textContent = 'Fehler beim Laden der Systemdaten';
|
||||
return;
|
||||
}
|
||||
|
||||
// Betriebssystem-Info
|
||||
document.getElementById('os-info').innerHTML = `
|
||||
<p><strong>Plattform:</strong> ${data.os.platform}</p>
|
||||
<p><strong>Distribution:</strong> ${data.os.distro}</p>
|
||||
<p><strong>Version:</strong> ${data.os.release}</p>
|
||||
<p><strong>Architektur:</strong> ${data.os.arch}</p>
|
||||
<p><strong>Betriebszeit:</strong> ${data.os.uptime}</p>
|
||||
`;
|
||||
|
||||
// CPU-Info
|
||||
document.getElementById('cpu-info').innerHTML = `
|
||||
<p><strong>Hersteller:</strong> ${data.cpu.manufacturer}</p>
|
||||
<p><strong>Modell:</strong> ${data.cpu.brand}</p>
|
||||
<p><strong>Geschwindigkeit:</strong> ${data.cpu.speed} GHz</p>
|
||||
<p><strong>Kerne:</strong> ${data.cpu.cores} (${data.cpu.physicalCores} physisch)</p>
|
||||
`;
|
||||
|
||||
// Speicher-Info
|
||||
document.getElementById('memory-info').innerHTML = `
|
||||
<p><strong>Gesamt:</strong> ${data.memory.total}</p>
|
||||
<p><strong>Verwendet:</strong> ${data.memory.used} (${data.memory.usedPercent}%)</p>
|
||||
<p><strong>Frei:</strong> ${data.memory.free}</p>
|
||||
`;
|
||||
|
||||
document.getElementById('memory-bar').style.width = `${data.memory.usedPercent}%`;
|
||||
document.getElementById('memory-bar').style.backgroundColor = getColorByPercentage(data.memory.usedPercent);
|
||||
|
||||
// Festplatten-Info
|
||||
let diskHTML = '<table><tr><th>Laufwerk</th><th>Typ</th><th>Größe</th><th>Verwendet</th><th>Verfügbar</th><th>Nutzung</th></tr>';
|
||||
|
||||
data.filesystem.forEach(fs => {
|
||||
diskHTML += `
|
||||
<tr>
|
||||
<td>${fs.mount}</td>
|
||||
<td>${fs.type}</td>
|
||||
<td>${fs.size}</td>
|
||||
<td>${fs.used}</td>
|
||||
<td>${fs.available}</td>
|
||||
<td>
|
||||
<div class="progress-container" style="margin: 0; width: 100%">
|
||||
<div class="progress-bar" style="width: ${fs.usePercent}%; background-color: ${getColorByPercentage(fs.usePercent)}"></div>
|
||||
</div>
|
||||
${fs.usePercent}%
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
diskHTML += '</table>';
|
||||
document.getElementById('disk-info').innerHTML = diskHTML;
|
||||
|
||||
// UI anzeigen
|
||||
loader.style.display = 'none';
|
||||
container.style.display = 'grid';
|
||||
}
|
||||
|
||||
// Netzwerk-Informationen laden
|
||||
async function loadNetworkInfo() {
|
||||
const loader = document.getElementById('network-loader');
|
||||
const container = document.getElementById('network-container');
|
||||
|
||||
loader.style.display = 'block';
|
||||
container.style.display = 'none';
|
||||
|
||||
const data = await fetchData('/api/network');
|
||||
if (!data) {
|
||||
loader.textContent = 'Fehler beim Laden der Netzwerkdaten';
|
||||
return;
|
||||
}
|
||||
|
||||
// Netzwerkschnittstellen
|
||||
let interfacesHTML = '<table><tr><th>Name</th><th>Status</th><th>IPv4</th><th>IPv6</th><th>MAC</th><th>Typ</th><th>DHCP</th></tr>';
|
||||
|
||||
data.interfaces.forEach(iface => {
|
||||
interfacesHTML += `
|
||||
<tr>
|
||||
<td>${iface.iface}</td>
|
||||
<td><span class="status ${iface.operstate === 'up' ? 'status-online' : 'status-offline'}">${iface.operstate}</span></td>
|
||||
<td>${iface.ip4 || '-'}</td>
|
||||
<td>${iface.ip6 || '-'}</td>
|
||||
<td>${iface.mac || '-'}</td>
|
||||
<td>${iface.type || '-'}</td>
|
||||
<td>${iface.dhcp ? 'Ja' : 'Nein'}</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
interfacesHTML += '</table>';
|
||||
document.getElementById('network-interfaces').innerHTML = interfacesHTML;
|
||||
|
||||
// DNS-Server
|
||||
let dnsHTML = '<ul>';
|
||||
data.dns.forEach(server => {
|
||||
dnsHTML += `<li>${server}</li>`;
|
||||
});
|
||||
dnsHTML += '</ul>';
|
||||
document.getElementById('dns-servers').innerHTML = dnsHTML;
|
||||
|
||||
// Gateway
|
||||
document.getElementById('default-gateway').innerHTML = `<p>${data.gateway || 'Nicht verfügbar'}</p>`;
|
||||
|
||||
// Netzwerkstatistiken
|
||||
let statsHTML = '<table><tr><th>Schnittstelle</th><th>Empfangen</th><th>Gesendet</th><th>Empfangen/s</th><th>Gesendet/s</th></tr>';
|
||||
|
||||
data.stats.forEach(stat => {
|
||||
statsHTML += `
|
||||
<tr>
|
||||
<td>${stat.iface}</td>
|
||||
<td>${stat.rx_bytes}</td>
|
||||
<td>${stat.tx_bytes}</td>
|
||||
<td>${stat.rx_sec}</td>
|
||||
<td>${stat.tx_sec}</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
statsHTML += '</table>';
|
||||
document.getElementById('network-stats').innerHTML = statsHTML;
|
||||
|
||||
// UI anzeigen
|
||||
loader.style.display = 'none';
|
||||
container.style.display = 'grid';
|
||||
}
|
||||
|
||||
// Dienst-Informationen laden
|
||||
async function loadServicesInfo() {
|
||||
const loader = document.getElementById('services-loader');
|
||||
const container = document.getElementById('services-container');
|
||||
|
||||
loader.style.display = 'block';
|
||||
container.style.display = 'none';
|
||||
|
||||
const data = await fetchData('/api/services');
|
||||
if (!data) {
|
||||
loader.textContent = 'Fehler beim Laden der Dienstdaten';
|
||||
return;
|
||||
}
|
||||
|
||||
// Frontend-Status
|
||||
document.getElementById('frontend-status').innerHTML = `
|
||||
<p>Status: <span class="status ${data.frontend.status === 'online' ? 'status-online' : 'status-offline'}">${data.frontend.status}</span></p>
|
||||
<p><strong>Name:</strong> ${data.frontend.name}</p>
|
||||
<p><strong>Host:</strong> ${data.frontend.host}</p>
|
||||
<p><strong>Port:</strong> ${data.frontend.port}</p>
|
||||
`;
|
||||
|
||||
// Backend-Status
|
||||
document.getElementById('backend-status').innerHTML = `
|
||||
<p>Status: <span class="status ${data.backend.status === 'online' ? 'status-online' : 'status-offline'}">${data.backend.status}</span></p>
|
||||
<p><strong>Name:</strong> ${data.backend.name}</p>
|
||||
<p><strong>Host:</strong> ${data.backend.host}</p>
|
||||
<p><strong>Port:</strong> ${data.backend.port}</p>
|
||||
`;
|
||||
|
||||
// Docker-Container
|
||||
if (data.docker.containers && data.docker.containers.length > 0) {
|
||||
let containersHTML = '<table><tr><th>ID</th><th>Name</th><th>Image</th><th>Status</th><th>Ports</th></tr>';
|
||||
|
||||
data.docker.containers.forEach(container => {
|
||||
containersHTML += `
|
||||
<tr>
|
||||
<td>${container.id}</td>
|
||||
<td>${container.name}</td>
|
||||
<td>${container.image}</td>
|
||||
<td>${container.status}</td>
|
||||
<td>${container.ports}</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
containersHTML += '</table>';
|
||||
document.getElementById('docker-container-list').innerHTML = containersHTML;
|
||||
} else {
|
||||
document.getElementById('docker-container-list').innerHTML = '<p>Keine Docker-Container gefunden</p>';
|
||||
}
|
||||
|
||||
// UI anzeigen
|
||||
loader.style.display = 'none';
|
||||
container.style.display = 'grid';
|
||||
}
|
||||
|
||||
// Netzwerk-Tools
|
||||
document.getElementById('ping-button').addEventListener('click', async () => {
|
||||
const hostInput = document.getElementById('ping-host');
|
||||
const resultBox = document.getElementById('ping-result');
|
||||
const host = hostInput.value.trim();
|
||||
|
||||
if (!host) {
|
||||
resultBox.textContent = 'Bitte geben Sie einen Hostnamen oder eine IP-Adresse ein';
|
||||
return;
|
||||
}
|
||||
|
||||
resultBox.textContent = 'Ping wird ausgeführt...';
|
||||
|
||||
const data = await fetchData(`/api/ping/${encodeURIComponent(host)}`);
|
||||
if (!data) {
|
||||
resultBox.textContent = 'Fehler beim Ausführen des Ping-Befehls';
|
||||
return;
|
||||
}
|
||||
|
||||
resultBox.textContent = data.output || data.error || 'Keine Ausgabe';
|
||||
});
|
||||
|
||||
document.getElementById('traceroute-button').addEventListener('click', async () => {
|
||||
const hostInput = document.getElementById('traceroute-host');
|
||||
const resultBox = document.getElementById('traceroute-result');
|
||||
const host = hostInput.value.trim();
|
||||
|
||||
if (!host) {
|
||||
resultBox.textContent = 'Bitte geben Sie einen Hostnamen oder eine IP-Adresse ein';
|
||||
return;
|
||||
}
|
||||
|
||||
resultBox.textContent = 'Traceroute wird ausgeführt...';
|
||||
|
||||
const data = await fetchData(`/api/traceroute/${encodeURIComponent(host)}`);
|
||||
if (!data) {
|
||||
resultBox.textContent = 'Fehler beim Ausführen des Traceroute-Befehls';
|
||||
return;
|
||||
}
|
||||
|
||||
resultBox.textContent = data.output || data.error || 'Keine Ausgabe';
|
||||
});
|
||||
|
||||
document.getElementById('nslookup-button').addEventListener('click', async () => {
|
||||
const hostInput = document.getElementById('nslookup-host');
|
||||
const resultBox = document.getElementById('nslookup-result');
|
||||
const host = hostInput.value.trim();
|
||||
|
||||
if (!host) {
|
||||
resultBox.textContent = 'Bitte geben Sie einen Hostnamen oder eine IP-Adresse ein';
|
||||
return;
|
||||
}
|
||||
|
||||
resultBox.textContent = 'DNS-Abfrage wird ausgeführt...';
|
||||
|
||||
const data = await fetchData(`/api/nslookup/${encodeURIComponent(host)}`);
|
||||
if (!data) {
|
||||
resultBox.textContent = 'Fehler beim Ausführen des NSLookup-Befehls';
|
||||
return;
|
||||
}
|
||||
|
||||
resultBox.textContent = data.output || data.error || 'Keine Ausgabe';
|
||||
});
|
||||
|
||||
// Echtzeit-Monitoring mit WebSockets
|
||||
const socket = io();
|
||||
|
||||
// CPU- und RAM-Statistiken
|
||||
socket.on('system-stats', data => {
|
||||
// CPU-Anzeige aktualisieren
|
||||
const cpuPercentage = data.cpu.load;
|
||||
document.getElementById('cpu-percentage').textContent = `${cpuPercentage}%`;
|
||||
document.getElementById('cpu-gauge').style.height = `${cpuPercentage}%`;
|
||||
document.getElementById('cpu-gauge').style.backgroundColor = getColorByPercentage(cpuPercentage);
|
||||
|
||||
// CPU-Kerne anzeigen
|
||||
const cpuCoresContainer = document.getElementById('cpu-cores-container');
|
||||
|
||||
if (cpuCoresContainer.childElementCount === 0) {
|
||||
// Erstelle Kern-Anzeigen, falls sie noch nicht existieren
|
||||
data.cpu.cores.forEach((load, index) => {
|
||||
const coreElement = document.createElement('div');
|
||||
coreElement.className = 'cpu-core';
|
||||
coreElement.innerHTML = `
|
||||
<div class="cpu-core-label">
|
||||
<span>Kern ${index}</span>
|
||||
<span id="cpu-core-${index}-value">${load}%</span>
|
||||
</div>
|
||||
<div class="cpu-core-bar">
|
||||
<div id="cpu-core-${index}-fill" class="cpu-core-fill" style="width: ${load}%; background-color: ${getColorByPercentage(load)}"></div>
|
||||
</div>
|
||||
`;
|
||||
cpuCoresContainer.appendChild(coreElement);
|
||||
});
|
||||
} else {
|
||||
// Aktualisiere bestehende Kern-Anzeigen
|
||||
data.cpu.cores.forEach((load, index) => {
|
||||
const valueElement = document.getElementById(`cpu-core-${index}-value`);
|
||||
const fillElement = document.getElementById(`cpu-core-${index}-fill`);
|
||||
|
||||
if (valueElement && fillElement) {
|
||||
valueElement.textContent = `${load}%`;
|
||||
fillElement.style.width = `${load}%`;
|
||||
fillElement.style.backgroundColor = getColorByPercentage(load);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// RAM-Anzeige aktualisieren
|
||||
const memPercentage = data.memory.usedPercent;
|
||||
document.getElementById('memory-percentage').textContent = `${memPercentage}%`;
|
||||
document.getElementById('memory-gauge').style.height = `${memPercentage}%`;
|
||||
document.getElementById('memory-gauge').style.backgroundColor = getColorByPercentage(memPercentage);
|
||||
|
||||
document.getElementById('memory-details').innerHTML = `
|
||||
<p><strong>Gesamt:</strong> ${formatBytes(data.memory.total)}</p>
|
||||
<p><strong>Verwendet:</strong> ${formatBytes(data.memory.used)}</p>
|
||||
<p><strong>Frei:</strong> ${formatBytes(data.memory.free)}</p>
|
||||
`;
|
||||
});
|
||||
|
||||
// Netzwerkstatistiken
|
||||
let networkChart;
|
||||
socket.on('network-stats', data => {
|
||||
const networkThroughput = document.getElementById('network-throughput');
|
||||
let throughputHTML = '';
|
||||
|
||||
data.stats.forEach(stat => {
|
||||
throughputHTML += `
|
||||
<div class="network-stat">
|
||||
<strong>${stat.iface}:</strong> ↓ ${formatBytes(stat.rx_sec)}/s | ↑ ${formatBytes(stat.tx_sec)}/s
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
networkThroughput.innerHTML = throughputHTML;
|
||||
|
||||
// Aktualisiere oder erstelle Netzwerk-Chart
|
||||
updateNetworkChart(data.stats);
|
||||
});
|
||||
|
||||
// Netzwerk-Chart initialisieren oder aktualisieren
|
||||
function updateNetworkChart(stats) {
|
||||
const ctx = document.getElementById('network-chart').getContext('2d');
|
||||
|
||||
if (!networkChart) {
|
||||
// Chart initialisieren, wenn er noch nicht existiert
|
||||
const labels = [];
|
||||
const rxData = [];
|
||||
const txData = [];
|
||||
|
||||
// Fülle anfängliche Daten
|
||||
for (let i = 0; i < 20; i++) {
|
||||
labels.push('');
|
||||
rxData.push(0);
|
||||
txData.push(0);
|
||||
}
|
||||
|
||||
networkChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [
|
||||
{
|
||||
label: 'Empfangen (Bytes/s)',
|
||||
data: rxData,
|
||||
borderColor: '#3f51b5',
|
||||
backgroundColor: 'rgba(63, 81, 181, 0.1)',
|
||||
borderWidth: 2,
|
||||
tension: 0.2,
|
||||
fill: true
|
||||
},
|
||||
{
|
||||
label: 'Gesendet (Bytes/s)',
|
||||
data: txData,
|
||||
borderColor: '#f44336',
|
||||
backgroundColor: 'rgba(244, 67, 54, 0.1)',
|
||||
borderWidth: 2,
|
||||
tension: 0.2,
|
||||
fill: true
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
callback: function(value) {
|
||||
return formatBytes(value, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
duration: 300
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Chart aktualisieren
|
||||
let rxTotal = 0;
|
||||
let txTotal = 0;
|
||||
|
||||
stats.forEach(stat => {
|
||||
rxTotal += stat.rx_sec || 0;
|
||||
txTotal += stat.tx_sec || 0;
|
||||
});
|
||||
|
||||
// Füge neue Daten hinzu und entferne alte
|
||||
networkChart.data.labels.push('');
|
||||
networkChart.data.labels.shift();
|
||||
|
||||
networkChart.data.datasets[0].data.push(rxTotal);
|
||||
networkChart.data.datasets[0].data.shift();
|
||||
|
||||
networkChart.data.datasets[1].data.push(txTotal);
|
||||
networkChart.data.datasets[1].data.shift();
|
||||
|
||||
networkChart.update();
|
||||
}
|
||||
}
|
||||
|
||||
// Hilfsfunktionen
|
||||
function getColorByPercentage(percent) {
|
||||
// Farbverlauf von Grün über Gelb nach Rot
|
||||
if (percent < 70) {
|
||||
return 'var(--success-color)';
|
||||
} else if (percent < 90) {
|
||||
return 'var(--warning-color)';
|
||||
} else {
|
||||
return 'var(--danger-color)';
|
||||
}
|
||||
}
|
||||
|
||||
function formatBytes(bytes, decimals = 2) {
|
||||
if (bytes === 0) return '0 B';
|
||||
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
// Initialisierung
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Zeit aktualisieren
|
||||
setInterval(() => {
|
||||
document.getElementById('current-time').innerHTML = `<strong>Zeitstempel:</strong> ${new Date().toLocaleString('de-DE')}`;
|
||||
}, 1000);
|
||||
|
||||
// Lade Systemdaten, wenn das System-Panel aktiv ist
|
||||
if (document.getElementById('system').classList.contains('active')) {
|
||||
loadSystemInfo();
|
||||
}
|
||||
|
||||
// Lade Script für Netzwerk-Chart, falls noch nicht geladen
|
||||
if (typeof Chart === 'undefined') {
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://cdn.jsdelivr.net/npm/chart.js';
|
||||
script.onload = () => {
|
||||
console.log('Chart.js geladen');
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
});
|
227
frontend/debug-server/public/views/index.ejs
Normal file
227
frontend/debug-server/public/views/index.ejs
Normal file
@@ -0,0 +1,227 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><%= title %></title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
h1, h2, h3 {
|
||||
color: #2c3e50;
|
||||
}
|
||||
.card {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
color: #34495e;
|
||||
}
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.btn {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 15px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.btn:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
.btn-primary {
|
||||
background-color: #2ecc71;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background-color: #27ae60;
|
||||
}
|
||||
.status {
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.status-good {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
.status-warning {
|
||||
background-color: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
.status-error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
.message {
|
||||
display: none;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.message-success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
.message-error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
.interface-list {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
.interface-item {
|
||||
background-color: #f8f9fa;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 4px;
|
||||
border-left: 4px solid #3498db;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1><%= title %></h1>
|
||||
<p>Letzte Aktualisierung: <%= lastCheck %></p>
|
||||
|
||||
<div id="message" class="message"></div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Backend-Konfiguration</h2>
|
||||
<div class="form-group">
|
||||
<label for="backendUrl">Backend-URL:</label>
|
||||
<input type="text" id="backendUrl" value="<%= backendUrl %>">
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn" onclick="testBackend()">Verbindung testen</button>
|
||||
<button type="button" class="btn btn-primary" onclick="updateBackend()">URL aktualisieren</button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Backend-Status</h2>
|
||||
<div class="status <%= backendStatus === 'Verbunden' ? 'status-good' : 'status-error' %>">
|
||||
<strong>Status:</strong> <%= backendStatus %>
|
||||
</div>
|
||||
<div class="status <%= pingStatus ? 'status-good' : 'status-error' %>">
|
||||
<strong>Ping:</strong> <%= pingStatus ? 'Erfolgreich' : 'Fehlgeschlagen' %>
|
||||
</div>
|
||||
<div class="status">
|
||||
<strong>Host:</strong> <%= backendHost %>
|
||||
</div>
|
||||
<div class="status">
|
||||
<strong>Port:</strong> <%= backendPort %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Netzwerkschnittstellen</h2>
|
||||
<ul class="interface-list">
|
||||
<% if (interfaces && interfaces.length > 0) { %>
|
||||
<% interfaces.forEach(function(iface) { %>
|
||||
<li class="interface-item">
|
||||
<strong><%= iface.name %>:</strong> <%= iface.address %>
|
||||
</li>
|
||||
<% }); %>
|
||||
<% } else { %>
|
||||
<li class="interface-item">Keine Netzwerkschnittstellen gefunden</li>
|
||||
<% } %>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
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 testBackend() {
|
||||
const backendUrl = document.getElementById('backendUrl').value;
|
||||
|
||||
if (!backendUrl) {
|
||||
showMessage('Bitte geben Sie eine Backend-URL ein', true);
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/test-backend', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ backendUrl })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
const pingStatus = data.ping ? 'Erfolgreich' : 'Fehlgeschlagen';
|
||||
const connectionStatus = data.connection ? 'Verbunden' : 'Keine Verbindung';
|
||||
|
||||
showMessage(`Ping: ${pingStatus}, API-Verbindung: ${connectionStatus}`, !(data.ping && data.connection));
|
||||
} else {
|
||||
showMessage(data.message, true);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showMessage(`Fehler: ${error}`, true);
|
||||
});
|
||||
}
|
||||
|
||||
function updateBackend() {
|
||||
const backendUrl = document.getElementById('backendUrl').value;
|
||||
|
||||
if (!backendUrl) {
|
||||
showMessage('Bitte geben Sie eine Backend-URL ein', true);
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/update-backend', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ backendUrl })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
showMessage(data.message, !data.success);
|
||||
|
||||
if (data.success) {
|
||||
// Lade die Seite neu nach erfolgreicher Aktualisierung
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
}, 1500);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showMessage(`Fehler: ${error}`, true);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
235
frontend/debug-server/src/app.js
Normal file
235
frontend/debug-server/src/app.js
Normal file
@@ -0,0 +1,235 @@
|
||||
const express = require('express');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const axios = require('axios');
|
||||
const os = require('os');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 8081;
|
||||
|
||||
// Konfigurationsdatei
|
||||
const CONFIG_FILE = path.join(__dirname, '../../../.env.local');
|
||||
const DEFAULT_BACKEND_URL = 'http://192.168.0.105:5000';
|
||||
|
||||
// Middleware
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(express.static(path.join(__dirname, '../public')));
|
||||
app.set('view engine', 'ejs');
|
||||
app.set('views', path.join(__dirname, '../public/views'));
|
||||
|
||||
// Hilfsfunktionen
|
||||
function getBackendUrl() {
|
||||
try {
|
||||
if (fs.existsSync(CONFIG_FILE)) {
|
||||
const content = fs.readFileSync(CONFIG_FILE, 'utf8');
|
||||
const match = content.match(/NEXT_PUBLIC_API_URL=(.+)/);
|
||||
if (match && match[1]) {
|
||||
return match[1].trim();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Lesen der Backend-URL:', error);
|
||||
}
|
||||
return DEFAULT_BACKEND_URL;
|
||||
}
|
||||
|
||||
function setBackendUrl(url) {
|
||||
try {
|
||||
let content = '';
|
||||
|
||||
if (fs.existsSync(CONFIG_FILE)) {
|
||||
content = fs.readFileSync(CONFIG_FILE, 'utf8');
|
||||
// Ersetze die URL, wenn sie bereits existiert
|
||||
if (content.match(/NEXT_PUBLIC_API_URL=.+/)) {
|
||||
content = content.replace(/NEXT_PUBLIC_API_URL=.+/, `NEXT_PUBLIC_API_URL=${url}`);
|
||||
} else {
|
||||
// Füge die URL hinzu, wenn sie nicht existiert
|
||||
content += `\nNEXT_PUBLIC_API_URL=${url}`;
|
||||
}
|
||||
} else {
|
||||
// Erstelle eine neue Datei mit der URL
|
||||
content = `NEXT_PUBLIC_API_URL=${url}`;
|
||||
}
|
||||
|
||||
fs.writeFileSync(CONFIG_FILE, content, 'utf8');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Speichern der Backend-URL:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getNetworkInterfaces() {
|
||||
const interfaces = [];
|
||||
const networkInterfaces = os.networkInterfaces();
|
||||
|
||||
for (const [name, netInterface] of Object.entries(networkInterfaces)) {
|
||||
if (name.startsWith('lo') || name.startsWith('docker') || name.startsWith('br-')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const iface of netInterface) {
|
||||
if (iface.family === 'IPv4' || iface.family === 4) {
|
||||
interfaces.push({
|
||||
name: name,
|
||||
address: iface.address
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
function pingHost(host) {
|
||||
try {
|
||||
const platform = process.platform;
|
||||
const cmd = platform === 'win32' ?
|
||||
`ping -n 1 -w 1000 ${host}` :
|
||||
`ping -c 1 -W 1 ${host}`;
|
||||
|
||||
execSync(cmd, { stdio: 'ignore' });
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function testBackendConnection(url) {
|
||||
try {
|
||||
const response = await axios.get(`${url}/api/test`, { timeout: 3000 });
|
||||
return response.status === 200;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Routen
|
||||
app.get('/', async (req, res) => {
|
||||
const backendUrl = getBackendUrl();
|
||||
const interfaces = getNetworkInterfaces();
|
||||
|
||||
// Analysiere die URL, um Host und Port zu extrahieren
|
||||
let backendHost = '';
|
||||
let backendPort = '';
|
||||
|
||||
try {
|
||||
const url = new URL(backendUrl);
|
||||
backendHost = url.hostname;
|
||||
backendPort = url.port || (url.protocol === 'https:' ? '443' : '80');
|
||||
} catch (error) {
|
||||
console.error('Ungültige Backend-URL:', error);
|
||||
}
|
||||
|
||||
// Prüfe Backend-Verbindung
|
||||
let backendStatus = 'Unbekannt';
|
||||
let pingStatus = false;
|
||||
|
||||
if (backendHost) {
|
||||
pingStatus = pingHost(backendHost);
|
||||
|
||||
if (pingStatus) {
|
||||
try {
|
||||
const connectionStatus = await testBackendConnection(backendUrl);
|
||||
backendStatus = connectionStatus ? 'Verbunden' : 'Keine API-Verbindung';
|
||||
} catch (error) {
|
||||
backendStatus = 'Verbindungsfehler';
|
||||
}
|
||||
} else {
|
||||
backendStatus = 'Nicht erreichbar';
|
||||
}
|
||||
}
|
||||
|
||||
res.render('index', {
|
||||
title: 'MYP Frontend Debug',
|
||||
backendUrl,
|
||||
backendHost,
|
||||
backendPort,
|
||||
backendStatus,
|
||||
pingStatus,
|
||||
interfaces,
|
||||
lastCheck: new Date().toLocaleString('de-DE')
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/update-backend', async (req, res) => {
|
||||
const { backendUrl } = req.body;
|
||||
|
||||
if (!backendUrl) {
|
||||
return res.json({ success: false, message: 'Keine Backend-URL angegeben' });
|
||||
}
|
||||
|
||||
// Validiere URL
|
||||
try {
|
||||
new URL(backendUrl);
|
||||
} catch (error) {
|
||||
return res.json({ success: false, message: 'Ungültige URL' });
|
||||
}
|
||||
|
||||
// Speichere die URL
|
||||
const saved = setBackendUrl(backendUrl);
|
||||
|
||||
if (!saved) {
|
||||
return res.json({ success: false, message: 'Fehler beim Speichern der URL' });
|
||||
}
|
||||
|
||||
// Teste die Verbindung zum Backend
|
||||
let connectionStatus = false;
|
||||
|
||||
try {
|
||||
connectionStatus = await testBackendConnection(backendUrl);
|
||||
} catch (error) {
|
||||
// Ignoriere Fehler
|
||||
}
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: 'Backend-URL erfolgreich aktualisiert',
|
||||
connection: connectionStatus
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/test-backend', async (req, res) => {
|
||||
const { backendUrl } = req.body;
|
||||
|
||||
if (!backendUrl) {
|
||||
return res.json({ success: false, message: 'Keine Backend-URL angegeben' });
|
||||
}
|
||||
|
||||
// Validiere URL
|
||||
let hostname = '';
|
||||
try {
|
||||
const url = new URL(backendUrl);
|
||||
hostname = url.hostname;
|
||||
} catch (error) {
|
||||
return res.json({ success: false, message: 'Ungültige URL' });
|
||||
}
|
||||
|
||||
// Teste Ping
|
||||
const pingStatus = pingHost(hostname);
|
||||
|
||||
// Teste API-Verbindung
|
||||
let connectionStatus = false;
|
||||
|
||||
if (pingStatus) {
|
||||
try {
|
||||
connectionStatus = await testBackendConnection(backendUrl);
|
||||
} catch (error) {
|
||||
// Ignoriere Fehler
|
||||
}
|
||||
}
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
ping: pingStatus,
|
||||
connection: connectionStatus
|
||||
});
|
||||
});
|
||||
|
||||
// Server starten
|
||||
app.listen(PORT, () => {
|
||||
console.log(`MYP Frontend Debug Server läuft auf Port ${PORT}`);
|
||||
});
|
471
frontend/debug-server/src/index.js
Normal file
471
frontend/debug-server/src/index.js
Normal file
@@ -0,0 +1,471 @@
|
||||
// Frontend Debug-Server für MYP
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const http = require('http');
|
||||
const si = require('systeminformation');
|
||||
const os = require('os');
|
||||
const osUtils = require('os-utils');
|
||||
const { exec } = require('child_process');
|
||||
const fetch = require('node-fetch');
|
||||
const socketIo = require('socket.io');
|
||||
|
||||
// Konfiguration
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
const io = socketIo(server);
|
||||
const PORT = 6666;
|
||||
const FRONTEND_PORT = 3000;
|
||||
const FRONTEND_HOST = 'localhost';
|
||||
const BACKEND_HOST = 'localhost';
|
||||
const BACKEND_PORT = 5000;
|
||||
|
||||
// View Engine einrichten
|
||||
app.set('view engine', 'ejs');
|
||||
app.set('views', path.join(__dirname, '../public/views'));
|
||||
app.use(express.static(path.join(__dirname, '../public')));
|
||||
app.use(express.json());
|
||||
|
||||
// Hauptseite rendern
|
||||
app.get('/', async (req, res) => {
|
||||
const hostname = os.hostname();
|
||||
const networkInterfaces = os.networkInterfaces();
|
||||
const ipAddresses = {};
|
||||
|
||||
// IP-Adressen sammeln
|
||||
Object.keys(networkInterfaces).forEach(interfaceName => {
|
||||
const interfaceInfo = networkInterfaces[interfaceName];
|
||||
const ipv4Addresses = interfaceInfo.filter(info => info.family === 'IPv4');
|
||||
if (ipv4Addresses.length > 0) {
|
||||
ipAddresses[interfaceName] = ipv4Addresses[0].address;
|
||||
}
|
||||
});
|
||||
|
||||
// Rendere die Hauptseite mit Basisdaten
|
||||
res.render('index', {
|
||||
hostname: hostname,
|
||||
ipAddresses: ipAddresses,
|
||||
timestamp: new Date().toLocaleString('de-DE'),
|
||||
});
|
||||
});
|
||||
|
||||
// API-Endpunkte
|
||||
|
||||
// Systeminformationen
|
||||
app.get('/api/system', async (req, res) => {
|
||||
try {
|
||||
const [cpu, mem, osInfo, diskLayout, fsSize] = await Promise.all([
|
||||
si.cpu(),
|
||||
si.mem(),
|
||||
si.osInfo(),
|
||||
si.diskLayout(),
|
||||
si.fsSize()
|
||||
]);
|
||||
|
||||
const data = {
|
||||
cpu: {
|
||||
manufacturer: cpu.manufacturer,
|
||||
brand: cpu.brand,
|
||||
speed: cpu.speed,
|
||||
cores: cpu.cores,
|
||||
physicalCores: cpu.physicalCores
|
||||
},
|
||||
memory: {
|
||||
total: formatBytes(mem.total),
|
||||
free: formatBytes(mem.free),
|
||||
used: formatBytes(mem.used),
|
||||
usedPercent: Math.round(mem.used / mem.total * 100)
|
||||
},
|
||||
os: {
|
||||
platform: osInfo.platform,
|
||||
distro: osInfo.distro,
|
||||
release: osInfo.release,
|
||||
arch: osInfo.arch,
|
||||
uptime: formatUptime(os.uptime())
|
||||
},
|
||||
filesystem: fsSize.map(fs => ({
|
||||
fs: fs.fs,
|
||||
type: fs.type,
|
||||
size: formatBytes(fs.size),
|
||||
used: formatBytes(fs.used),
|
||||
available: formatBytes(fs.available),
|
||||
mount: fs.mount,
|
||||
usePercent: Math.round(fs.use)
|
||||
})),
|
||||
disks: diskLayout.map(disk => ({
|
||||
device: disk.device,
|
||||
type: disk.type,
|
||||
name: disk.name,
|
||||
size: formatBytes(disk.size)
|
||||
}))
|
||||
};
|
||||
|
||||
res.json(data);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen der Systemdaten:', error);
|
||||
res.status(500).json({ error: 'Fehler beim Abrufen der Systemdaten' });
|
||||
}
|
||||
});
|
||||
|
||||
// Netzwerkinformationen
|
||||
app.get('/api/network', async (req, res) => {
|
||||
try {
|
||||
const [netInterfaces, netStats] = await Promise.all([
|
||||
si.networkInterfaces(),
|
||||
si.networkStats()
|
||||
]);
|
||||
|
||||
const dns = await getDnsServers();
|
||||
const gateway = await getDefaultGateway();
|
||||
|
||||
const data = {
|
||||
interfaces: netInterfaces.map(iface => ({
|
||||
iface: iface.iface,
|
||||
ip4: iface.ip4,
|
||||
ip6: iface.ip6,
|
||||
mac: iface.mac,
|
||||
internal: iface.internal,
|
||||
operstate: iface.operstate,
|
||||
type: iface.type,
|
||||
speed: iface.speed,
|
||||
dhcp: iface.dhcp
|
||||
})),
|
||||
stats: netStats.map(stat => ({
|
||||
iface: stat.iface,
|
||||
rx_bytes: formatBytes(stat.rx_bytes),
|
||||
tx_bytes: formatBytes(stat.tx_bytes),
|
||||
rx_sec: formatBytes(stat.rx_sec),
|
||||
tx_sec: formatBytes(stat.tx_sec)
|
||||
})),
|
||||
dns: dns,
|
||||
gateway: gateway
|
||||
};
|
||||
|
||||
res.json(data);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen der Netzwerkdaten:', error);
|
||||
res.status(500).json({ error: 'Fehler beim Abrufen der Netzwerkdaten' });
|
||||
}
|
||||
});
|
||||
|
||||
// Dienststatus
|
||||
app.get('/api/services', async (req, res) => {
|
||||
try {
|
||||
// Prüfen ob Frontend (Next.js) läuft
|
||||
const frontendStatus = await checkServiceStatus(FRONTEND_HOST, FRONTEND_PORT);
|
||||
|
||||
// Prüfen ob Backend (Flask) läuft
|
||||
const backendStatus = await checkServiceStatus(BACKEND_HOST, BACKEND_PORT);
|
||||
|
||||
// Docker-Container Status abrufen
|
||||
const containers = await getDockerContainers();
|
||||
|
||||
const data = {
|
||||
frontend: {
|
||||
name: 'Next.js Frontend',
|
||||
status: frontendStatus ? 'online' : 'offline',
|
||||
port: FRONTEND_PORT,
|
||||
host: FRONTEND_HOST
|
||||
},
|
||||
backend: {
|
||||
name: 'Flask Backend',
|
||||
status: backendStatus ? 'online' : 'offline',
|
||||
port: BACKEND_PORT,
|
||||
host: BACKEND_HOST
|
||||
},
|
||||
docker: {
|
||||
containers: containers
|
||||
}
|
||||
};
|
||||
|
||||
res.json(data);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen der Dienststatus:', error);
|
||||
res.status(500).json({ error: 'Fehler beim Abrufen der Dienststatus' });
|
||||
}
|
||||
});
|
||||
|
||||
// Ping-Endpunkt für Netzwerkdiagnose
|
||||
app.get('/api/ping/:host', (req, res) => {
|
||||
const host = req.params.host;
|
||||
|
||||
// Sicherheitscheck für den Hostnamen
|
||||
if (!isValidHostname(host)) {
|
||||
return res.status(400).json({ error: 'Ungültiger Hostname oder IP-Adresse' });
|
||||
}
|
||||
|
||||
// Ping-Befehl ausführen
|
||||
exec(`ping -n 4 ${host}`, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
return res.json({
|
||||
success: false,
|
||||
output: stderr || stdout,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
output: stdout
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Traceroute-Endpunkt für Netzwerkdiagnose
|
||||
app.get('/api/traceroute/:host', (req, res) => {
|
||||
const host = req.params.host;
|
||||
|
||||
// Sicherheitscheck für den Hostnamen
|
||||
if (!isValidHostname(host)) {
|
||||
return res.status(400).json({ error: 'Ungültiger Hostname oder IP-Adresse' });
|
||||
}
|
||||
|
||||
// Traceroute-Befehl ausführen (Windows: tracert, Unix: traceroute)
|
||||
const command = process.platform === 'win32' ? 'tracert' : 'traceroute';
|
||||
exec(`${command} ${host}`, (error, stdout, stderr) => {
|
||||
// Traceroute kann einen Nicht-Null-Exit-Code zurückgeben, selbst wenn es teilweise erfolgreich ist
|
||||
res.json({
|
||||
success: true,
|
||||
output: stdout,
|
||||
error: stderr
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// DNS-Lookup-Endpunkt für Netzwerkdiagnose
|
||||
app.get('/api/nslookup/:host', (req, res) => {
|
||||
const host = req.params.host;
|
||||
|
||||
// Sicherheitscheck für den Hostnamen
|
||||
if (!isValidHostname(host)) {
|
||||
return res.status(400).json({ error: 'Ungültiger Hostname oder IP-Adresse' });
|
||||
}
|
||||
|
||||
// NSLookup-Befehl ausführen
|
||||
exec(`nslookup ${host}`, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
return res.json({
|
||||
success: false,
|
||||
output: stderr || stdout,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
output: stdout
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Echtzeit-Updates über WebSockets
|
||||
io.on('connection', (socket) => {
|
||||
console.log('Neue WebSocket-Verbindung');
|
||||
|
||||
// CPU- und Arbeitsspeichernutzung im Intervall senden
|
||||
const systemMonitorInterval = setInterval(async () => {
|
||||
try {
|
||||
const [cpu, mem] = await Promise.all([
|
||||
si.currentLoad(),
|
||||
si.mem()
|
||||
]);
|
||||
|
||||
socket.emit('system-stats', {
|
||||
cpu: {
|
||||
load: Math.round(cpu.currentLoad),
|
||||
cores: cpu.cpus.map(core => Math.round(core.load))
|
||||
},
|
||||
memory: {
|
||||
total: mem.total,
|
||||
used: mem.used,
|
||||
free: mem.free,
|
||||
usedPercent: Math.round(mem.used / mem.total * 100)
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Senden der Systemstatistiken:', error);
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
// Netzwerkstatistiken im Intervall senden
|
||||
const networkMonitorInterval = setInterval(async () => {
|
||||
try {
|
||||
const netStats = await si.networkStats();
|
||||
|
||||
socket.emit('network-stats', {
|
||||
stats: netStats.map(stat => ({
|
||||
iface: stat.iface,
|
||||
rx_bytes: stat.rx_bytes,
|
||||
tx_bytes: stat.tx_bytes,
|
||||
rx_sec: stat.rx_sec,
|
||||
tx_sec: stat.tx_sec
|
||||
}))
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Senden der Netzwerkstatistiken:', error);
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
// Aufräumen, wenn die Verbindung getrennt wird
|
||||
socket.on('disconnect', () => {
|
||||
console.log('WebSocket-Verbindung getrennt');
|
||||
clearInterval(systemMonitorInterval);
|
||||
clearInterval(networkMonitorInterval);
|
||||
});
|
||||
});
|
||||
|
||||
// Hilfsfunktionen
|
||||
|
||||
// Bytes in lesbare Größen formatieren
|
||||
function formatBytes(bytes, decimals = 2) {
|
||||
if (bytes === 0) return '0 B';
|
||||
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
// Uptime in lesbare Zeit formatieren
|
||||
function formatUptime(seconds) {
|
||||
const days = Math.floor(seconds / 86400);
|
||||
const hours = Math.floor((seconds % 86400) / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
|
||||
return `${days} Tage, ${hours} Stunden, ${minutes} Minuten, ${secs} Sekunden`;
|
||||
}
|
||||
|
||||
// Service-Status überprüfen
|
||||
async function checkServiceStatus(host, port) {
|
||||
return new Promise(resolve => {
|
||||
const socket = new (require('net').Socket)();
|
||||
|
||||
socket.setTimeout(1000);
|
||||
|
||||
socket.on('connect', () => {
|
||||
socket.destroy();
|
||||
resolve(true);
|
||||
});
|
||||
|
||||
socket.on('timeout', () => {
|
||||
socket.destroy();
|
||||
resolve(false);
|
||||
});
|
||||
|
||||
socket.on('error', () => {
|
||||
socket.destroy();
|
||||
resolve(false);
|
||||
});
|
||||
|
||||
socket.connect(port, host);
|
||||
});
|
||||
}
|
||||
|
||||
// Docker-Container abfragen
|
||||
async function getDockerContainers() {
|
||||
return new Promise((resolve) => {
|
||||
exec('docker ps --format "{{.ID}},{{.Image}},{{.Status}},{{.Ports}},{{.Names}}"', (error, stdout) => {
|
||||
if (error) {
|
||||
resolve([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const containers = [];
|
||||
const lines = stdout.trim().split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
if (line) {
|
||||
const [id, image, status, ports, name] = line.split(',');
|
||||
containers.push({ id, image, status, ports, name });
|
||||
}
|
||||
}
|
||||
|
||||
resolve(containers);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// DNS-Server abfragen
|
||||
async function getDnsServers() {
|
||||
return new Promise((resolve) => {
|
||||
if (process.platform === 'win32') {
|
||||
// Windows: DNS-Server über PowerShell abfragen
|
||||
exec('powershell.exe -Command "Get-DnsClientServerAddress -AddressFamily IPv4 | Select-Object -ExpandProperty ServerAddresses"', (error, stdout) => {
|
||||
if (error) {
|
||||
resolve(['DNS-Server konnten nicht ermittelt werden']);
|
||||
return;
|
||||
}
|
||||
|
||||
const servers = stdout.trim().split('\r\n').filter(Boolean);
|
||||
resolve(servers);
|
||||
});
|
||||
} else {
|
||||
// Unix: DNS-Server aus /etc/resolv.conf lesen
|
||||
exec('cat /etc/resolv.conf | grep nameserver | cut -d " " -f 2', (error, stdout) => {
|
||||
if (error) {
|
||||
resolve(['DNS-Server konnten nicht ermittelt werden']);
|
||||
return;
|
||||
}
|
||||
|
||||
const servers = stdout.trim().split('\n').filter(Boolean);
|
||||
resolve(servers);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Standard-Gateway abfragen
|
||||
async function getDefaultGateway() {
|
||||
return new Promise((resolve) => {
|
||||
if (process.platform === 'win32') {
|
||||
// Windows: Gateway über PowerShell abfragen
|
||||
exec('powershell.exe -Command "Get-NetRoute -DestinationPrefix 0.0.0.0/0 | Select-Object -ExpandProperty NextHop"', (error, stdout) => {
|
||||
if (error) {
|
||||
resolve('Gateway konnte nicht ermittelt werden');
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(stdout.trim());
|
||||
});
|
||||
} else {
|
||||
// Unix: Gateway aus den Routentabellen lesen
|
||||
exec("ip route | grep default | awk '{print $3}'", (error, stdout) => {
|
||||
if (error) {
|
||||
resolve('Gateway konnte nicht ermittelt werden');
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(stdout.trim());
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Validierung des Hostnamens für Sicherheit
|
||||
function isValidHostname(hostname) {
|
||||
// Längenprüfung
|
||||
if (!hostname || hostname.length > 255) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Erlaubte Hostnamen
|
||||
if (hostname === 'localhost' || hostname === '127.0.0.1') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// IPv4-Prüfung
|
||||
const ipv4Regex = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||
if (ipv4Regex.test(hostname)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Hostname-Prüfung
|
||||
const hostnameRegex = /^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$/;
|
||||
return hostnameRegex.test(hostname);
|
||||
}
|
||||
|
||||
// Server starten
|
||||
server.listen(PORT, () => {
|
||||
console.log(`MYP Frontend Debug-Server läuft auf http://localhost:${PORT}`);
|
||||
});
|
Reference in New Issue
Block a user