241 lines
8 KiB
TypeScript
241 lines
8 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { getAutotaskClient } from '@/lib/services/autotask-factory';
|
|
import { getDattoRMMClient } from '@/lib/services/datto-rmm-factory';
|
|
import { apiCache } from '@/lib/services/cache';
|
|
import { DattoRMMDevice } from '@/lib/types/datto-rmm';
|
|
import { ConfigurationItem } from '@/lib/types/autotask';
|
|
|
|
interface DeviceComparison {
|
|
autotaskDevice?: ConfigurationItem;
|
|
rmmDevice?: DattoRMMDevice;
|
|
status: 'matched' | 'autotask-only' | 'rmm-only';
|
|
matchedBy?: string; // What field was used to match
|
|
}
|
|
|
|
export async function GET(request: NextRequest) {
|
|
try {
|
|
const searchParams = request.nextUrl.searchParams;
|
|
const companyId = searchParams.get('companyId');
|
|
const companyName = searchParams.get('companyName');
|
|
const activeFilter = searchParams.get('activeFilter') || 'active';
|
|
|
|
// Check cache first
|
|
const cacheKey = `rmm-devices:${companyId}:${activeFilter}`;
|
|
const cached = apiCache.get(cacheKey);
|
|
if (cached) {
|
|
console.log(`Cache hit for ${cacheKey}`);
|
|
return NextResponse.json(cached);
|
|
}
|
|
|
|
if (!companyId) {
|
|
return NextResponse.json({
|
|
rmmDevices: [],
|
|
autotaskDevices: [],
|
|
comparison: [],
|
|
message: 'Please select a company to view devices'
|
|
});
|
|
}
|
|
|
|
// Get Autotask devices based on active filter
|
|
const autotaskClient = getAutotaskClient();
|
|
let autotaskDevices: ConfigurationItem[] = [];
|
|
|
|
if (activeFilter === 'all') {
|
|
// Get all devices regardless of status
|
|
const allItems = await autotaskClient.queryEntity<ConfigurationItem>('ConfigurationItems', {
|
|
filter: [{ op: 'eq', field: 'companyID', value: parseInt(companyId) }],
|
|
});
|
|
autotaskDevices = allItems;
|
|
} else if (activeFilter === 'inactive') {
|
|
// Get only inactive devices
|
|
const inactiveItems = await autotaskClient.queryEntity<ConfigurationItem>('ConfigurationItems', {
|
|
filter: [
|
|
{ op: 'eq', field: 'companyID', value: parseInt(companyId) },
|
|
{ op: 'eq', field: 'isActive', value: false }
|
|
],
|
|
});
|
|
autotaskDevices = inactiveItems;
|
|
} else {
|
|
// Default: get only active devices
|
|
autotaskDevices = await autotaskClient.getConfigurationItemsByCompany(parseInt(companyId));
|
|
}
|
|
|
|
// Get RMM devices
|
|
let rmmDevices: DattoRMMDevice[] = [];
|
|
try {
|
|
const rmmClient = getDattoRMMClient();
|
|
|
|
if (companyName) {
|
|
// Try to get devices by company name (matching site name)
|
|
rmmDevices = await rmmClient.getDevicesByCompanyName(companyName);
|
|
} else {
|
|
// If no company name, get all devices and try to match
|
|
rmmDevices = await rmmClient.getAllDevices();
|
|
}
|
|
} catch (rmmError) {
|
|
console.error('Error fetching RMM devices:', rmmError);
|
|
// Continue with empty RMM devices array
|
|
}
|
|
|
|
// Compare and match devices
|
|
const comparison: DeviceComparison[] = [];
|
|
const matchedAutotaskIds = new Set<number>();
|
|
const matchedRmmIds = new Set<string>();
|
|
|
|
// Try to match devices
|
|
for (const rmmDevice of rmmDevices) {
|
|
let matched = false;
|
|
|
|
// Try to match by RMM Device UID
|
|
if (rmmDevice.uid) {
|
|
const autotaskMatch = autotaskDevices.find(
|
|
at => at.rmmDeviceUID === rmmDevice.uid && !matchedAutotaskIds.has(at.id)
|
|
);
|
|
|
|
if (autotaskMatch) {
|
|
comparison.push({
|
|
autotaskDevice: autotaskMatch,
|
|
rmmDevice: rmmDevice,
|
|
status: 'matched',
|
|
matchedBy: 'RMM UID'
|
|
});
|
|
matchedAutotaskIds.add(autotaskMatch.id);
|
|
matchedRmmIds.add(rmmDevice.id);
|
|
matched = true;
|
|
}
|
|
}
|
|
|
|
// Try to match by serial number
|
|
if (!matched && rmmDevice.serialNumber) {
|
|
const autotaskMatch = autotaskDevices.find(
|
|
at => (at.serialNumber === rmmDevice.serialNumber ||
|
|
at.dattoSerialNumber === rmmDevice.serialNumber) &&
|
|
!matchedAutotaskIds.has(at.id)
|
|
);
|
|
|
|
if (autotaskMatch) {
|
|
comparison.push({
|
|
autotaskDevice: autotaskMatch,
|
|
rmmDevice: rmmDevice,
|
|
status: 'matched',
|
|
matchedBy: 'Serial Number'
|
|
});
|
|
matchedAutotaskIds.add(autotaskMatch.id);
|
|
matchedRmmIds.add(rmmDevice.id);
|
|
matched = true;
|
|
}
|
|
}
|
|
|
|
// Try to match by hostname
|
|
if (!matched && rmmDevice.hostname) {
|
|
const autotaskMatch = autotaskDevices.find(
|
|
at => (at.rmmDeviceAuditHostname?.toLowerCase() === rmmDevice.hostname.toLowerCase() ||
|
|
at.dattoHostname?.toLowerCase() === rmmDevice.hostname.toLowerCase() ||
|
|
at.referenceTitle?.toLowerCase().includes(rmmDevice.hostname.toLowerCase())) &&
|
|
!matchedAutotaskIds.has(at.id)
|
|
);
|
|
|
|
if (autotaskMatch) {
|
|
comparison.push({
|
|
autotaskDevice: autotaskMatch,
|
|
rmmDevice: rmmDevice,
|
|
status: 'matched',
|
|
matchedBy: 'Hostname'
|
|
});
|
|
matchedAutotaskIds.add(autotaskMatch.id);
|
|
matchedRmmIds.add(rmmDevice.id);
|
|
matched = true;
|
|
}
|
|
}
|
|
|
|
// Try to match by IP address
|
|
if (!matched && (rmmDevice.intIpAddress || rmmDevice.extIpAddress)) {
|
|
const autotaskMatch = autotaskDevices.find(
|
|
at => (at.rmmDeviceAuditIPAddress === rmmDevice.intIpAddress ||
|
|
at.rmmDeviceAuditIPAddress === rmmDevice.extIpAddress ||
|
|
at.dattoInternalIP === rmmDevice.intIpAddress ||
|
|
at.dattoRemoteIP === rmmDevice.extIpAddress) &&
|
|
!matchedAutotaskIds.has(at.id)
|
|
);
|
|
|
|
if (autotaskMatch) {
|
|
comparison.push({
|
|
autotaskDevice: autotaskMatch,
|
|
rmmDevice: rmmDevice,
|
|
status: 'matched',
|
|
matchedBy: 'IP Address'
|
|
});
|
|
matchedAutotaskIds.add(autotaskMatch.id);
|
|
matchedRmmIds.add(rmmDevice.id);
|
|
matched = true;
|
|
}
|
|
}
|
|
|
|
// If no match found, add as RMM-only
|
|
if (!matched) {
|
|
comparison.push({
|
|
rmmDevice: rmmDevice,
|
|
status: 'rmm-only'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Add Autotask-only devices
|
|
for (const autotaskDevice of autotaskDevices) {
|
|
if (!matchedAutotaskIds.has(autotaskDevice.id)) {
|
|
comparison.push({
|
|
autotaskDevice: autotaskDevice,
|
|
status: 'autotask-only'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Sort comparison results
|
|
comparison.sort((a, b) => {
|
|
// Sort by status first (matched, then autotask-only, then rmm-only)
|
|
const statusOrder = { 'matched': 0, 'autotask-only': 1, 'rmm-only': 2 };
|
|
const statusDiff = statusOrder[a.status] - statusOrder[b.status];
|
|
if (statusDiff !== 0) return statusDiff;
|
|
|
|
// Then sort by device name
|
|
const aName = a.autotaskDevice?.referenceTitle || a.rmmDevice?.hostname || '';
|
|
const bName = b.autotaskDevice?.referenceTitle || b.rmmDevice?.hostname || '';
|
|
return aName.localeCompare(bName);
|
|
});
|
|
|
|
const response = {
|
|
rmmDevices,
|
|
autotaskDevices,
|
|
comparison,
|
|
stats: {
|
|
totalRmm: rmmDevices.length,
|
|
totalAutotask: autotaskDevices.length,
|
|
matched: comparison.filter(c => c.status === 'matched').length,
|
|
autotaskOnly: comparison.filter(c => c.status === 'autotask-only').length,
|
|
rmmOnly: comparison.filter(c => c.status === 'rmm-only').length,
|
|
}
|
|
};
|
|
|
|
// Cache for 2 minutes
|
|
apiCache.set(cacheKey, response, 120); // 120 seconds = 2 minutes
|
|
|
|
return NextResponse.json(response);
|
|
} catch (error) {
|
|
console.error('Error in RMM devices endpoint:', error);
|
|
|
|
if (error instanceof Error) {
|
|
return NextResponse.json(
|
|
{
|
|
error: 'Failed to fetch devices',
|
|
message: error.message,
|
|
},
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
|
|
return NextResponse.json(
|
|
{ error: 'An unexpected error occurred' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|