"feat: Added debug server and related components for improved development experience"

This commit is contained in:
2025-05-23 07:24:51 +02:00
parent d457a8d86b
commit 9f6219832c
189 changed files with 35730 additions and 133 deletions

View 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}`);
});

View 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}`);
});