- 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
140 lines
3.4 KiB
TypeScript
140 lines
3.4 KiB
TypeScript
/**
|
|
* Global sync progress tracker
|
|
* Allows tracking progress of long-running sync operations
|
|
* Can be polled from the UI to show real-time progress
|
|
*/
|
|
|
|
export interface SyncProgressState {
|
|
syncId: string;
|
|
entityType: string;
|
|
status: 'idle' | 'running' | 'completed' | 'failed';
|
|
currentPage: number;
|
|
totalRecords: number;
|
|
estimatedTotal?: number;
|
|
startTime: number;
|
|
endTime?: number;
|
|
error?: string;
|
|
phase: 'fetching' | 'mapping' | 'upserting' | 'deleting' | 'completed';
|
|
}
|
|
|
|
class SyncProgressTracker {
|
|
private progressMap: Map<string, SyncProgressState> = new Map();
|
|
|
|
/**
|
|
* Start tracking a new sync operation
|
|
*/
|
|
startSync(syncId: string, entityType: string): void {
|
|
this.progressMap.set(syncId, {
|
|
syncId,
|
|
entityType,
|
|
status: 'running',
|
|
currentPage: 0,
|
|
totalRecords: 0,
|
|
startTime: Date.now(),
|
|
phase: 'fetching',
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update progress for a sync operation
|
|
*/
|
|
updateProgress(
|
|
syncId: string,
|
|
updates: Partial<Omit<SyncProgressState, 'syncId' | 'entityType' | 'startTime'>>
|
|
): void {
|
|
const current = this.progressMap.get(syncId);
|
|
if (!current) return;
|
|
|
|
this.progressMap.set(syncId, {
|
|
...current,
|
|
...updates,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Mark sync as completed
|
|
*/
|
|
completeSync(syncId: string, totalRecords: number): void {
|
|
const current = this.progressMap.get(syncId);
|
|
if (!current) return;
|
|
|
|
this.progressMap.set(syncId, {
|
|
...current,
|
|
status: 'completed',
|
|
totalRecords,
|
|
endTime: Date.now(),
|
|
phase: 'completed',
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Mark sync as failed
|
|
*/
|
|
failSync(syncId: string, error: string): void {
|
|
const current = this.progressMap.get(syncId);
|
|
if (!current) return;
|
|
|
|
this.progressMap.set(syncId, {
|
|
...current,
|
|
status: 'failed',
|
|
endTime: Date.now(),
|
|
error,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get progress for a specific sync
|
|
*/
|
|
getProgress(syncId: string): SyncProgressState | null {
|
|
return this.progressMap.get(syncId) || null;
|
|
}
|
|
|
|
/**
|
|
* Get all active syncs
|
|
*/
|
|
getActiveSyncs(): SyncProgressState[] {
|
|
return Array.from(this.progressMap.values()).filter(
|
|
(p) => p.status === 'running'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get the most recent sync for an entity type
|
|
*/
|
|
getLatestSync(entityType: string): SyncProgressState | null {
|
|
const syncs = Array.from(this.progressMap.values())
|
|
.filter((p) => p.entityType === entityType)
|
|
.sort((a, b) => b.startTime - a.startTime);
|
|
|
|
return syncs[0] || null;
|
|
}
|
|
|
|
/**
|
|
* Clean up old completed/failed syncs (keep last 10 per entity)
|
|
*/
|
|
cleanup(): void {
|
|
const byEntity = new Map<string, SyncProgressState[]>();
|
|
|
|
// Group by entity type
|
|
for (const progress of this.progressMap.values()) {
|
|
if (!byEntity.has(progress.entityType)) {
|
|
byEntity.set(progress.entityType, []);
|
|
}
|
|
byEntity.get(progress.entityType)!.push(progress);
|
|
}
|
|
|
|
// Keep only the 10 most recent per entity
|
|
for (const [entityType, syncs] of byEntity.entries()) {
|
|
const sorted = syncs.sort((a, b) => b.startTime - a.startTime);
|
|
const toKeep = sorted.slice(0, 10);
|
|
const toRemove = sorted.slice(10);
|
|
|
|
for (const sync of toRemove) {
|
|
this.progressMap.delete(sync.syncId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Global singleton instance
|
|
export const syncProgressTracker = new SyncProgressTracker();
|