wulf-pulse/components/configuration-items/addigy-tab.tsx
lorentz 8f955a0ff9 feat(07.1-05): user-tz on shared client components
- DetailModal: thread tz through resolveLabel(...) module helper +
  default export's 3 inline date/time calls.
- IntegrationStatusTabs: thread tz through fmtDate helper +
  VeeamTab sub-component prop.
- SyncScheduler: thread tz into closure-scoped formatDate helper.
- audit-log-table, user-table, user-sessions, active-sessions: inline
  toLocale calls in component body.
- analysis-view: useUserTimezone in AnalysisView; thread tz into 4
  toLocaleString calls.
- resolution-trend, volume-trend (recharts): module-scope fmtDate(iso)
  → fmtDate(iso, tz); useUserTimezone in named export; thread tz into
  axis tickFormatter + tooltip labelFormatter.
- ticket-detail-modal: thread tz into formatDate arrow inside
  TicketDetailModal.
- TimelineView: useUserTimezone; thread tz into 4 toLocale*String calls
  (hour/day/month/event-time formatters).
- ScoreCard: useUserTimezone in AggregateScoreCard; thread tz into the
  date-range latest call.
- addigy-tab: useUserTimezone in AddigyTab; thread tz into 2 inline calls.
- activity-sparkline: module-scope fmtHour(iso) → fmtHour(iso, tz);
  useUserTimezone in ActivitySparkline; update 3 callsites in title/aria.
- compliance-detail-table: thread tz from ComplianceDetailTable into
  ContractCoverageModal sub-component (2 inline date calls).
- company-backup-detail: module-scope formatDate(d) → formatDate(d, tz);
  useUserTimezone in CompanyBackupDetail; update 3 callsites.

Migrates 31 of 81 audit leak callsites.
2026-05-07 08:43:27 -04:00

