Add: Agent v2.1.2, WebSocket Live Shell, WebRTC Remote Desktop (Beta), Announcement Push

- Agent v2.1.2: WebSocket ShellService, ACK nach WS-Ankündigungen, shown_announcements Fix
- Backend: shellServer.js mit WebSocket-Server (Shell + Announcement Push + RTC Signaling)
- Backend: patch.controller.js Fix (command_id=0 Falsy-Bug beim Auto-Update)
- Frontend: Remote Desktop Tab (WebRTC Beta) in AgentDetailPage für super_admin/admin
- Frontend: PatchManagementPage auf v2.1.2 aktualisiert
- WPF Notification: AllowsTransparency=False + kein DropShadowEffect (Dispatcher-Crash Fix)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 13:31:11 +02:00
parent d584e65226
commit c23091fa6e
27 changed files with 3481 additions and 556 deletions

View File

@@ -1,4 +1,5 @@
const { getDatabase } = require('../config/database');
const { pushAnnouncementToAgent, broadcastAnnouncement, getConnectedAgents } = require('../ws/shellServer');
// Hilfsfunktion: welche Ankündigungen sind für einen Agenten relevant (nach Gruppe)?
function getForAgent(agentId) {
@@ -94,7 +95,18 @@ const create = (req, res) => {
INSERT INTO announcements (title, message, type, target_groups, target_agent_ids, created_by_user_id, expires_at)
VALUES (?, ?, ?, ?, ?, ?, ?)
`).run(title.trim(), message.trim(), type, JSON.stringify(target_groups), JSON.stringify(target_agent_ids), req.user.id, expires_at || null);
res.status(201).json(db.prepare('SELECT * FROM announcements WHERE id=?').get(result.lastInsertRowid));
const ann = db.prepare('SELECT * FROM announcements WHERE id=?').get(result.lastInsertRowid);
// WebSocket-Push: sofort an verbundene Agents schicken
const wsPayload = { id: ann.id, title: ann.title, message: ann.message, type: ann.type };
const agentIds = JSON.parse(ann.target_agent_ids || '[]');
if (agentIds.length > 0) {
agentIds.forEach(id => pushAnnouncementToAgent(id, [wsPayload]));
} else {
broadcastAnnouncement(wsPayload);
}
res.status(201).json(ann);
};
const update = (req, res) => {

View File

@@ -7,7 +7,10 @@ class FidoKeyController {
* GET /api/fido-keys
*/
static getAllKeys = asyncHandler(async (req, res) => {
const keys = FidoKeyService.getAllKeys();
const { user_id } = req.query;
const keys = user_id
? FidoKeyService.getKeysByUser(parseInt(user_id))
: FidoKeyService.getAllKeys();
res.json({
status: 'success',

View File

@@ -273,11 +273,14 @@ const reportCommandResult = (req, res) => {
if (!agentKey || agentKey !== process.env.AGENT_API_KEY) {
return res.status(401).json({ error: 'Unauthorized' });
}
const { command_id, status, result } = req.body;
if (!command_id) return res.status(400).json({ error: 'command_id erforderlich' });
const command_id = req.body.command_id ?? req.body.CommandId;
const { status, result } = req.body;
if (command_id === undefined || command_id === null) return res.status(400).json({ error: 'command_id erforderlich' });
const db = getDatabase();
db.prepare("UPDATE patch_commands SET status=?, result=?, completed_at=CURRENT_TIMESTAMP WHERE id=?")
.run(status || 'done', result || null, command_id);
if (command_id !== 0) {
db.prepare("UPDATE patch_commands SET status=?, result=?, completed_at=CURRENT_TIMESTAMP WHERE id=?")
.run(status || 'done', result || null, command_id);
}
res.json({ success: true });
};
@@ -293,6 +296,37 @@ const getPendingCommands = (agentId) => {
return { pending: cmds, running };
};
// Remote Shell: POST /api/patch/shell/:agentId
const triggerShell = (req, res) => {
const { agentId } = req.params;
const { command } = req.body;
if (!command) return res.status(400).json({ error: 'command erforderlich' });
const db = getDatabase();
const result = db.prepare(
"INSERT INTO patch_commands (agent_id, command, params, triggered_by_user_id) VALUES (?,?,?,?)"
).run(parseInt(agentId), 'shell_exec', command, req.user.id);
res.status(201).json({ id: result.lastInsertRowid });
};
// Remote Shell: GET /api/patch/shell/:agentId/result/:cmdId
const getShellResult = (req, res) => {
const { cmdId } = req.params;
const db = getDatabase();
const cmd = db.prepare("SELECT id, status, result, created_at, completed_at FROM patch_commands WHERE id=?").get(parseInt(cmdId));
if (!cmd) return res.status(404).json({ error: 'Nicht gefunden' });
res.json(cmd);
};
// Remote Shell: GET /api/patch/shell/:agentId/history
const getShellHistory = (req, res) => {
const { agentId } = req.params;
const db = getDatabase();
const cmds = db.prepare(
"SELECT id, params, status, result, created_at, completed_at FROM patch_commands WHERE agent_id=? AND command='shell_exec' ORDER BY created_at DESC LIMIT 50"
).all(parseInt(agentId));
res.json(cmds);
};
module.exports = {
getGroups, createGroup, updateGroup, deleteGroup, releaseVersionToGroup, releaseVersionToAll,
upsertPolicy, deletePolicy,
@@ -300,4 +334,5 @@ module.exports = {
getOverview,
triggerCommand, triggerGroupCommand, getCommands, reportCommandResult,
getPendingCommands,
triggerShell, getShellResult, getShellHistory,
};