Skip to content

Instantly share code, notes, and snippets.

@dexit
Created June 13, 2025 15:58
Show Gist options
  • Save dexit/de8adbd96d76b002918bb7f8eb661712 to your computer and use it in GitHub Desktop.
Save dexit/de8adbd96d76b002918bb7f8eb661712 to your computer and use it in GitHub Desktop.
datamigration.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Migration Process Tracker</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* Custom styles for enhanced UI */
body {
background-color: #f8fafc;
font-family: 'Inter', sans-serif;
}
.card {
background-color: #ffffff;
border-radius: 0.75rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
transition: all 0.3s ease;
}
.card:hover {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
transform: translateY(-2px);
}
.log-entry {
transition: all 0.3s ease;
padding: 0.5rem 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.log-entry:last-child {
border-bottom: none;
}
.log-entry:hover {
background-color: rgba(255, 255, 255, 0.05);
}
.stage-container {
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
margin-bottom: 2rem;
}
.stage-container::before {
content: '';
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 4px;
background-color: #e5e7eb;
transform: translateY(-50%);
z-index: 0;
}
.stage-progress-line {
content: '';
position: absolute;
top: 50%;
left: 0;
height: 4px;
background-color: #3b82f6;
transform: translateY(-50%);
z-index: 1;
width: 0%;
transition: width 0.5s ease-in-out;
}
.stage-indicator {
position: relative;
z-index: 2;
text-align: center;
flex-grow: 1;
padding: 0 10px;
}
.stage-indicator:first-child {
text-align: left;
transform: translateX(10px);
}
.stage-indicator:last-child {
text-align: right;
transform: translateX(-10px);
}
.progress-dot {
width: 24px;
height: 24px;
border-radius: 50%;
background-color: #d1d5db;
border: 3px solid #ffffff;
margin: 0 auto 0.5rem auto;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
color: transparent;
font-size: 0.8rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.progress-dot.active {
background-color: #3b82f6;
border-color: #93c5fd;
box-shadow: 0 0 0 5px rgba(59, 130, 246, 0.3);
}
.progress-dot.completed {
background-color: #22c55e;
border-color: #86efac;
color: #ffffff;
}
.progress-dot.failed {
background-color: #ef4444;
border-color: #fca5a5;
color: #ffffff;
}
.progress-dot.pending {
background-color: #f59e0b;
border-color: #fcd34d;
color: #ffffff;
}
.stage-text {
font-size: 0.875rem;
font-weight: 500;
color: #6b7280;
transition: color 0.3s ease;
}
.stage-indicator.active .stage-text {
color: #3b82f6;
font-weight: 600;
}
.stage-indicator.completed .stage-text {
color: #22c55e;
font-weight: 600;
}
.stage-indicator.failed .stage-text {
color: #ef4444;
font-weight: 600;
}
.stage-indicator.pending .stage-text {
color: #f59e0b;
font-weight: 600;
}
/* Skeleton Loader Styles */
.skeleton {
background-color: #e5e7eb;
border-radius: 0.25rem;
animation: pulse 1.5s infinite ease-in-out;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.skeleton-text {
height: 1em;
margin-bottom: 0.5em;
}
.skeleton-text.short { width: 60%; }
.skeleton-text.medium { width: 80%; }
.skeleton-text.long { width: 100%; }
.skeleton-circle {
width: 2rem;
height: 2rem;
border-radius: 50%;
}
.skeleton-table-row {
display: flex;
justify-content: space-between;
padding: 0.75rem 1rem;
border-bottom: 1px solid #f3f4f6;
}
.skeleton-table-cell {
height: 1em;
width: 30%;
margin-right: 1rem;
}
.skeleton-table-cell:last-child {
margin-right: 0;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
/* Custom animations */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.fade-in {
animation: fadeIn 0.3s ease-in-out;
}
/* Status badges */
.status-badge {
padding: 0.25rem 0.5rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
display: inline-flex;
align-items: center;
}
.status-badge i {
margin-right: 0.25rem;
font-size: 0.625rem;
}
/* Tooltip */
.tooltip {
position: relative;
}
.tooltip .tooltip-text {
visibility: hidden;
width: 120px;
background-color: #1e293b;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
margin-left: -60px;
opacity: 0;
transition: opacity 0.3s;
font-size: 0.75rem;
}
.tooltip:hover .tooltip-text {
visibility: visible;
opacity: 1;
}
/* Custom tabs */
.tab {
padding: 0.75rem 1rem;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: all 0.2s ease;
}
.tab.active {
border-bottom-color: #3b82f6;
color: #3b82f6;
font-weight: 600;
}
.tab:hover:not(.active) {
border-bottom-color: #e5e7eb;
}
/* Custom dropdown */
.dropdown {
position: relative;
display: inline-block;
}
.dropdown-content {
display: none;
position: absolute;
right: 0;
background-color: #fff;
min-width: 160px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
z-index: 1;
border-radius: 0.5rem;
overflow: hidden;
}
.dropdown:hover .dropdown-content {
display: block;
}
.dropdown-item {
padding: 0.5rem 1rem;
display: block;
color: #4b5563;
transition: all 0.2s ease;
}
.dropdown-item:hover {
background-color: #f3f4f6;
color: #1f2937;
}
/* Custom switch */
.switch {
position: relative;
display: inline-block;
width: 40px;
height: 20px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 34px;
}
.slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 2px;
bottom: 2px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .slider {
background-color: #3b82f6;
}
input:checked + .slider:before {
transform: translateX(20px);
}
</style>
</head>
<body class="bg-gray-50 min-h-screen text-gray-800">
<div class="container mx-auto px-4 py-8">
<!-- Header -->
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4">
<div class="flex items-center">
<div class="bg-blue-600 text-white p-3 rounded-xl mr-4">
<i class="fas fa-exchange-alt text-2xl"></i>
</div>
<div>
<h1 class="text-3xl font-bold text-gray-800">Migration Process Tracker</h1>
<p class="text-gray-600">Monitor and manage your data migration workflows</p>
</div>
</div>
<div class="flex flex-col sm:flex-row gap-3 w-full md:w-auto">
<button id="refreshBtn" class="bg-white border border-gray-300 hover:bg-gray-50 text-gray-700 px-4 py-2 rounded-lg flex items-center justify-center transition duration-200">
<i class="fas fa-sync-alt mr-2"></i> Refresh
</button>
<button id="newMigrationBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg flex items-center justify-center font-medium transition duration-200 transform hover:scale-[1.02]">
<i class="fas fa-plus mr-2"></i> New Migration
</button>
</div>
</div>
<!-- Migration Dashboard -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<!-- Stats Card 1 -->
<div class="card">
<div class="p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm font-medium">Successful Migrations</p>
<h3 class="text-3xl font-bold text-gray-800 mt-1" id="successCount">0</h3>
</div>
<div class="p-3 rounded-full bg-green-100 text-green-600">
<i class="fas fa-check-circle text-2xl"></i>
</div>
</div>
<div class="mt-4">
<div class="flex items-center text-sm text-gray-600">
<span class="text-green-500 mr-1"><i class="fas fa-arrow-up"></i> 12%</span>
<span>vs last week</span>
</div>
</div>
</div>
</div>
<!-- Stats Card 2 -->
<div class="card">
<div class="p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm font-medium">Failed Migrations</p>
<h3 class="text-3xl font-bold text-gray-800 mt-1" id="errorCount">0</h3>
</div>
<div class="p-3 rounded-full bg-red-100 text-red-600">
<i class="fas fa-exclamation-circle text-2xl"></i>
</div>
</div>
<div class="mt-4">
<div class="flex items-center text-sm text-gray-600">
<span class="text-red-500 mr-1"><i class="fas fa-arrow-down"></i> 8%</span>
<span>vs last week</span>
</div>
</div>
</div>
</div>
<!-- Stats Card 3 -->
<div class="card">
<div class="p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm font-medium">In Progress</p>
<h3 class="text-3xl font-bold text-gray-800 mt-1" id="inProgressCount">0</h3>
</div>
<div class="p-3 rounded-full bg-blue-100 text-blue-600">
<i class="fas fa-sync-alt text-2xl"></i>
</div>
</div>
<div class="mt-4">
<div class="flex items-center text-sm text-gray-600">
<span class="text-blue-500 mr-1"><i class="fas fa-arrow-up"></i> 5%</span>
<span>vs last week</span>
</div>
</div>
</div>
</div>
<!-- Stats Card 4 -->
<div class="card">
<div class="p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm font-medium">Avg. Duration</p>
<h3 class="text-3xl font-bold text-gray-800 mt-1" id="avgDuration">0s</h3>
</div>
<div class="p-3 rounded-full bg-purple-100 text-purple-600">
<i class="fas fa-clock text-2xl"></i>
</div>
</div>
<div class="mt-4">
<div class="flex items-center text-sm text-gray-600">
<span class="text-purple-500 mr-1"><i class="fas fa-arrow-down"></i> 15%</span>
<span>vs last week</span>
</div>
</div>
</div>
</div>
</div>
<!-- Current Migration Process -->
<div class="card mb-8 overflow-hidden">
<!-- Process Header -->
<div class="bg-gray-50 px-6 py-4 border-b flex flex-col sm:flex-row justify-between items-start sm:items-center">
<div class="flex items-center mb-3 sm:mb-0">
<i class="fas fa-project-diagram mr-3 text-blue-600 text-xl"></i>
<h2 class="text-xl font-semibold text-gray-800">Current Migration Process</h2>
</div>
<div class="flex items-center space-x-4">
<div class="flex items-center text-sm text-gray-600">
<span class="mr-2">Last Updated:</span>
<span id="lastUpdated" class="font-medium">--:--:--</span>
</div>
<div class="flex items-center">
<label class="switch mr-2">
<input type="checkbox" id="autoRefreshToggle" checked>
<span class="slider"></span>
</label>
<span class="text-sm text-gray-600">Auto-refresh</span>
</div>
</div>
</div>
<!-- Process Stages -->
<div class="p-6">
<div class="stage-container">
<div class="stage-progress-line" id="stageProgressLine" style="width: 0%;"></div>
<div class="stage-indicator" data-stage="HubSpot Lookup">
<div class="progress-dot"></div>
<p class="stage-text">HubSpot Lookup</p>
</div>
<div class="stage-indicator" data-stage="PICS Init">
<div class="progress-dot"></div>
<p class="stage-text">PICS Init</p>
</div>
<div class="stage-indicator" data-stage="Applicant">
<div class="progress-dot"></div>
<p class="stage-text">Applicant</p>
</div>
<div class="stage-indicator" data-stage="Opportunity">
<div class="progress-dot"></div>
<p class="stage-text">Opportunity</p>
</div>
<div class="stage-indicator" data-stage="Complete">
<div class="progress-dot"></div>
<p class="stage-text">Complete</p>
</div>
</div>
<!-- Process Details -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
<div class="bg-gray-50 rounded-lg p-5">
<h3 class="font-semibold text-gray-700 mb-4 flex items-center text-lg">
<i class="fas fa-info-circle mr-3 text-blue-600"></i>
Migration Details
</h3>
<div class="space-y-3 text-gray-700">
<div class="flex justify-between">
<span class="text-gray-500">Object ID:</span>
<span id="objectId" class="font-medium">--</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500">Event ID:</span>
<span id="eventId" class="font-medium">--</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500">Current Stage:</span>
<span id="currentStage" class="font-medium">--</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500">Current Step:</span>
<span id="currentStep" class="font-medium">--</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500">Started:</span>
<span id="startTime" class="font-medium">--</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500">Duration:</span>
<span id="duration" class="font-medium">--</span>
</div>
</div>
</div>
<div class="bg-gray-50 rounded-lg p-5">
<h3 class="font-semibold text-gray-700 mb-4 flex items-center text-lg">
<i class="fas fa-cog mr-3 text-blue-600"></i>
System Info
</h3>
<div class="space-y-3 text-gray-700">
<div class="flex justify-between">
<span class="text-gray-500">API Status:</span>
<span class="font-medium text-green-600 flex items-center">
<span class="w-2 h-2 rounded-full bg-green-500 mr-2"></span>
Connected
</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500">Database:</span>
<span class="font-medium">MySQL 8.0</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500">Last Sync:</span>
<span class="font-medium">2 minutes ago</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500">Next Sync:</span>
<span class="font-medium">In 5 minutes</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500">Memory Usage:</span>
<div class="w-24 bg-gray-200 rounded-full h-2">
<div class="bg-blue-600 h-2 rounded-full" style="width: 45%"></div>
</div>
</div>
<div class="flex justify-between">
<span class="text-gray-500">CPU Load:</span>
<div class="w-24 bg-gray-200 rounded-full h-2">
<div class="bg-blue-600 h-2 rounded-full" style="width: 28%"></div>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 rounded-lg p-5">
<h3 class="font-semibold text-gray-700 mb-4 flex items-center text-lg">
<i class="fas fa-chart-line mr-3 text-blue-600"></i>
Performance Metrics
</h3>
<div class="space-y-3 text-gray-700">
<div class="flex justify-between">
<span class="text-gray-500">Records Processed:</span>
<span id="recordsProcessed" class="font-medium">0</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500">API Calls:</span>
<span id="apiCalls" class="font-medium">0</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500">Avg. API Latency:</span>
<span id="apiLatency" class="font-medium">0ms</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500">Data Volume:</span>
<span id="dataVolume" class="font-medium">0 MB</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500">Success Rate:</span>
<div class="w-24 bg-gray-200 rounded-full h-2">
<div class="bg-green-500 h-2 rounded-full" style="width: 92%"></div>
</div>
</div>
<div class="flex justify-between">
<span class="text-gray-500">Error Rate:</span>
<div class="w-24 bg-gray-200 rounded-full h-2">
<div class="bg-red-500 h-2 rounded-full" style="width: 8%"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Tabs for additional details -->
<div class="border-b border-gray-200 mb-6">
<div class="flex space-x-6">
<div class="tab active" data-tab="mappings">
<i class="fas fa-map-marked-alt mr-2"></i> Property Mappings
</div>
<div class="tab" data-tab="data">
<i class="fas fa-database mr-2"></i> Sample Data
</div>
<div class="tab" data-tab="errors">
<i class="fas fa-exclamation-triangle mr-2"></i> Errors & Warnings
</div>
<div class="tab" data-tab="logs">
<i class="fas fa-scroll mr-2"></i> Process Logs
</div>
</div>
</div>
<!-- Tab Content -->
<div id="mappingsContent" class="tab-content active">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Source Property</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Target Property</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Transformation</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">contact_id</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">externalId</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">String</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">Direct</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
Mapped
</span>
</td>
</tr>
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">firstname</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">firstName</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">String</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">Direct</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
Mapped
</span>
</td>
</tr>
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">lastname</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">lastName</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">String</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">Direct</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
Mapped
</span>
</td>
</tr>
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">email</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">email</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">String</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">Direct</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
Mapped
</span>
</td>
</tr>
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">createdate</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">createdDate</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">DateTime</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">Format conversion</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800">
Pending
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div id="dataContent" class="tab-content hidden">
<div class="bg-gray-50 rounded-lg p-5">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium text-gray-800">Sample Source Data</h3>
<div class="relative">
<button id="sampleDataDropdownBtn" class="flex items-center text-gray-600 hover:text-gray-900">
<span>JSON</span>
<i class="fas fa-chevron-down ml-2 text-sm"></i>
</button>
<div id="sampleDataDropdown" class="hidden absolute right-0 mt-2 w-32 bg-white rounded-md shadow-lg z-10">
<div class="py-1">
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" data-format="json">JSON</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" data-format="xml">XML</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" data-format="csv">CSV</a>
</div>
</div>
</div>
</div>
<div class="bg-gray-800 rounded-lg p-4 overflow-x-auto">
<pre class="text-green-400 text-sm"><code id="sampleDataJson">{
"contact_id": "12345",
"firstname": "John",
"lastname": "Doe",
"email": "[email protected]",
"createdate": "2023-01-15T08:30:00Z",
"status": "active",
"company": "Acme Inc",
"phone": "+1 (555) 123-4567"
}</code></pre>
<pre class="text-green-400 text-sm hidden" id="sampleDataXml"><code>&lt;contact&gt;
&lt;contact_id&gt;12345&lt;/contact_id&gt;
&lt;firstname&gt;John&lt;/firstname&gt;
&lt;lastname&gt;Doe&lt;/lastname&gt;
&lt;email&gt;[email protected]&lt;/email&gt;
&lt;createdate&gt;2023-01-15T08:30:00Z&lt;/createdate&gt;
&lt;status&gt;active&lt;/status&gt;
&lt;company&gt;Acme Inc&lt;/company&gt;
&lt;phone&gt;+1 (555) 123-4567&lt;/phone&gt;
&lt;/contact&gt;</code></pre>
<pre class="text-green-400 text-sm hidden" id="sampleDataCsv"><code>contact_id,firstname,lastname,email,createdate,status,company,phone
12345,John,Doe,[email protected],2023-01-15T08:30:00Z,active,Acme Inc,"+1 (555) 123-4567"</code></pre>
</div>
</div>
</div>
<div id="errorsContent" class="tab-content hidden">
<div class="bg-gray-50 rounded-lg p-5">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium text-gray-800">Errors & Warnings</h3>
<div class="flex space-x-2">
<button class="px-3 py-1 text-xs font-medium rounded-full bg-red-100 text-red-800">Errors (2)</button>
<button class="px-3 py-1 text-xs font-medium rounded-full bg-yellow-100 text-yellow-800">Warnings (4)</button>
<button class="px-3 py-1 text-xs font-medium rounded-full bg-blue-100 text-blue-800">All (6)</button>
</div>
</div>
<div class="space-y-4">
<div class="bg-white rounded-lg shadow-sm p-4 border-l-4 border-red-500">
<div class="flex justify-between items-start">
<div>
<h4 class="font-medium text-gray-800">Missing required field: email</h4>
<p class="text-sm text-gray-600 mt-1">Record ID: 12345, Stage: HubSpot Lookup</p>
</div>
<span class="text-xs font-medium text-red-500">Error</span>
</div>
<div class="mt-3 text-sm text-gray-700 bg-gray-50 p-3 rounded">
<p>Cannot proceed with migration as the email field is required but missing in the source data.</p>
</div>
</div>
<div class="bg-white rounded-lg shadow-sm p-4 border-l-4 border-yellow-500">
<div class="flex justify-between items-start">
<div>
<h4 class="font-medium text-gray-800">Date format mismatch</h4>
<p class="text-sm text-gray-600 mt-1">Record ID: 67890, Stage: PICS Init</p>
</div>
<span class="text-xs font-medium text-yellow-500">Warning</span>
</div>
<div class="mt-3 text-sm text-gray-700 bg-gray-50 p-3 rounded">
<p>The createdate field contains an unexpected format (MM/DD/YYYY instead of YYYY-MM-DD). Attempting to auto-convert.</p>
</div>
</div>
<div class="bg-white rounded-lg shadow-sm p-4 border-l-4 border-yellow-500">
<div class="flex justify-between items-start">
<div>
<h4 class="font-medium text-gray-800">Duplicate record detected</h4>
<p class="text-sm text-gray-600 mt-1">Record ID: 54321, Stage: Applicant</p>
</div>
<span class="text-xs font-medium text-yellow-500">Warning</span>
</div>
<div class="mt-3 text-sm text-gray-700 bg-gray-50 p-3 rounded">
<p>A record with the same email ([email protected]) already exists in the target system. Will update existing record instead of creating new one.</p>
</div>
</div>
</div>
</div>
</div>
<div id="logsContent" class="tab-content hidden">
<div class="bg-gray-50 rounded-lg p-5">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium text-gray-800">Process Logs</h3>
<div class="flex items-center space-x-2">
<div class="relative">
<button id="logLevelDropdownBtn" class="flex items-center text-gray-600 hover:text-gray-900">
<span>All Levels</span>
<i class="fas fa-chevron-down ml-2 text-sm"></i>
</button>
<div id="logLevelDropdown" class="hidden absolute right-0 mt-2 w-40 bg-white rounded-md shadow-lg z-10">
<div class="py-1">
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" data-level="all">All Levels</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" data-level="info">Info</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" data-level="warning">Warning</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" data-level="error">Error</a>
</div>
</div>
</div>
<button class="text-blue-600 hover:text-blue-800 text-sm font-medium">
<i class="fas fa-download mr-1"></i> Export
</button>
</div>
</div>
<div class="bg-gray-800 rounded-lg p-4 overflow-y-auto max-h-96">
<div class="space-y-2">
<div class="log-entry text-green-400 text-sm">
<span class="text-gray-400">[2023-06-15 09:15:23]</span> INFO: Migration process started
</div>
<div class="log-entry text-green-400 text-sm">
<span class="text-gray-400">[2023-06-15 09:15:25]</span> INFO: Connected to source system (HubSpot)
</div>
<div class="log-entry text-green-400 text-sm">
<span class="text-gray-400">[2023-06-15 09:15:27]</span> INFO: Connected to target system (PICS)
</div>
<div class="log-entry text-yellow-400 text-sm">
<span class="text-gray-400">[2023-06-15 09:15:30]</span> WARNING: Date format mismatch in record 67890 (createdate)
</div>
<div class="log-entry text-green-400 text-sm">
<span class="text-gray-400">[2023-06-15 09:15:35]</span> INFO: Processing batch 1 of 5 (20 records)
</div>
<div class="log-entry text-red-400 text-sm">
<span class="text-gray-400">[2023-06-15 09:15:38]</span> ERROR: Missing required field: email in record 12345
</div>
<div class="log-entry text-green-400 text-sm">
<span class="text-gray-400">[2023-06-15 09:15:40]</span> INFO: Batch 1 completed (19/20 records)
</div>
<div class="log-entry text-yellow-400 text-sm">
<span class="text-gray-400">[2023-06-15 09:15:45]</span> WARNING: Duplicate record detected (email: [email protected])
</div>
<div class="log-entry text-green-400 text-sm">
<span class="text-gray-400">[2023-06-15 09:15:50]</span> INFO: Processing batch 2 of 5 (20 records)
</div>
<div class="log-entry text-green-400 text-sm">
<span class="text-gray-400">[2023-06-15 09:15:55]</span> INFO: Batch 2 completed (20/20 records)
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Recent Migrations -->
<div class="card mb-8">
<div class="bg-gray-50 px-6 py-4 border-b">
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center">
<h2 class="text-xl font-semibold text-gray-800 mb-2 sm:mb-0">
<i class="fas fa-history mr-3 text-blue-600"></i> Recent Migrations
</h2>
<div class="flex items-center space-x-4">
<div class="relative">
<button id="timeRangeDropdownBtn" class="flex items-center text-gray-600 hover:text-gray-900">
<span>Last 24 hours</span>
<i class="fas fa-chevron-down ml-2 text-sm"></i>
</button>
<div id="timeRangeDropdown" class="hidden absolute right-0 mt-2 w-40 bg-white rounded-md shadow-lg z-10">
<div class="py-1">
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" data-range="1">Last hour</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" data-range="24">Last 24 hours</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" data-range="168">Last 7 days</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" data-range="720">Last 30 days</a>
</div>
</div>
</div>
<button class="text-blue-600 hover:text-blue-800 text-sm font-medium">
<i class="fas fa-filter mr-1"></i> Filter
</button>
</div>
</div>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Migration ID</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Object Type</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Records</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Started</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Duration</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-blue-600">MIG-2023-06-15-001</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">Contact</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
Completed
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">125</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">2023-06-15 09:15</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">2m 45s</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<a href="#" class="text-blue-600 hover:text-blue-900 mr-3">Details</a>
<a href="#" class="text-gray-600 hover:text-gray-900">Logs</a>
</td>
</tr>
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-blue-600">MIG-2023-06-15-002</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">Company</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800">
In Progress
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">42/87</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">2023-06-15 10:30</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">1m 12s</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<a href="#" class="text-blue-600 hover:text-blue-900 mr-3">Details</a>
<a href="#" class="text-gray-600 hover:text-gray-900">Logs</a>
</td>
</tr>
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-blue-600">MIG-2023-06-14-005</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">Deal</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">
Failed
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">0/63</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">2023-06-14 16:45</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">0m 23s</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<a href="#" class="text-blue-600 hover:text-blue-900 mr-3">Details</a>
<a href="#" class="text-gray-600 hover:text-gray-900">Logs</a>
</td>
</tr>
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-blue-600">MIG-2023-06-14-004</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">Contact</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
Completed
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">98</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">2023-06-14 14:20</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">1m 58s</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<a href="#" class="text-blue-600 hover:text-blue-900 mr-3">Details</a>
<a href="#" class="text-gray-600 hover:text-gray-900">Logs</a>
</td>
</tr>
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-blue-600">MIG-2023-06-14-003</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">Contact</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800">
Partial
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">87/120</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">2023-06-14 11:05</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">3m 12s</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<a href="#" class="text-blue-600 hover:text-blue-900 mr-3">Details</a>
<a href="#" class="text-gray-600 hover:text-gray-900">Logs</a>
</td>
</tr>
</tbody>
</table>
</div>
<div class="bg-gray-50 px-6 py-3 border-t">
<div class="flex flex-col sm:flex-row justify-between items-center">
<div class="text-sm text-gray-500 mb-2 sm:mb-0">
Showing <span class="font-medium">1</span> to <span class="font-medium">5</span> of <span class="font-medium">24</span> migrations
</div>
<div class="flex space-x-1">
<button class="px-3 py-1 rounded-md bg-white border border-gray-300 text-sm font-medium text-gray-700 hover:bg-gray-50">
Previous
</button>
<button class="px-3 py-1 rounded-md bg-blue-600 text-white text-sm font-medium hover:bg-blue-700">
1
</button>
<button class="px-3 py-1 rounded-md bg-white border border-gray-300 text-sm font-medium text-gray-700 hover:bg-gray-50">
2
</button>
<button class="px-3 py-1 rounded-md bg-white border border-gray-300 text-sm font-medium text-gray-700 hover:bg-gray-50">
3
</button>
<button class="px-3 py-1 rounded-md bg-white border border-gray-300 text-sm font-medium text-gray-700 hover:bg-gray-50">
Next
</button>
</div>
</div>
</div>
</div>
</div>
<script>
// Tab switching functionality
document.addEventListener('DOMContentLoaded', function() {
// Tab switching
const tabs = document.querySelectorAll('.tab');
tabs.forEach(tab => {
tab.addEventListener('click', function() {
// Remove active class from all tabs and content
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.add('hidden'));
// Add active class to clicked tab
this.classList.add('active');
// Show corresponding content
const tabName = this.getAttribute('data-tab');
document.getElementById(tabName + 'Content').classList.remove('hidden');
});
});
// Sample data format dropdown
const sampleDataDropdownBtn = document.getElementById('sampleDataDropdownBtn');
const sampleDataDropdown = document.getElementById('sampleDataDropdown');
sampleDataDropdownBtn.addEventListener('click', function(e) {
e.stopPropagation();
sampleDataDropdown.classList.toggle('hidden');
});
document.addEventListener('click', function() {
sampleDataDropdown.classList.add('hidden');
});
// Sample data format switching
document.querySelectorAll('#sampleDataDropdown a').forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const format = this.getAttribute('data-format');
sampleDataDropdownBtn.querySelector('span').textContent = format.toUpperCase();
// Hide all format previews
document.getElementById('sampleDataJson').classList.add('hidden');
document.getElementById('sampleDataXml').classList.add('hidden');
document.getElementById('sampleDataCsv').classList.add('hidden');
// Show selected format
document.getElementById('sampleData' + format.charAt(0).toUpperCase() + format.slice(1)).classList.remove('hidden');
sampleDataDropdown.classList.add('hidden');
});
});
// Log level dropdown
const logLevelDropdownBtn = document.getElementById('logLevelDropdownBtn');
const logLevelDropdown = document.getElementById('logLevelDropdown');
logLevelDropdownBtn.addEventListener('click', function(e) {
e.stopPropagation();
logLevelDropdown.classList.toggle('hidden');
});
document.addEventListener('click', function() {
logLevelDropdown.classList.add('hidden');
});
// Time range dropdown
const timeRangeDropdownBtn = document.getElementById('timeRangeDropdownBtn');
const timeRangeDropdown = document.getElementById('timeRangeDropdown');
timeRangeDropdownBtn.addEventListener('click', function(e) {
e.stopPropagation();
timeRangeDropdown.classList.toggle('hidden');
});
document.addEventListener('click', function() {
timeRangeDropdown.classList.add('hidden');
});
// Simulate migration progress
simulateMigrationProgress();
// Update stats
updateStats();
// Auto-refresh toggle
const autoRefreshToggle = document.getElementById('autoRefreshToggle');
let refreshInterval;
function startAutoRefresh() {
refreshInterval = setInterval(() => {
simulateMigrationProgress();
updateStats();
}, 5000);
}
function stopAutoRefresh() {
clearInterval(refreshInterval);
}
autoRefreshToggle.addEventListener('change', function() {
if (this.checked) {
startAutoRefresh();
} else {
stopAutoRefresh();
}
});
// Start auto-refresh initially
startAutoRefresh();
// Manual refresh button
document.getElementById('refreshBtn').addEventListener('click', function() {
simulateMigrationProgress();
updateStats();
});
// New migration button
document.getElementById('newMigrationBtn').addEventListener('click', function() {
alert('New migration workflow will start here!');
});
});
function simulateMigrationProgress() {
const stages = ['HubSpot Lookup', 'PICS Init', 'Applicant', 'Opportunity', 'Complete'];
const randomStageIndex = Math.floor(Math.random() * stages.length);
const currentStage = stages[randomStageIndex];
// Update progress line
const progressPercentage = (randomStageIndex / (stages.length - 1)) * 100;
document.getElementById('stageProgressLine').style.width = progressPercentage + '%';
// Update stage indicators
document.querySelectorAll('.stage-indicator').forEach((indicator, index) => {
const dot = indicator.querySelector('.progress-dot');
const text = indicator.querySelector('.stage-text');
indicator.classList.remove('active', 'completed', 'failed', 'pending');
dot.classList.remove('active', 'completed', 'failed', 'pending');
if (index < randomStageIndex) {
indicator.classList.add('completed');
dot.classList.add('completed');
dot.innerHTML = '<i class="fas fa-check"></i>';
} else if (index === randomStageIndex) {
indicator.classList.add('active');
dot.classList.add('active');
// Randomly set some stages to failed or pending
if (Math.random() < 0.2 && currentStage !== 'Complete') {
indicator.classList.add('failed');
dot.classList.add('failed');
dot.innerHTML = '<i class="fas fa-times"></i>';
} else if (Math.random() < 0.3 && currentStage !== 'Complete') {
indicator.classList.add('pending');
dot.classList.add('pending');
dot.innerHTML = '<i class="fas fa-clock"></i>';
}
}
});
// Update details
document.getElementById('currentStage').textContent = currentStage;
document.getElementById('currentStep').textContent = getRandomStep(currentStage);
document.getElementById('objectId').textContent = 'OBJ-' + Math.floor(Math.random() * 10000);
document.getElementById('eventId').textContent = 'EVT-' + Math.floor(Math.random() * 10000);
const now = new Date();
const startTime = new Date(now.getTime() - Math.floor(Math.random() * 30 + 5) * 1000);
document.getElementById('startTime').textContent = formatTime(startTime);
document.getElementById('lastUpdated').textContent = formatTime(now);
const durationSeconds = Math.floor((now - startTime) / 1000);
document.getElementById('duration').textContent = durationSeconds + 's';
// Update performance metrics
document.getElementById('recordsProcessed').textContent = Math.floor(Math.random() * 100);
document.getElementById('apiCalls').textContent = Math.floor(Math.random() * 50);
document.getElementById('apiLatency').textContent = Math.floor(Math.random() * 500) + 'ms';
document.getElementById('dataVolume').textContent = (Math.random() * 10).toFixed(2) + ' MB';
}
function updateStats() {
document.getElementById('successCount').textContent = Math.floor(Math.random() * 50);
document.getElementById('errorCount').textContent = Math.floor(Math.random() * 10);
document.getElementById('inProgressCount').textContent = Math.floor(Math.random() * 5);
document.getElementById('avgDuration').textContent = Math.floor(Math.random() * 120) + 's';
}
function formatTime(date) {
return date.toLocaleTimeString() + ' ' + date.toLocaleDateString();
}
function getRandomStep(stage) {
const steps = {
'HubSpot Lookup': ['Fetching contacts', 'Validating data', 'Preparing for transfer'],
'PICS Init': ['Establishing connection', 'Creating records', 'Mapping fields'],
'Applicant': ['Processing applications', 'Verifying details', 'Updating status'],
'Opportunity': ['Calculating values', 'Syncing deals', 'Finalizing records'],
'Complete': ['Finalizing', 'Generating report', 'Cleaning up']
};
const stageSteps = steps[stage] || ['Processing'];
return stageSteps[Math.floor(Math.random() * stageSteps.length)];
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment