Add Remote Desktop (JPEG-over-WebSocket), Agent v2.2.0

- RtcService.cs: Screen capture via Graphics.CopyFromScreen, ~8fps JPEG stream over WebSocket
- shellServer.js: Separate rdp-agent/rdp types with independent socket maps (no shell collision)
- AgentDetailPage: WebSocket-based RemoteDesktop component replaces WebRTC attempt
- setup.iss: Fixed filename to include 'v' prefix (IT-Nexus-Agent-Setup-v2.2.0.exe)
- Removed SIPSorcery dependencies, added System.Drawing.Common

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 14:13:18 +02:00
parent c23091fa6e
commit 0a8b186585
7 changed files with 256 additions and 99 deletions

View File

@@ -7,6 +7,10 @@ const agentSockets = new Map();
// agentId -> WebSocket
const browserSockets = new Map();
// RDP: separate Maps damit Shell nicht kollidiert
const rdpAgentSockets = new Map();
const rdpBrowserSockets = new Map();
function setupWebSocketServer(httpServer) {
const wss = new WebSocket.Server({ server: httpServer, path: '/ws' });
@@ -24,6 +28,10 @@ function setupWebSocketServer(httpServer) {
handleAgent(ws, url);
} else if (type === 'shell') {
handleBrowser(ws, url);
} else if (type === 'rdp-agent') {
handleRdpAgent(ws, url);
} else if (type === 'rdp') {
handleRdpBrowser(ws, url);
} else {
ws.close(1008, 'unknown type');
}
@@ -124,6 +132,63 @@ function handleBrowser(ws, url) {
});
}
function handleRdpAgent(ws, url) {
const key = url.searchParams.get('key');
if (key !== process.env.AGENT_API_KEY) { ws.close(1008, 'unauthorized'); return; }
const hostname = url.searchParams.get('hostname');
if (!hostname) { ws.close(1008, 'hostname required'); return; }
const db = getDatabase();
const agent = db.prepare('SELECT id FROM monitoring_agents WHERE hostname = ?').get(hostname);
if (!agent) { ws.close(1008, 'agent not found'); return; }
const agentId = agent.id;
const oldWs = rdpAgentSockets.get(agentId);
if (oldWs?.readyState === WebSocket.OPEN) oldWs.close();
rdpAgentSockets.set(agentId, ws);
console.log(`[WS-RDP] Agent verbunden: ${hostname}`);
ws.on('message', (data) => {
const bws = rdpBrowserSockets.get(agentId);
if (bws?.readyState === WebSocket.OPEN) bws.send(data.toString());
});
ws.on('close', () => {
rdpAgentSockets.delete(agentId);
const bws = rdpBrowserSockets.get(agentId);
if (bws?.readyState === WebSocket.OPEN) {
bws.send(JSON.stringify({ type: 'rdp_disconnected' }));
}
console.log(`[WS-RDP] Agent getrennt: ${hostname}`);
});
}
function handleRdpBrowser(ws, url) {
const token = url.searchParams.get('token');
try { jwt.verify(token, process.env.JWT_SECRET); }
catch { ws.close(1008, 'unauthorized'); return; }
const agentId = parseInt(url.searchParams.get('agentId'));
if (!agentId) { ws.close(1008, 'agentId required'); return; }
const oldBws = rdpBrowserSockets.get(agentId);
if (oldBws?.readyState === WebSocket.OPEN) oldBws.close();
rdpBrowserSockets.set(agentId, ws);
ws.on('message', (data) => {
const aws = rdpAgentSockets.get(agentId);
if (aws?.readyState === WebSocket.OPEN) aws.send(data.toString());
});
ws.on('close', () => {
rdpBrowserSockets.delete(agentId);
const aws = rdpAgentSockets.get(agentId);
if (aws?.readyState === WebSocket.OPEN) {
aws.send(JSON.stringify({ type: 'rdp_stop' }));
}
});
}
// Ankündigung an Agent pushen (für sofortige Zustellung)
function pushAnnouncementToAgent(agentId, announcements) {
const aws = agentSockets.get(agentId);