fix(agent): preserve full telemetry (disks, cpu_details, logged_users) and format desktop_control driver-action params (#180)
This commit is contained in:
@@ -784,7 +784,8 @@ router.post('/resources/:id/driver-action', async (req, res, next) => {
|
||||
if (!resource) return res.status(404).json({ status: 'error', message: 'resource not found' });
|
||||
const { action, params } = req.body || {};
|
||||
if (!action) return res.status(400).json({ status: 'error', message: 'action is required' });
|
||||
const result = await DriverRegistry.execAction(resource, action, params || {});
|
||||
const actionParams = params || req.body || {};
|
||||
const result = await DriverRegistry.execAction(resource, action, actionParams);
|
||||
res.json({ status: 'ok', resourceId: resource.id, result });
|
||||
} catch (err) { next(err); }
|
||||
});
|
||||
|
||||
@@ -120,11 +120,17 @@ class AgentManager {
|
||||
const discovery = {
|
||||
hostname: payload.hostname || '',
|
||||
ip_addresses: Array.isArray(payload.ip_addresses) ? payload.ip_addresses : [],
|
||||
public_ip: payload.public_ip || '',
|
||||
os: payload.os || '',
|
||||
kernel: payload.kernel || '',
|
||||
cpu: payload.cpu || '',
|
||||
cpu_details: payload.cpu_details || {},
|
||||
ram_total_gb: payload.ram_total_gb || 0,
|
||||
ram_details: payload.ram_details || {},
|
||||
disk_total_gb: payload.disk_total_gb || 0,
|
||||
disks: Array.isArray(payload.disks) ? payload.disks : [],
|
||||
logged_users: Array.isArray(payload.logged_users) ? payload.logged_users : [],
|
||||
host_details: payload.host_details || {},
|
||||
location: payload.location || 'default',
|
||||
// The agent's enabled capabilities (from its local agent.yml). The agent
|
||||
// is the authoritative source for what it will actually do.
|
||||
@@ -134,6 +140,107 @@ class AgentManager {
|
||||
await this.applyDiscoveryToDirectory(agent, discovery);
|
||||
}
|
||||
|
||||
async handleTelemetry(agent, payload) {
|
||||
await this.touch(agent, {
|
||||
lastTelemetry: {
|
||||
cpu_usage_percent: payload.cpu_usage_percent || 0,
|
||||
cpu_details: payload.cpu_details || {},
|
||||
ram_usage_percent: payload.ram_usage_percent || 0,
|
||||
ram_details: payload.ram_details || {},
|
||||
disk_usage_percent: payload.disk_usage_percent || 0,
|
||||
disks: Array.isArray(payload.disks) ? payload.disks : [],
|
||||
logged_users: Array.isArray(payload.logged_users) ? payload.logged_users : [],
|
||||
host_details: payload.host_details || {},
|
||||
zfs_health: payload.zfs_health || 'N/A',
|
||||
gpu_usage_percent: payload.gpu_usage_percent ?? -1,
|
||||
timestamp: payload.timestamp || new Date().toISOString()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async handleHeartbeat(agent, payload, ws) {
|
||||
await this.touch(agent);
|
||||
try {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'heartbeat_ack',
|
||||
payload: { timestamp: new Date().toISOString() }
|
||||
}));
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
async handleResponse(agent, payload) {
|
||||
const state = this.live.get(agent.id);
|
||||
if (state) {
|
||||
state.lastResponse = {
|
||||
status: payload.status || 'ok',
|
||||
message: payload.message || '',
|
||||
output: payload.output || '',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
await this.touch(agent);
|
||||
}
|
||||
|
||||
async sendCommand(agent, commandType, payload = {}, isHighRisk = false) {
|
||||
const state = this.live.get(agent.id);
|
||||
if (!state || !state.ws || state.ws.readyState !== 1) {
|
||||
throw new Error(`Agent "${agent.name}" is not connected`);
|
||||
}
|
||||
|
||||
const finalPayload = { ...payload };
|
||||
if (isHighRisk) finalPayload.signature = await this.signPayload(finalPayload);
|
||||
|
||||
const message = { type: commandType, payload: finalPayload };
|
||||
state.ws.send(JSON.stringify(message));
|
||||
return message;
|
||||
}
|
||||
|
||||
// Live view for one agent, for merging into its row.
|
||||
liveState(agentId) {
|
||||
const state = this.live.get(agentId);
|
||||
if (!state) return { connected: false, lastResponse: null };
|
||||
return {
|
||||
connected: !!(state.ws && state.ws.readyState === 1),
|
||||
ipAddress: state.ipAddress,
|
||||
connectedAt: state.connectedAt,
|
||||
lastResponse: state.lastResponse || null
|
||||
};
|
||||
}
|
||||
|
||||
// Find connected/enrolled agent bound to a resource ID (or inherited from parent Host).
|
||||
async getAgentForResource(resourceId) {
|
||||
if (!resourceId) return null;
|
||||
const rows = await Agent.list().catch(() => []);
|
||||
let agent = rows.find(a => a.resourceId === resourceId);
|
||||
if (!agent) {
|
||||
try {
|
||||
const { Resource } = require('../models/resource');
|
||||
const { ResourceEdge } = require('../models/resource');
|
||||
const res = await Resource.get(resourceId);
|
||||
if (res && res.kind === 'service') {
|
||||
const edges = await ResourceEdge.list({ where: { childId: resourceId } });
|
||||
for (const edge of edges) {
|
||||
const parentRes = await Resource.get(edge.parentId);
|
||||
if (parentRes && parentRes.kind === 'host') {
|
||||
agent = rows.find(a => a.resourceId === parentRes.id || (parentRes.metadata && parentRes.metadata.agentId === a.id));
|
||||
if (agent) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[AgentManager] parent agent lookup error:', err.message);
|
||||
}
|
||||
}
|
||||
if (!agent) return null;
|
||||
return agent.toPublic(this.liveState(agent.id));
|
||||
}
|
||||
|
||||
// Every enrolled agent, connected or not.
|
||||
async listAgents() {
|
||||
const rows = await Agent.list();
|
||||
return rows.map(a => a.toPublic(this.liveState(a.id)));
|
||||
}
|
||||
|
||||
// An agent runs ON the host it describes, which makes it the most
|
||||
// authoritative source the directory has -- more so than a hypervisor API or
|
||||
// a network scan. It previously updated nothing at all: the facts sat on an
|
||||
|
||||
@@ -810,8 +810,8 @@
|
||||
// CPU details
|
||||
const cpuDet = t.cpu_details || d.cpu_details || {};
|
||||
const cpuModel = cpuDet.model || d.cpu || 'Unknown CPU';
|
||||
const cpuCores = cpuDet.cores || 'N/A';
|
||||
const cpuThreads = cpuDet.threads || 'N/A';
|
||||
const cpuCores = (cpuDet.cores != null && cpuDet.cores > 0) ? cpuDet.cores : (d.cpu_details && d.cpu_details.cores ? d.cpu_details.cores : 'N/A');
|
||||
const cpuThreads = (cpuDet.threads != null && cpuDet.threads > 0) ? cpuDet.threads : (d.cpu_details && d.cpu_details.threads ? d.cpu_details.threads : 'N/A');
|
||||
const cpuSpeed = cpuDet.mhz ? (cpuDet.mhz >= 1000 ? (cpuDet.mhz / 1000).toFixed(2) + ' GHz' : cpuDet.mhz.toFixed(0) + ' MHz') : '';
|
||||
|
||||
// Disks
|
||||
@@ -1579,8 +1579,10 @@
|
||||
app.messages.action(`Sending '${action}' desktop command to agent...`, $('#res-modal'), 'info');
|
||||
const res = await app.api.post(`directory-admin/resources/${resourceId}/driver-action`, {
|
||||
action: 'desktop_control',
|
||||
params: {
|
||||
subAction: action,
|
||||
user: username
|
||||
}
|
||||
});
|
||||
app.messages.action(`Desktop action '${action}' completed successfully!`, $('#res-modal'), 'success');
|
||||
} catch (err) {
|
||||
|
||||
Reference in New Issue
Block a user