338 lines
12 KiB
TypeScript
338 lines
12 KiB
TypeScript
|
|
'use client';
|
||
|
|
|
||
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||
|
|
import { Badge } from '@/components/ui/badge';
|
||
|
|
import { Label } from '@/components/ui/label';
|
||
|
|
import {
|
||
|
|
Monitor,
|
||
|
|
Info,
|
||
|
|
Network,
|
||
|
|
HardDrive,
|
||
|
|
Shield,
|
||
|
|
Wifi,
|
||
|
|
XCircle
|
||
|
|
} from 'lucide-react';
|
||
|
|
import { format } from 'date-fns';
|
||
|
|
import { DattoRMMDevice } from '@/lib/types/datto-rmm';
|
||
|
|
import { lookupDeviceBySerial, formatDeviceInfo } from '@/lib/utils/device-lookup';
|
||
|
|
|
||
|
|
interface RMMTabProps {
|
||
|
|
device?: DattoRMMDevice;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function RMMTab({ device }: RMMTabProps) {
|
||
|
|
if (!device) {
|
||
|
|
return (
|
||
|
|
<Card className="border-0 shadow-lg">
|
||
|
|
<CardContent className="pt-6">
|
||
|
|
<div className="text-center py-12 text-muted-foreground">
|
||
|
|
<Monitor className="w-12 h-12 mx-auto mb-4 opacity-50" />
|
||
|
|
<p>No RMM data available for this device</p>
|
||
|
|
</div>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Card className="border-0 shadow-lg">
|
||
|
|
<CardHeader className="bg-gradient-to-r from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 rounded-t-lg">
|
||
|
|
<CardTitle className="text-lg">RMM Device Information</CardTitle>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent className="pt-6">
|
||
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
|
|
{/* Basic Information */}
|
||
|
|
<div className="space-y-4">
|
||
|
|
<h3 className="font-semibold flex items-center gap-2">
|
||
|
|
<Info className="w-4 h-4" />
|
||
|
|
Basic Information
|
||
|
|
</h3>
|
||
|
|
|
||
|
|
<div className="space-y-3">
|
||
|
|
<div>
|
||
|
|
<Label>Hostname</Label>
|
||
|
|
<p className="text-sm text-muted-foreground mt-1">{device.hostname}</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<Label>Description</Label>
|
||
|
|
<p className="text-sm text-muted-foreground mt-1">
|
||
|
|
{device.description || '-'}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<Label>Serial Number</Label>
|
||
|
|
<p className="text-sm text-muted-foreground font-mono mt-1">
|
||
|
|
{device.serialNumber || '-'}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<Label>Device Type</Label>
|
||
|
|
<p className="text-sm text-muted-foreground mt-1">
|
||
|
|
{device.deviceType?.type || '-'}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<Label>Status</Label>
|
||
|
|
<div className="flex items-center gap-2 mt-1">
|
||
|
|
{device.online ? (
|
||
|
|
<Badge variant="default" className="bg-green-600">
|
||
|
|
<Wifi className="w-3 h-3 mr-1" />
|
||
|
|
Online
|
||
|
|
</Badge>
|
||
|
|
) : (
|
||
|
|
<Badge variant="secondary">
|
||
|
|
<XCircle className="w-3 h-3 mr-1" />
|
||
|
|
Offline
|
||
|
|
</Badge>
|
||
|
|
)}
|
||
|
|
{device.rebootRequired && (
|
||
|
|
<Badge variant="outline" className="text-orange-600">
|
||
|
|
Reboot Required
|
||
|
|
</Badge>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Network Information */}
|
||
|
|
<div className="space-y-4">
|
||
|
|
<h3 className="font-semibold flex items-center gap-2">
|
||
|
|
<Network className="w-4 h-4" />
|
||
|
|
Network Information
|
||
|
|
</h3>
|
||
|
|
|
||
|
|
<div className="space-y-3">
|
||
|
|
<div>
|
||
|
|
<Label>Internal IP</Label>
|
||
|
|
<p className="text-sm text-muted-foreground font-mono mt-1">
|
||
|
|
{device.intIpAddress || '-'}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<Label>External IP</Label>
|
||
|
|
<p className="text-sm text-muted-foreground font-mono mt-1">
|
||
|
|
{device.extIpAddress || '-'}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<Label>MAC Addresses</Label>
|
||
|
|
<div className="text-sm text-muted-foreground font-mono mt-1">
|
||
|
|
{device.macAddresses && device.macAddresses.length > 0 ? (
|
||
|
|
device.macAddresses.map((mac, i) => (
|
||
|
|
<div key={i}>{mac}</div>
|
||
|
|
))
|
||
|
|
) : (
|
||
|
|
'-'
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<Label>Domain</Label>
|
||
|
|
<p className="text-sm text-muted-foreground mt-1">
|
||
|
|
{device.domain || '-'}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<Label>Last Seen</Label>
|
||
|
|
<p className="text-sm text-muted-foreground mt-1">
|
||
|
|
{device.lastSeen ?
|
||
|
|
format(new Date(device.lastSeen), 'MMM d, yyyy h:mm a') :
|
||
|
|
'-'}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<Label>Last User</Label>
|
||
|
|
<p className="text-sm text-muted-foreground mt-1">
|
||
|
|
{device.lastLoggedInUser || '-'}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* System Information */}
|
||
|
|
<div className="space-y-4">
|
||
|
|
<h3 className="font-semibold flex items-center gap-2">
|
||
|
|
<HardDrive className="w-4 h-4" />
|
||
|
|
System Information
|
||
|
|
</h3>
|
||
|
|
|
||
|
|
<div className="space-y-3">
|
||
|
|
<div>
|
||
|
|
<Label>Operating System</Label>
|
||
|
|
<p className="text-sm text-muted-foreground mt-1">
|
||
|
|
{device.operatingSystem || '-'}
|
||
|
|
</p>
|
||
|
|
{device.a64Bit !== undefined && (
|
||
|
|
<Badge variant="outline" className="text-xs mt-1">
|
||
|
|
{device.a64Bit ? '64-bit' : '32-bit'}
|
||
|
|
</Badge>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<Label>Device Category</Label>
|
||
|
|
<p className="text-sm text-muted-foreground mt-1">
|
||
|
|
{device.deviceType?.category || '-'}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<Label>Manufacturer</Label>
|
||
|
|
<p className="text-sm text-muted-foreground mt-1">
|
||
|
|
{(() => {
|
||
|
|
if (device.manufacturer) return device.manufacturer;
|
||
|
|
const deviceInfo = lookupDeviceBySerial(device.serialNumber);
|
||
|
|
return deviceInfo?.manufacturer || '-';
|
||
|
|
})()}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<Label>Model</Label>
|
||
|
|
<p className="text-sm text-muted-foreground mt-1">
|
||
|
|
{(() => {
|
||
|
|
if (device.model) return device.model;
|
||
|
|
const deviceInfo = lookupDeviceBySerial(device.serialNumber);
|
||
|
|
return deviceInfo?.estimatedModel || deviceInfo?.modelFamily || '-';
|
||
|
|
})()}
|
||
|
|
</p>
|
||
|
|
{!device.model && device.serialNumber && (
|
||
|
|
<p className="text-xs text-muted-foreground mt-1 italic">
|
||
|
|
Estimated from serial: {device.serialNumber}
|
||
|
|
</p>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<Label>CPU</Label>
|
||
|
|
<p className="text-sm text-muted-foreground mt-1">
|
||
|
|
{device.cpuName || '-'} {device.cpuCores ? `(${device.cpuCores} cores)` : ''}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<Label>Memory</Label>
|
||
|
|
<p className="text-sm text-muted-foreground mt-1">
|
||
|
|
{device.memory ? `${(device.memory / 1024).toFixed(2)} GB` : '-'}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<Label>Total Disk Size</Label>
|
||
|
|
<p className="text-sm text-muted-foreground mt-1">
|
||
|
|
{device.diskSize ? `${(device.diskSize / (1024 * 1024 * 1024)).toFixed(2)} GB` : '-'}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<Label>Agent Version</Label>
|
||
|
|
<p className="text-sm text-muted-foreground mt-1">
|
||
|
|
{device.displayVersion || device.cagVersion || '-'}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<Label>Last Reboot</Label>
|
||
|
|
<p className="text-sm text-muted-foreground mt-1">
|
||
|
|
{device.lastReboot ?
|
||
|
|
format(new Date(device.lastReboot), 'MMM d, yyyy h:mm a') :
|
||
|
|
'-'}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<Label>Created Date</Label>
|
||
|
|
<p className="text-sm text-muted-foreground mt-1">
|
||
|
|
{device.creationDate ?
|
||
|
|
format(new Date(device.creationDate), 'MMM d, yyyy') :
|
||
|
|
'-'}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{device.warrantyDate && (
|
||
|
|
<div>
|
||
|
|
<Label>Warranty Expiration</Label>
|
||
|
|
<p className="text-sm text-muted-foreground mt-1">
|
||
|
|
{format(new Date(device.warrantyDate), 'MMM d, yyyy')}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Security Information */}
|
||
|
|
<div className="space-y-4">
|
||
|
|
<h3 className="font-semibold flex items-center gap-2">
|
||
|
|
<Shield className="w-4 h-4" />
|
||
|
|
Security Information
|
||
|
|
</h3>
|
||
|
|
|
||
|
|
<div className="space-y-3">
|
||
|
|
<div>
|
||
|
|
<Label>Antivirus</Label>
|
||
|
|
<div className="space-y-1 mt-1">
|
||
|
|
<p className="text-sm text-muted-foreground">
|
||
|
|
{device.antivirus?.antivirusProduct || 'Not detected'}
|
||
|
|
</p>
|
||
|
|
{device.antivirus?.antivirusStatus && (
|
||
|
|
<Badge
|
||
|
|
variant={device.antivirus.antivirusStatus === 'RunningAndUpToDate' ? 'default' : 'secondary'}
|
||
|
|
className={device.antivirus.antivirusStatus === 'RunningAndUpToDate' ? 'bg-green-600' : ''}
|
||
|
|
>
|
||
|
|
{device.antivirus.antivirusStatus.replace(/([A-Z])/g, ' $1').trim()}
|
||
|
|
</Badge>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<Label>Patch Management</Label>
|
||
|
|
<div className="space-y-2 mt-1">
|
||
|
|
{device.patchManagement?.patchStatus && (
|
||
|
|
<Badge
|
||
|
|
variant={device.patchManagement.patchStatus === 'FullyPatched' ? 'default' : 'secondary'}
|
||
|
|
className={device.patchManagement.patchStatus === 'FullyPatched' ? 'bg-green-600' : ''}
|
||
|
|
>
|
||
|
|
{device.patchManagement.patchStatus.replace(/([A-Z])/g, ' $1').trim()}
|
||
|
|
</Badge>
|
||
|
|
)}
|
||
|
|
<div className="flex gap-4">
|
||
|
|
<div>
|
||
|
|
<p className="text-xs text-muted-foreground">Pending</p>
|
||
|
|
<p className="text-lg font-semibold text-orange-600">
|
||
|
|
{device.patchManagement?.patchesApprovedPending || 0}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<p className="text-xs text-muted-foreground">Installed</p>
|
||
|
|
<p className="text-lg font-semibold text-green-600">
|
||
|
|
{device.patchManagement?.patchesInstalled || 0}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<p className="text-xs text-muted-foreground">Not Approved</p>
|
||
|
|
<p className="text-lg font-semibold text-gray-600">
|
||
|
|
{device.patchManagement?.patchesNotApproved || 0}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
);
|
||
|
|
}
|