wulf-pulse/lib/services/rmm/settings.ts
lorentz 1112a06afe feat: RMM Overshell, IT Glue audit/write-back, LogLift, link-aware bundles, dashboard overhaul
- RMM Overshell (migration 077): admin page, dispatch UI, executor/worker, target
  resolver, script registry (AD/DHCP/DNS/event-log/services/software/network/loglift)
- LogLift evidence pipeline (migration 078): upload webhook, B2 storage client,
  receiver/matcher, EventLogCollector PowerShell script
- IT Glue audit + write-back (migrations 075, 076): asset-audit runner, ticket
  xrefs, applications/configurations browse pages + apply/revert/audit endpoints
- Link-aware analyzer bundles (migration 073) + provider toggle (migration 074):
  link-discovery service, OpenRouter LLM provider, related-tickets/itglue-suggestion
  panels, analyze-bundle endpoint
- Endpoint data model + device-link reconciliation (migrations 079, 080): conflicts
  admin page, reconciler service, resolve endpoints
- Dashboard overhaul: integration-health service + alerts, overview/health endpoints
- Permissions: add itglue + rmm scopes; middleware: public /api/rmm/loglift route

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 07:13:18 -04:00

189 lines
6 KiB
TypeScript

/**
* Read/write the rmm_settings singleton row.
*
* Caches the discovered Overshell component_uid so we don't re-scan
* components on every dispatch. The discover endpoint repopulates it.
*/
import postgresClient from '@/lib/services/postgres-client';
import { getDattoRMMClient } from '@/lib/services/datto-rmm-factory';
export interface RmmSettings {
overshellComponentUid: string | null;
overshellComponentName: string | null;
overshellVariableName: string;
discoveredAt: string | null;
// Phase 4.3 — LogLift component cache.
logliftComponentUid: string | null;
logliftComponentName: string | null;
logliftDiscoveredAt: string | null;
updatedAt: string;
}
interface RawSettingsRow {
overshell_component_uid: string | null;
overshell_component_name: string | null;
overshell_variable_name: string;
discovered_at: Date | null;
loglift_component_uid: string | null;
loglift_component_name: string | null;
loglift_discovered_at: Date | null;
updated_at: Date;
}
function rowToSettings(r: RawSettingsRow): RmmSettings {
return {
overshellComponentUid: r.overshell_component_uid,
overshellComponentName: r.overshell_component_name,
overshellVariableName: r.overshell_variable_name,
discoveredAt: r.discovered_at?.toISOString() ?? null,
logliftComponentUid: r.loglift_component_uid,
logliftComponentName: r.loglift_component_name,
logliftDiscoveredAt: r.loglift_discovered_at?.toISOString() ?? null,
updatedAt: r.updated_at.toISOString(),
};
}
export async function getRmmSettings(): Promise<RmmSettings> {
const res = await postgresClient.query<RawSettingsRow>(
`SELECT overshell_component_uid, overshell_component_name,
overshell_variable_name, discovered_at,
loglift_component_uid, loglift_component_name, loglift_discovered_at,
updated_at
FROM rmm_settings WHERE id = true LIMIT 1`
);
if (res.rowCount === 0) {
// Defensive: re-seed if the singleton row vanished somehow.
await postgresClient.query(
`INSERT INTO rmm_settings (id) VALUES (true) ON CONFLICT DO NOTHING`
);
return {
overshellComponentUid: null,
overshellComponentName: null,
overshellVariableName: 'CommandLine',
discoveredAt: null,
logliftComponentUid: null,
logliftComponentName: null,
logliftDiscoveredAt: null,
updatedAt: new Date().toISOString(),
};
}
return rowToSettings(res.rows[0]);
}
/**
* Force-discover the Overshell component via the Datto RMM API and persist
* the uid + name. Idempotent. Returns the updated settings.
*/
export async function discoverOvershellComponent(): Promise<{
settings: RmmSettings;
discovered: { uid: string; name: string } | null;
}> {
const client = getDattoRMMClient();
const found = await client.findOvershellComponent();
if (!found) {
return { settings: await getRmmSettings(), discovered: null };
}
await postgresClient.query(
`UPDATE rmm_settings
SET overshell_component_uid = $1,
overshell_component_name = $2,
discovered_at = NOW(),
updated_at = NOW()
WHERE id = true`,
[found.uid, found.name]
);
const settings = await getRmmSettings();
return { settings, discovered: found };
}
export async function updateOvershellVariableName(name: string): Promise<RmmSettings> {
await postgresClient.query(
`UPDATE rmm_settings
SET overshell_variable_name = $1,
updated_at = NOW()
WHERE id = true`,
[name]
);
return getRmmSettings();
}
/**
* Resolve the component_uid Pulse should use, discovering on demand if the
* cache is empty. Throws if the component cannot be found at all.
*/
export async function resolveOvershellComponent(): Promise<{
componentUid: string;
variableName: string;
}> {
let s = await getRmmSettings();
if (!s.overshellComponentUid) {
const result = await discoverOvershellComponent();
if (!result.discovered) {
throw new Error(
'Overshell component not found in Datto RMM. Register a component whose name contains "overshell" or update the discovery pattern.'
);
}
s = result.settings;
}
return {
componentUid: s.overshellComponentUid as string,
variableName: s.overshellVariableName,
};
}
// =============================================================================
// Phase 4.3 — LogLift component discovery
// =============================================================================
/**
* Match anything containing "loglift" or "eventlog" in the component
* name. The user's existing tenant should have a registered Datto
* component (matching one of these names) that owns the PowerShell
* collector logic.
*/
const LOGLIFT_PATTERN = /loglift|eventlog/i;
export async function discoverLogliftComponent(): Promise<{
settings: RmmSettings;
discovered: { uid: string; name: string } | null;
}> {
const client = getDattoRMMClient();
const found = await client.findComponentByName(LOGLIFT_PATTERN);
if (!found) {
return { settings: await getRmmSettings(), discovered: null };
}
await postgresClient.query(
`UPDATE rmm_settings
SET loglift_component_uid = $1,
loglift_component_name = $2,
loglift_discovered_at = NOW(),
updated_at = NOW()
WHERE id = true`,
[found.uid, found.name]
);
const settings = await getRmmSettings();
return { settings, discovered: found };
}
/**
* Resolve the LogLift component_uid Pulse should dispatch. Throws with a
* helpful message if the component hasn't been discovered yet.
*/
export async function resolveLogliftComponent(): Promise<{ componentUid: string }> {
let s = await getRmmSettings();
if (!s.logliftComponentUid) {
const result = await discoverLogliftComponent();
if (!result.discovered) {
throw new Error(
'LogLift component not found in Datto RMM. Register a component whose name contains "loglift" or "eventlog" and re-run discovery from /admin/rmm-overshell.'
);
}
s = result.settings;
}
return { componentUid: s.logliftComponentUid as string };
}