- 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>
189 lines
6 KiB
TypeScript
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 };
|
|
}
|