'use client'; import { useState, useEffect, useCallback } from 'react'; import Link from 'next/link'; import { Button } from '@/components/ui/button'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { StatusBadge } from '@/components/ui/status-badge'; import { ArrowLeft, Activity, History, BookOpen, Loader2, RefreshCw, ExternalLink, CheckCircle2, XCircle, Clock, AlertTriangle, Building2, Monitor, Users, Key, FileText, Globe, Shield, Package, } from 'lucide-react'; function fmtDate(d: string | null) { if (!d) return 'Never'; return new Date(d).toLocaleString(undefined, { month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit', }); } function fmtDuration(ms: number | null) { if (!ms) return '—'; if (ms < 60000) return `${Math.round(ms / 1000)}s`; return `${Math.floor(ms / 60000)}m ${Math.round((ms % 60000) / 1000)}s`; } function StatCard({ label, value, sub, icon: Icon, cls, }: { label: string; value: string | number; sub?: string; icon?: React.ElementType; cls?: string; }) { return (
{Icon && }{label}
{value}
{sub &&
{sub}
}
); } function SyncStatusBadge({ status }: { status: string }) { const tone = status === 'completed' ? 'ok' : status === 'failed' ? 'error' : status === 'running' ? 'info' : 'warn'; return ( {status === 'running' && } {status} ); } function StatusTab({ syncData, onSync, syncing, }: { syncData: any; onSync: () => void; syncing: boolean; }) { if (!syncData) { return (
); } const counts = syncData.counts ?? {}; const latest = syncData.history?.[0]; return (
{/* Connection bar */}

Connected to IT Glue

Last sync: {fmtDate(latest?.completed_at ?? null)} {latest?.duration_ms && ` · ${fmtDuration(latest.duration_ms)}`}

{/* Record counts */}

Synced Records

{/* Latest sync entity breakdown */} {latest?.entities?.length > 0 && (

Last Sync Breakdown {latest.status && }

Entity Records Duration Status {latest.entities.map((e: any, i: number) => ( {e.entity} {e.recordsUpserted.toLocaleString()} {fmtDuration(e.duration)} {e.success ? : } ))}
)}
); } function HistoryTab({ history }: { history: any[] }) { if (!history.length) { return (
No sync history yet — run a sync to populate
); } return (
Status Triggered by Records Started Duration {history.map((row: any, i: number) => ( {row.triggered_by ?? 'system'} {(row.total_upserted ?? 0).toLocaleString()} {fmtDate(row.started_at)} {fmtDuration(row.duration_ms)} ))}
); } function AboutTab() { return (

Synced Entities

{[ ['Organizations', 'All IT Glue organizations with type, status, PSA linkage'], ['Locations', 'Physical locations per organization with address details'], ['Contacts', 'Contacts with emails, phones, type, and location linkage'], ['Configurations', 'All CIs with hostname, IP, serial, OS, manufacturer, model'], ['Flexible Assets', 'All flexible asset types with full trait data as JSONB'], ['Flexible Asset Types', 'Type definitions and field schemas'], ['Passwords', 'Credentials with category, folder, username, URL'], ['Password Folders', 'Folder hierarchy per organization'], ['Documents', 'IT Glue documents with full content'], ['Domains', 'Domain records with expiry and registrar info'], ['Expirations', 'All expiration records across organizations'], ['Reference Tables', 'Org types/statuses, config types/statuses, contact types, manufacturers, models, OS, platforms, countries'], ].map(([name, desc]) => (
{name} {desc}
))}

Authentication

API key via x-api-key header · Base URL: https://api.itglue.com

Response format: JSON:API (application/vnd.api+json)

Database Tables

All data is stored in tables prefixed itg_ in the Pulse PostgreSQL database. Each table includes a synced_at timestamp and uses ON CONFLICT DO UPDATE for idempotent upserts.

Notes

); } export default function ITGluePage() { const [syncData, setSyncData] = useState(null); const [syncing, setSyncing] = useState(false); const fetchStatus = useCallback(async () => { try { const res = await fetch('/api/itglue/sync'); if (res.ok) setSyncData(await res.json()); } catch {} }, []); useEffect(() => { fetchStatus(); }, [fetchStatus]); // Poll while sync is in progress useEffect(() => { if (!syncData?.inProgress && !syncing) return; const interval = setInterval(fetchStatus, 5000); return () => clearInterval(interval); }, [syncData?.inProgress, syncing, fetchStatus]); const handleSync = async () => { setSyncing(true); try { await fetch('/api/itglue/sync', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ triggeredBy: 'manual' }), }); await fetchStatus(); } catch { setSyncing(false); } // syncing flag cleared by poll detecting inProgress=false }; // Clear syncing flag once inProgress goes false useEffect(() => { if (syncData && !syncData.inProgress && syncing) { setSyncing(false); } }, [syncData, syncing]); return (
{/* Header */}

IT Glue

Organizations, configurations, contacts, passwords, flexible assets

Status History About
); }