wulf-pulse/autotask-app/app/api/rmm-devices/route.ts

242 lines
8.1 KiB
TypeScript
Raw Normal View History

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);
Add Addigy API integration and Docker deployment with Redis caching - Implemented complete Addigy API v2 client with authentication via x-api-key - Added device and policy endpoints with automatic org ID resolution - Created field mapping from snake_case to Title Case for UI compatibility - Handles nested 'facts' response structure from Addigy devices API - Added comprehensive API documentation in ADDIGY_API_GUIDE.md - Multi-stage Dockerfile with optimized production build - Custom ports: App on 3100, Redis on 6380 (avoids conflicts) - Docker Compose orchestration with health checks - Standalone Next.js output for smaller container images - Non-root user execution for security - Implemented Redis caching layer for API responses - 5-minute TTL with graceful fallback if Redis unavailable - Cache key structure: service:entity:filter1:filter2 - Applied to Addigy devices endpoint with cache hit/miss logging - Fixed TypeScript strict mode errors for production builds - Added null safety checks with optional chaining throughout API routes - Wrapped useSearchParams in Suspense boundary for Next.js 15+ compatibility - Fixed type assertions for dynamic API responses - Corrected Set<string> type mismatches in device comparison logic - Created DOCKER_README.md with complete deployment guide - Updated ADDIGY_API_GUIDE.md with real-world API patterns - Documented response structures, field mappings, and troubleshooting - Next.js 16.0.0 with Turbopack - Redis 7 with AOF persistence - Podman/Docker compatible - TypeScript strict mode compliant
2025-10-28 22:49:08 -04:00
matchedRmmIds.add(String(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);
Add Addigy API integration and Docker deployment with Redis caching - Implemented complete Addigy API v2 client with authentication via x-api-key - Added device and policy endpoints with automatic org ID resolution - Created field mapping from snake_case to Title Case for UI compatibility - Handles nested 'facts' response structure from Addigy devices API - Added comprehensive API documentation in ADDIGY_API_GUIDE.md - Multi-stage Dockerfile with optimized production build - Custom ports: App on 3100, Redis on 6380 (avoids conflicts) - Docker Compose orchestration with health checks - Standalone Next.js output for smaller container images - Non-root user execution for security - Implemented Redis caching layer for API responses - 5-minute TTL with graceful fallback if Redis unavailable - Cache key structure: service:entity:filter1:filter2 - Applied to Addigy devices endpoint with cache hit/miss logging - Fixed TypeScript strict mode errors for production builds - Added null safety checks with optional chaining throughout API routes - Wrapped useSearchParams in Suspense boundary for Next.js 15+ compatibility - Fixed type assertions for dynamic API responses - Corrected Set<string> type mismatches in device comparison logic - Created DOCKER_README.md with complete deployment guide - Updated ADDIGY_API_GUIDE.md with real-world API patterns - Documented response structures, field mappings, and troubleshooting - Next.js 16.0.0 with Turbopack - Redis 7 with AOF persistence - Podman/Docker compatible - TypeScript strict mode compliant
2025-10-28 22:49:08 -04:00
matchedRmmIds.add(String(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);
Add Addigy API integration and Docker deployment with Redis caching - Implemented complete Addigy API v2 client with authentication via x-api-key - Added device and policy endpoints with automatic org ID resolution - Created field mapping from snake_case to Title Case for UI compatibility - Handles nested 'facts' response structure from Addigy devices API - Added comprehensive API documentation in ADDIGY_API_GUIDE.md - Multi-stage Dockerfile with optimized production build - Custom ports: App on 3100, Redis on 6380 (avoids conflicts) - Docker Compose orchestration with health checks - Standalone Next.js output for smaller container images - Non-root user execution for security - Implemented Redis caching layer for API responses - 5-minute TTL with graceful fallback if Redis unavailable - Cache key structure: service:entity:filter1:filter2 - Applied to Addigy devices endpoint with cache hit/miss logging - Fixed TypeScript strict mode errors for production builds - Added null safety checks with optional chaining throughout API routes - Wrapped useSearchParams in Suspense boundary for Next.js 15+ compatibility - Fixed type assertions for dynamic API responses - Corrected Set<string> type mismatches in device comparison logic - Created DOCKER_README.md with complete deployment guide - Updated ADDIGY_API_GUIDE.md with real-world API patterns - Documented response structures, field mappings, and troubleshooting - Next.js 16.0.0 with Turbopack - Redis 7 with AOF persistence - Podman/Docker compatible - TypeScript strict mode compliant
2025-10-28 22:49:08 -04:00
matchedRmmIds.add(String(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);
Add Addigy API integration and Docker deployment with Redis caching - Implemented complete Addigy API v2 client with authentication via x-api-key - Added device and policy endpoints with automatic org ID resolution - Created field mapping from snake_case to Title Case for UI compatibility - Handles nested 'facts' response structure from Addigy devices API - Added comprehensive API documentation in ADDIGY_API_GUIDE.md - Multi-stage Dockerfile with optimized production build - Custom ports: App on 3100, Redis on 6380 (avoids conflicts) - Docker Compose orchestration with health checks - Standalone Next.js output for smaller container images - Non-root user execution for security - Implemented Redis caching layer for API responses - 5-minute TTL with graceful fallback if Redis unavailable - Cache key structure: service:entity:filter1:filter2 - Applied to Addigy devices endpoint with cache hit/miss logging - Fixed TypeScript strict mode errors for production builds - Added null safety checks with optional chaining throughout API routes - Wrapped useSearchParams in Suspense boundary for Next.js 15+ compatibility - Fixed type assertions for dynamic API responses - Corrected Set<string> type mismatches in device comparison logic - Created DOCKER_README.md with complete deployment guide - Updated ADDIGY_API_GUIDE.md with real-world API patterns - Documented response structures, field mappings, and troubleshooting - Next.js 16.0.0 with Turbopack - Redis 7 with AOF persistence - Podman/Docker compatible - TypeScript strict mode compliant
2025-10-28 22:49:08 -04:00
matchedRmmIds.add(String(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 }
);
}
}