362 lines
13 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 {
Smartphone,
Info,
HardDrive,
Shield,
Wifi,
XCircle,
Battery,
CheckCircle,
AlertCircle
} from 'lucide-react';
import { AddigyDevice } from '@/lib/types/addigy';
import { useUserTimezone } from '@/lib/hooks/use-user-timezone';
interface AddigyTabProps {
device?: AddigyDevice;
}
export function AddigyTab({ device }: AddigyTabProps) {
const tz = useUserTimezone();
if (!device) {
return (
<Card className="border-0 shadow-lg">
<CardContent className="pt-6">
<div className="text-center py-12 text-muted-foreground">
<Smartphone className="w-12 h-12 mx-auto mb-4 opacity-50" />
<p>No Addigy data available for this device</p>
</div>
</CardContent>
</Card>
);
}
const isOnline = device.online;
const batteryPercentage = device['Battery Percentage'];
const isCharging = device['Battery Charging'];
const freeSpacePercentage = device['Free Disk Percentage'];
return (
<Card className="border-0 shadow-lg">
<CardHeader className="bg-gradient-to-r from-orange-50 to-orange-100 dark:from-orange-950 dark:to-orange-900 rounded-t-lg">
<CardTitle className="text-lg flex items-center gap-2">
<Smartphone className="w-5 h-5" />
Addigy Device Information (Apple RMM)
</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>Device Name</Label>
<p className="text-sm text-muted-foreground mt-1">{device['Device Name']}</p>
</div>
<div>
<Label>Model</Label>
<p className="text-sm text-muted-foreground mt-1">
{device['Device Model Name'] || '-'}
</p>
</div>
<div>
<Label>Serial Number</Label>
<p className="text-sm text-muted-foreground font-mono mt-1">
{device['Serial Number'] || '-'}
</p>
</div>
<div>
<Label>Current User</Label>
<p className="text-sm text-muted-foreground mt-1">
{device['Current User'] || '-'}
</p>
</div>
<div>
<Label>Status</Label>
<div className="flex items-center gap-2 mt-1">
{isOnline ? (
<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>
)}
</div>
</div>
{device['Last Check In'] && (
<div>
<Label>Last Check In</Label>
<p className="text-sm text-muted-foreground mt-1">
{new Date(device['Last Check In']).toLocaleString(undefined, { timeZone: tz })}
</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['MAC OS X Version'] || device['iOS Version'] || '-'}
</p>
</div>
{device['Processor Type'] && (
<div>
<Label>Processor</Label>
<p className="text-sm text-muted-foreground mt-1">
{device['Processor Type']}
{device['Processor Speed (GHz)'] && ` @ ${device['Processor Speed (GHz)']} GHz`}
</p>
</div>
)}
{device['Total Disk Space (GB)'] && (
<div>
<Label>Disk Space</Label>
<div className="mt-1">
<p className="text-sm text-muted-foreground">
{device['Free Disk Space (GB)']} GB free of {device['Total Disk Space (GB)']} GB
</p>
{freeSpacePercentage !== undefined && (
<div className="flex items-center gap-2 mt-1">
<div className="flex-1 h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
<div
className={`h-full ${
freeSpacePercentage < 10 ? 'bg-red-500' :
freeSpacePercentage < 20 ? 'bg-orange-500' :
'bg-green-500'
}`}
style={{ width: `${freeSpacePercentage}%` }}
/>
</div>
<span className="text-xs text-muted-foreground">{freeSpacePercentage.toFixed(1)}%</span>
</div>
)}
</div>
</div>
)}
{batteryPercentage !== undefined && (
<div>
<Label>Battery</Label>
<div className="flex items-center gap-2 mt-1">
<Battery className={`w-4 h-4 ${isCharging ? 'text-green-600' : ''}`} />
<span className="text-sm text-muted-foreground">
{batteryPercentage}%
{isCharging && ' (Charging)'}
</span>
{device['Battery Capacity Loss Percentage'] !== undefined && (
<span className="text-xs text-muted-foreground">
({device['Battery Capacity Loss Percentage']}% capacity loss)
</span>
)}
</div>
</div>
)}
<div>
<Label>Agent Version</Label>
<p className="text-sm text-muted-foreground mt-1">
{device['Agent Version'] || '-'}
</p>
</div>
{device.Timezone && (
<div>
<Label>Timezone</Label>
<p className="text-sm text-muted-foreground mt-1">
{device.Timezone}
</p>
</div>
)}
</div>
</div>
{/* Security & Features */}
<div className="space-y-4">
<h3 className="font-semibold flex items-center gap-2">
<Shield className="w-4 h-4" />
Security & Features
</h3>
<div className="space-y-3">
<div>
<Label>Firewall</Label>
<div className="flex items-center gap-2 mt-1">
{device['Firewall Enabled'] ? (
<Badge variant="default" className="bg-green-600">
<CheckCircle className="w-3 h-3 mr-1" />
Enabled
</Badge>
) : (
<Badge variant="destructive">
<XCircle className="w-3 h-3 mr-1" />
Disabled
</Badge>
)}
</div>
</div>
<div>
<Label>FileVault</Label>
<div className="flex items-center gap-2 mt-1">
{device['FileVault Enabled'] ? (
<Badge variant="default" className="bg-green-600">
<CheckCircle className="w-3 h-3 mr-1" />
Enabled
</Badge>
) : (
<Badge variant="destructive">
<XCircle className="w-3 h-3 mr-1" />
Disabled
</Badge>
)}
</div>
</div>
{device['Remote Login Enabled'] !== undefined && (
<div>
<Label>Remote Login</Label>
<div className="flex items-center gap-2 mt-1">
{device['Remote Login Enabled'] ? (
<Badge variant="outline">
<CheckCircle className="w-3 h-3 mr-1" />
Enabled
</Badge>
) : (
<Badge variant="secondary">
<XCircle className="w-3 h-3 mr-1" />
Disabled
</Badge>
)}
</div>
</div>
)}
{device['SMART Failing'] !== undefined && (
<div>
<Label>SMART Status</Label>
<div className="flex items-center gap-2 mt-1">
{device['SMART Failing'] ? (
<Badge variant="destructive">
<AlertCircle className="w-3 h-3 mr-1" />
Failing
</Badge>
) : (
<Badge variant="default" className="bg-green-600">
<CheckCircle className="w-3 h-3 mr-1" />
Healthy
</Badge>
)}
</div>
</div>
)}
{device['Has Wireless'] !== undefined && (
<div>
<Label>Wireless Capability</Label>
<p className="text-sm text-muted-foreground mt-1">
{device['Has Wireless'] ? 'Yes' : 'No'}
</p>
</div>
)}
{device['XCode Installed'] !== undefined && (
<div>
<Label>XCode</Label>
<p className="text-sm text-muted-foreground mt-1">
{device['XCode Installed'] ? 'Installed' : 'Not Installed'}
</p>
</div>
)}
</div>
</div>
{/* Additional Information */}
<div className="space-y-4">
<h3 className="font-semibold flex items-center gap-2">
<Info className="w-4 h-4" />
Additional Information
</h3>
<div className="space-y-3">
<div>
<Label>Agent ID</Label>
<p className="text-sm text-muted-foreground font-mono mt-1">
{device.agentid}
</p>
</div>
<div>
<Label>Policy ID</Label>
<p className="text-sm text-muted-foreground font-mono mt-1">
{device.policy_id}
</p>
</div>
{device['Warranty Expiration Date'] && (
<div>
<Label>Warranty</Label>
<p className="text-sm text-muted-foreground mt-1">
Expires: {new Date(device['Warranty Expiration Date']).toLocaleDateString(undefined, { timeZone: tz })}
{device['Warranty Days Left'] !== undefined && (
<span className="ml-2">({device['Warranty Days Left']} days left)</span>
)}
</p>
</div>
)}
{device['TeamViewer Client Id'] && (
<div>
<Label>TeamViewer ID</Label>
<p className="text-sm text-muted-foreground font-mono mt-1">
{device['TeamViewer Client Id']}
</p>
</div>
)}
{device['Displays Serial Number'] && device['Displays Serial Number'].length > 0 && (
<div>
<Label>Display Serial Numbers</Label>
<div className="text-sm text-muted-foreground font-mono mt-1 space-y-1">
{device['Displays Serial Number'].map((serial, idx) => (
<div key={idx}>{serial}</div>
))}
</div>
</div>
)}
</div>
</div>
</div>
</CardContent>
</Card>
);
}