- Add admin dashboard with sync controls and data browser - Implement RMM, Auvik, and Addigy organization mappings - Add chunked ticket sync with progress tracking - Implement entity sync service with rate limiting - Add analytics engine and performance optimizer - Create data browser for all PSA entities - Add navigation components and UI improvements - Implement background processing and sync services - Add comprehensive documentation and migration scripts - Update configuration items with multi-system support - Enhance contact management and purchase history - Add issue type assignment and LLM analyzer - Improve error handling and logging utilities
259 lines
9 KiB
TypeScript
259 lines
9 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from '@/components/ui/dialog';
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Skeleton } from '@/components/ui/skeleton';
|
|
import { PSATab } from './psa-tab';
|
|
import { RMMTab } from './rmm-tab';
|
|
import { AuvikTab } from './auvik-tab';
|
|
import { AddigyTab } from './addigy-tab';
|
|
import { StatusCards } from './status-cards';
|
|
import {
|
|
Server,
|
|
Monitor,
|
|
Network,
|
|
AlertCircle
|
|
} from 'lucide-react';
|
|
import { ConfigurationItem } from '@/lib/types/autotask';
|
|
import { DattoRMMDevice } from '@/lib/types/datto-rmm';
|
|
import { AuvikDevice } from '@/lib/types/auvik';
|
|
import { AddigyDevice } from '@/lib/types/addigy';
|
|
|
|
interface ConfigItemDetail {
|
|
autotaskDevice?: ConfigurationItem;
|
|
rmmDevice?: DattoRMMDevice;
|
|
auvikDevice?: AuvikDevice;
|
|
addigyDevice?: AddigyDevice;
|
|
companyName?: string;
|
|
}
|
|
|
|
interface ConfigItemModalProps {
|
|
itemId: string | number | null;
|
|
type?: 'autotask' | 'rmm';
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
rmmDevice?: DattoRMMDevice; // Pass RMM device directly from comparison
|
|
auvikDevice?: AuvikDevice; // Pass Auvik device directly from comparison
|
|
addigyDevice?: AddigyDevice; // Pass Addigy device directly from comparison
|
|
}
|
|
|
|
export function ConfigItemModal({
|
|
itemId,
|
|
type = 'autotask',
|
|
open,
|
|
onOpenChange,
|
|
rmmDevice: passedRmmDevice,
|
|
auvikDevice: passedAuvikDevice,
|
|
addigyDevice: passedAddigyDevice
|
|
}: ConfigItemModalProps) {
|
|
const [data, setData] = useState<ConfigItemDetail>({});
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (!open || !itemId) {
|
|
return;
|
|
}
|
|
|
|
const fetchData = async () => {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
console.log('Modal opening with:', {
|
|
itemId,
|
|
hasPassedRmmDevice: !!passedRmmDevice,
|
|
hasPassedAuvikDevice: !!passedAuvikDevice,
|
|
hasPassedAddigyDevice: !!passedAddigyDevice,
|
|
passedRmmDevice,
|
|
passedAuvikDevice,
|
|
passedAddigyDevice
|
|
});
|
|
|
|
try {
|
|
// For RMM-only, Auvik-only, or Addigy-only devices, skip API call and use passed data
|
|
if (itemId === 'rmm-only' || itemId === 'auvik-only' || itemId === 'addigy-only') {
|
|
console.log('Using passed devices only (no Autotask record)');
|
|
setData({
|
|
autotaskDevice: undefined,
|
|
rmmDevice: passedRmmDevice,
|
|
auvikDevice: passedAuvikDevice,
|
|
addigyDevice: passedAddigyDevice,
|
|
companyName: passedRmmDevice?.siteName || passedAuvikDevice?.deviceName || passedAddigyDevice?.['Device Name'] || 'Unknown',
|
|
});
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
|
|
// If we have passed devices, use lightweight endpoint that only fetches PSA data
|
|
// Otherwise use full endpoint that does RMM/Auvik/Addigy matching
|
|
const endpoint = (passedRmmDevice || passedAuvikDevice || passedAddigyDevice)
|
|
? `/api/configuration-items/${itemId}/lightweight`
|
|
: `/api/configuration-items/${itemId}?type=${type}`;
|
|
|
|
console.log(`Using ${passedRmmDevice || passedAuvikDevice ? 'lightweight' : 'full'} endpoint`);
|
|
|
|
const response = await fetch(endpoint);
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch configuration item');
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
console.log('Fetched result:', {
|
|
hasAutotask: !!result.autotaskDevice,
|
|
hasRmm: !!result.rmmDevice,
|
|
hasAuvik: !!result.auvikDevice,
|
|
hasAddigy: !!result.addigyDevice
|
|
});
|
|
|
|
// Always prioritize passed devices over fetched data
|
|
const finalData = {
|
|
autotaskDevice: result.autotaskDevice,
|
|
rmmDevice: passedRmmDevice || result.rmmDevice,
|
|
auvikDevice: passedAuvikDevice || result.auvikDevice,
|
|
addigyDevice: passedAddigyDevice || result.addigyDevice,
|
|
companyName: result.companyName,
|
|
};
|
|
|
|
console.log('Final data:', {
|
|
hasAutotask: !!finalData.autotaskDevice,
|
|
hasRmm: !!finalData.rmmDevice,
|
|
hasAuvik: !!finalData.auvikDevice,
|
|
hasAddigy: !!finalData.addigyDevice
|
|
});
|
|
|
|
setData(finalData);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'An error occurred');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
fetchData();
|
|
}, [itemId, type, open, passedRmmDevice, passedAuvikDevice, passedAddigyDevice]);
|
|
|
|
const handleUpdate = (updatedDevice: ConfigurationItem) => {
|
|
setData({ ...data, autotaskDevice: updatedDevice });
|
|
};
|
|
|
|
const device = data.autotaskDevice;
|
|
const rmmDevice = data.rmmDevice;
|
|
const auvikDevice = data.auvikDevice;
|
|
const addigyDevice = data.addigyDevice;
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent
|
|
className="w-[95vw] sm:w-[90vw] md:w-[85vw] lg:w-[80vw] xl:w-[75vw] h-[90vh] overflow-y-auto"
|
|
style={{ maxWidth: '1400px' }}
|
|
>
|
|
<DialogHeader>
|
|
<DialogTitle className="flex items-center gap-3">
|
|
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-purple-600 to-purple-700 text-white">
|
|
<Server className="h-4 w-4" />
|
|
</div>
|
|
<div>
|
|
<div className="text-lg font-semibold">
|
|
{device?.referenceTitle || rmmDevice?.hostname || 'Configuration Item'}
|
|
</div>
|
|
{data.companyName && (
|
|
<div className="text-sm font-normal text-muted-foreground">
|
|
{data.companyName}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</DialogTitle>
|
|
<DialogDescription>
|
|
View and manage configuration item details from PSA, RMM, Auvik, and Addigy systems
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
{loading ? (
|
|
<div className="space-y-4 py-4">
|
|
<Skeleton className="h-24 w-full" />
|
|
<Skeleton className="h-96 w-full" />
|
|
</div>
|
|
) : error ? (
|
|
<div className="py-8">
|
|
<div className="flex items-center gap-2 text-red-500 justify-center">
|
|
<AlertCircle className="w-5 h-5" />
|
|
<p>Error: {error}</p>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-6">
|
|
{/* Status Cards */}
|
|
<StatusCards device={device} rmmDevice={rmmDevice} />
|
|
|
|
{/* Tabs */}
|
|
<Tabs defaultValue="psa" className="space-y-4">
|
|
<TabsList className="grid w-full grid-cols-4">
|
|
<TabsTrigger value="psa" className="flex items-center gap-2">
|
|
<Server className="w-4 h-4" />
|
|
PSA Data
|
|
{device && (
|
|
<Badge variant="outline" className="ml-1">
|
|
{device.isActive ? 'Active' : 'Inactive'}
|
|
</Badge>
|
|
)}
|
|
</TabsTrigger>
|
|
<TabsTrigger value="rmm" className="flex items-center gap-2">
|
|
<Monitor className="w-4 h-4" />
|
|
RMM Data
|
|
{rmmDevice && (
|
|
<Badge variant="outline" className="ml-1">
|
|
{rmmDevice.online ? 'Online' : 'Offline'}
|
|
</Badge>
|
|
)}
|
|
</TabsTrigger>
|
|
<TabsTrigger value="auvik" className="flex items-center gap-2">
|
|
<Network className="w-4 h-4" />
|
|
Auvik Data
|
|
{auvikDevice && (
|
|
<Badge variant="outline" className="ml-1">
|
|
{auvikDevice.onlineStatus === 'online' ? 'Online' : 'Offline'}
|
|
</Badge>
|
|
)}
|
|
</TabsTrigger>
|
|
<TabsTrigger value="addigy" className="flex items-center gap-2">
|
|
<Monitor className="w-4 h-4" />
|
|
Addigy Data
|
|
{addigyDevice && (
|
|
<Badge variant="outline" className="ml-1">
|
|
{addigyDevice.online ? 'Online' : 'Offline'}
|
|
</Badge>
|
|
)}
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="psa">
|
|
<PSATab device={device} onUpdate={handleUpdate} />
|
|
</TabsContent>
|
|
|
|
<TabsContent value="rmm">
|
|
<RMMTab device={rmmDevice} />
|
|
</TabsContent>
|
|
|
|
<TabsContent value="auvik">
|
|
<AuvikTab device={auvikDevice} />
|
|
</TabsContent>
|
|
|
|
<TabsContent value="addigy">
|
|
<AddigyTab device={addigyDevice} />
|
|
</TabsContent>
|
|
</Tabs>
|
|
</div>
|
|
)}
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|