@power-maverick/tool-data-migrator 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,238 @@
1
+ # Data Migrator
2
+
3
+ Migrate data from one Dataverse environment to another with intelligent auto-mapping and smart operations.
4
+
5
+ ## Overview
6
+
7
+ The Data Migrator is a React-based tool designed exclusively for Power Platform ToolBox (PPTB) that enables seamless data transfer between Dataverse environments with advanced features:
8
+
9
+ - **Auto-Mapping**: Automatically map users, teams, and business units between environments
10
+ - **Smart Operations**: Choose between create, update, or upsert operations
11
+ - **Field Selection**: Select which fields to migrate
12
+ - **Lookup Handling**: Intelligent mapping of lookup fields and references
13
+ - **Progress Tracking**: Real-time progress monitoring with detailed status for each record
14
+ - **Batch Processing**: Efficient batch processing for large data sets
15
+
16
+ ## Features
17
+
18
+ ### Auto-Mapping
19
+
20
+ The tool automatically maps system entities between source and target environments:
21
+
22
+ - **Users**: Matched by domain name, email address, or full name
23
+ - **Teams**: Matched by name and team type
24
+ - **Business Units**: Matched by name
25
+
26
+ Each mapping includes a confidence level (high, medium, low) based on the matching criteria used.
27
+
28
+ ### Smart Migration Operations
29
+
30
+ Choose the operation that fits your scenario:
31
+
32
+ - **Create**: Insert new records only (fails if record exists)
33
+ - **Update**: Update existing records by primary key (requires records to exist)
34
+ - **Delete**: Delete records from target environment by matching primary key
35
+
36
+ ### Preview Before Migration
37
+
38
+ - Preview data before migration to verify the operation
39
+ - Shows action column (CREATE, UPDATE, DELETE)
40
+ - Displays primary ID, primary name, and selected fields
41
+ - Limited to first 100 records for quick review
42
+ - Confirm before starting the actual migration
43
+
44
+ ### Field and Lookup Management
45
+
46
+ - Select which fields to include in the migration
47
+ - Fields are loaded on-demand after entity selection for better performance
48
+ - Configure lookup field mapping strategies:
49
+ - **Auto-Map**: Automatically map system entities (users, teams, business units)
50
+ - **Skip**: Exclude the lookup field from migration
51
+
52
+ ### Flexible Filtering
53
+
54
+ - **OData Filters**: Use OData syntax for simple filtering
55
+ - Example: `statecode eq 0 and createdon gt 2024-01-01`
56
+ - **FetchXML Queries**: Use complete FetchXML for complex queries
57
+ - Supports advanced filtering, joins, and aggregations
58
+ - Toggle between filter types with a modern selector
59
+
60
+ ### Advanced Options
61
+
62
+ - **Filter Query**: Apply OData or FetchXML filters to select specific records
63
+ - **Batch Size**: Control batch size for optimal performance (1-100 records per batch)
64
+
65
+ ### Modern, Fluid UI
66
+
67
+ - **Step-based workflow**: Clear progression through configuration steps
68
+ - **Collapsible sections**: Minimize scrolling with expandable step cards
69
+ - **Modern design**: Gradient headers, card-based layout, smooth transitions
70
+ - **Responsive**: Works well on different screen sizes
71
+ - **Visual feedback**: Color-coded badges, progress indicators, and status displays
72
+
73
+ ### Progress Tracking
74
+
75
+ Real-time monitoring with:
76
+
77
+ - Overall progress bar
78
+ - Statistics (total, successful, failed, skipped)
79
+ - Batch processing status
80
+ - Detailed record-by-record status with error messages
81
+
82
+ ## Technical Stack
83
+
84
+ - **React 18** with TypeScript
85
+ - **Fluent UI React Components** for modern UI
86
+ - **Vite** for fast development and optimized builds
87
+ - **PPTB API** for all Dataverse operations
88
+ - **@pptb/types v1.0.16** - Latest PPTB type definitions
89
+
90
+ ## Installation
91
+
92
+ This tool is distributed as part of the PPTB-Tools monorepo and can be installed in Power Platform ToolBox.
93
+
94
+ ### Prerequisites
95
+
96
+ - Node.js >= 18.0.0
97
+ - npm >= 9.0.0
98
+
99
+ ### Build
100
+
101
+ ```bash
102
+ cd tools/data-migrator
103
+ npm install
104
+ npm run build
105
+ ```
106
+
107
+ The build output will be in the `dist` directory.
108
+
109
+ ## Usage in PPTB
110
+
111
+ The tool follows a step-by-step workflow:
112
+
113
+ 1. **Install the tool** in Power Platform ToolBox
114
+ 2. **Connect to your source environment** (primary connection)
115
+ 3. **Select a secondary connection** as the target environment
116
+ 4. **Open the Data Migrator tool**
117
+ 5. The tool will display both connections:
118
+ - Source (Primary): Your source environment
119
+ - Target (Secondary): Your target environment
120
+
121
+ ### Migration Workflow
122
+
123
+ 6. **Select an entity** to migrate
124
+ - Entity data (without fields) loads initially for faster performance
125
+ 7. **Wait for fields to load** automatically after entity selection
126
+ 8. **Select fields** to include in the migration
127
+ 9. **Add filter** (optional) to select a subset of records using OData syntax
128
+ 10. **Choose migration operation** from settings:
129
+ - **Create**: Insert new records only
130
+ - **Update**: Update existing records by primary key
131
+ - **Delete**: Delete records from target environment
132
+ 11. **Set batch size** (max 100 records per batch)
133
+ 12. **Auto-map system entities** (optional):
134
+ - Click "Auto-Map System Entities" to map users, teams, and business units
135
+ - Review the auto-mapping results
136
+ 13. **Preview the data** to be migrated:
137
+ - Shows Action column (CREATE, UPDATE, DELETE)
138
+ - Displays primary ID and primary name
139
+ - Shows selected fields
140
+ - Limited to first 100 records for preview
141
+ 14. **Start Migration** from the preview and monitor progress
142
+
143
+ ## Important Notes
144
+
145
+ - **Secondary Connection Required**: This tool requires both a primary (source) and secondary (target) connection to be configured in PPTB
146
+ - **Source Environment**: Data is read from the primary connection
147
+ - **Target Environment**: Data is written to the secondary connection
148
+ - **Auto-Mapping**: Users, teams, and business units are mapped between source and target environments
149
+ - **Preview First**: Always preview data before starting migration to verify the operation and data
150
+ - **Batch Limit**: Maximum batch size is 100 records for optimal performance
151
+
152
+ ## Use Cases
153
+
154
+ - **Environment Refresh**: Migrate configuration or transactional data after environment refresh
155
+ - **Dev to Test Migration**: Move test data from development to test environments
156
+ - **Cross-Tenant Migration**: Transfer data between different tenants with user/team mapping
157
+ - **Partial Data Migration**: Use filters to migrate specific records
158
+ - **Data Deletion**: Remove specific records from target environment based on source data
159
+
160
+ ## Architecture
161
+
162
+ ### Components
163
+
164
+ - **App.tsx**: Main application component and state management
165
+ - **EntitySelector**: Entity selection dropdown
166
+ - **OperationSelector**: Migration operation selection
167
+ - **FieldSelector**: Field selection with bulk actions
168
+ - **LookupMapper**: Lookup field mapping configuration
169
+ - **MigrationProgress**: Real-time progress display
170
+ - **AutoMappingPanel**: Auto-mapping results modal
171
+
172
+ ### Utilities
173
+
174
+ - **DataverseClient**: Handles all Dataverse API interactions via PPTB API
175
+ - **MigrationEngine**: Core migration logic with auto-mapping and transformation
176
+
177
+ ## Design Philosophy
178
+
179
+ The UI follows a **modern minimalist approach**:
180
+
181
+ - No header or unnecessary chrome
182
+ - Compact layout to minimize scrolling
183
+ - Clear visual hierarchy
184
+ - Progressive disclosure (options appear as needed)
185
+ - Real-time feedback
186
+ - Clean, professional appearance
187
+
188
+ ## Reference
189
+
190
+ This tool is inspired by [Colso.Xrm.DataTransporter](https://github.com/bcolpaert/Colso.Xrm.DataTransporter) with enhancements for modern React, PPTB integration, and improved user experience.
191
+
192
+ ## Limitations
193
+
194
+ - Primary key-based operations only (for update and upsert)
195
+ - Lookup auto-mapping limited to system entities (users, teams, business units)
196
+ - Requires same metadata schema in source and target environments
197
+ - Large data sets may take time to migrate (monitor batch progress)
198
+
199
+ ## Best Practices
200
+
201
+ 1. **Test First**: Always test with a small data set first
202
+ 2. **Use Filters**: Use OData filters to migrate specific records
203
+ 3. **Check Mappings**: Review auto-mapping results before starting migration
204
+ 4. **Monitor Progress**: Watch for errors during migration
205
+ 5. **Backup Data**: Always backup target environment before migration
206
+ 6. **Same Metadata**: Ensure source and target have matching entity/field schemas
207
+
208
+ ## Troubleshooting
209
+
210
+ ### Migration Fails
211
+
212
+ - Check that target environment has the same entity/field structure
213
+ - Verify required fields have values
214
+ - Review error messages in the progress panel
215
+
216
+ ### Lookup Mapping Issues
217
+
218
+ - Run auto-mapping before starting migration
219
+ - Verify users/teams/business units exist in target environment
220
+ - Check that names match between environments
221
+
222
+ ### Performance Issues
223
+
224
+ - Reduce batch size for better stability
225
+ - Use filters to migrate fewer records at once
226
+ - Check network connectivity
227
+
228
+ ## Contributing
229
+
230
+ Contributions are welcome! Please feel free to submit a Pull Request.
231
+
232
+ ## License
233
+
234
+ This project is licensed under the GPL-2.0 License - see the [LICENSE](../../LICENSE) file for details.
235
+
236
+ ## Support
237
+
238
+ For issues, questions, or contributions, please visit the [GitHub repository](https://github.com/Power-Maverick/PPTB-Tools).
package/dist/index.css ADDED
@@ -0,0 +1 @@
1
+ :root{--primary-color: #0078d4;--primary-hover: #106ebe;--primary-light: #deecf9;--error-color: #d13438;--success-color: #107c10;--warning-color: #ff8c00;--border-color: #e1e1e1;--background: #f5f5f5;--background-light: #faf9f8;--card-bg: #ffffff;--text-primary: #323130;--text-secondary: #605e5c;--shadow-sm: 0 1px 3px rgba(0, 0, 0, .08);--shadow-md: 0 2px 6px rgba(0, 0, 0, .12);--shadow-lg: 0 4px 12px rgba(0, 0, 0, .15)}*{box-sizing:border-box}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-size:14px;color:var(--text-primary);background-color:var(--background)}.app-container{min-height:100vh;background-color:var(--background)}.loading-container,.error-container{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:20px}.loading-spinner{width:40px;height:40px;border:4px solid var(--border-color);border-top-color:var(--primary-color);border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.error-banner{display:flex;justify-content:space-between;align-items:center;background-color:#fde7e9;color:var(--error-color);padding:12px 20px;border-left:4px solid var(--error-color);margin:16px;border-radius:4px;box-shadow:var(--shadow-sm)}.error-banner button{background:none;border:none;color:var(--error-color);font-size:20px;cursor:pointer;padding:0;width:24px;height:24px;display:flex;align-items:center;justify-content:center}.error-message-inline{display:flex;justify-content:space-between;align-items:center;background-color:#fde7e9;color:var(--error-color);padding:12px 16px;border-left:4px solid var(--error-color);margin-top:16px;border-radius:4px;box-shadow:var(--shadow-sm);animation:slideDown .3s ease-out}.error-message-inline button{background:none;border:none;color:var(--error-color);font-size:20px;cursor:pointer;padding:0;width:24px;height:24px;display:flex;align-items:center;justify-content:center}.error-message-progress{display:flex;justify-content:space-between;align-items:center;background-color:#fde7e9;color:var(--error-color);padding:12px 16px;border-left:4px solid var(--error-color);margin-bottom:16px;border-radius:4px}.error-message-progress button{background:none;border:none;color:var(--error-color);font-size:20px;cursor:pointer;padding:0;width:24px;height:24px;display:flex;align-items:center;justify-content:center}@keyframes slideDown{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.config-toolbar{display:flex;gap:12px;margin-bottom:20px;padding:12px;background:var(--card-bg);border-radius:8px;box-shadow:var(--shadow-sm)}.content-fluid{max-width:1200px;margin:0 auto;padding:20px}.header-card{background:linear-gradient(135deg,var(--primary-color) 0%,#005a9e 100%);border-radius:12px;padding:24px;margin-bottom:20px;box-shadow:var(--shadow-md);color:#fff}.header-content{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:16px}.app-title{margin:0;font-size:28px;font-weight:600;color:#fff}.connection-flow{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.connection-badge{background:#ffffff26;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border-radius:8px;padding:8px 16px;display:flex;flex-direction:column;gap:4px}.connection-badge.source{border-left:3px solid #4caf50}.connection-badge.target{border-left:3px solid #ff9800}.connection-label{font-size:10px;text-transform:uppercase;letter-spacing:.5px;opacity:.9}.connection-url{font-size:12px;font-weight:500}.flow-arrow{font-size:24px;opacity:.8}.steps-container{display:flex;flex-direction:column;gap:16px}.step-card{background:var(--card-bg);border-radius:12px;overflow:hidden;box-shadow:var(--shadow-sm);transition:all .3s ease}.step-card.active{box-shadow:var(--shadow-md)}.step-card.collapsed{box-shadow:var(--shadow-sm)}.step-card.collapsed .step-content{display:none}.step-header{display:flex;align-items:center;gap:16px;padding:16px 20px;background:var(--background-light);border-bottom:1px solid var(--border-color);cursor:pointer;transition:background .2s}.step-card.collapsed .step-header{border-bottom:none}.step-header:hover{background:#f0f0f0}.step-number{width:32px;height:32px;border-radius:50%;background:var(--primary-color);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:600;font-size:14px;flex-shrink:0}.step-title{margin:0;font-size:16px;font-weight:600;flex:1}.step-count{font-size:12px;color:var(--text-secondary);background:var(--primary-light);padding:4px 12px;border-radius:12px}.step-badge{font-size:11px;color:var(--text-secondary);background:#e0e0e0;padding:4px 10px;border-radius:10px;text-transform:uppercase;letter-spacing:.5px}.step-toggle{width:24px;height:24px;display:flex;align-items:center;justify-content:center;font-size:20px;font-weight:600;color:var(--primary-color);flex-shrink:0}.step-content{padding:20px}.loading-card{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;background:var(--card-bg);border-radius:12px;box-shadow:var(--shadow-sm)}.loading-card p{margin:12px 0 0;color:var(--text-secondary)}.form-group{margin-bottom:16px}.form-group label{display:block;margin-bottom:8px;font-weight:600;font-size:13px;color:var(--text-primary)}.modern-input,.modern-textarea,select.modern-input{width:100%;padding:10px 14px;border:2px solid var(--border-color);border-radius:8px;font-size:14px;font-family:inherit;transition:all .2s;background:#fff}.modern-input:focus,.modern-textarea:focus,select.modern-input:focus{outline:none;border-color:var(--primary-color);box-shadow:0 0 0 3px var(--primary-light)}.modern-textarea{resize:vertical;font-family:Courier New,monospace;line-height:1.5}.field-hint{font-size:12px;color:var(--text-secondary);margin:6px 0 0}.filter-type-selector{display:flex;gap:8px;margin-bottom:16px;background:var(--background-light);padding:4px;border-radius:8px}.filter-type-btn{flex:1;padding:8px 16px;border:none;background:transparent;border-radius:6px;cursor:pointer;font-weight:500;font-size:13px;color:var(--text-secondary);transition:all .2s}.filter-type-btn.active{background:#fff;color:var(--primary-color);box-shadow:var(--shadow-sm)}.filter-type-btn:hover:not(.active){background:#ffffff80}.settings-grid{display:grid;grid-template-columns:1fr 1fr;gap:20px}@media(max-width:768px){.settings-grid{grid-template-columns:1fr}}.operation-checkboxes{display:flex;flex-direction:column;gap:12px;padding:12px;background:var(--background-light);border-radius:8px;border:2px solid var(--border-color)}.action-bar{display:flex;justify-content:center;padding:24px 0}.btn-preview{background:linear-gradient(135deg,var(--primary-color) 0%,#005a9e 100%);color:#fff;border:none;padding:14px 48px;font-size:15px;font-weight:600;border-radius:28px;cursor:pointer;transition:all .3s ease;box-shadow:var(--shadow-md);text-transform:uppercase;letter-spacing:.5px}.btn-preview:hover:not(:disabled){transform:translateY(-2px);box-shadow:var(--shadow-lg)}.btn-preview:disabled{background:#ccc;cursor:not-allowed;transform:none}.btn-primary{background-color:var(--primary-color);color:#fff;border:none;padding:10px 24px;font-size:14px;font-weight:500;border-radius:6px;cursor:pointer;transition:all .2s}.btn-primary:hover:not(:disabled){background-color:var(--primary-hover);transform:translateY(-1px);box-shadow:var(--shadow-sm)}.btn-primary:disabled{background-color:#ccc;cursor:not-allowed}.btn-secondary{background-color:#fff;color:var(--text-primary);border:2px solid var(--border-color);padding:8px 20px;font-size:14px;font-weight:500;border-radius:6px;cursor:pointer;transition:all .2s}.btn-secondary:hover:not(:disabled){background-color:var(--background-light);border-color:var(--primary-color)}.progress-card{background:var(--card-bg);border-radius:12px;padding:24px;margin-top:20px;box-shadow:var(--shadow-md)}.field-list{max-height:400px;overflow-y:auto;border:2px solid var(--border-color);border-radius:8px;padding:12px;background:#fff}.checkbox-group{display:flex;align-items:center;gap:10px;margin-bottom:10px;padding:8px;border-radius:6px;transition:background .2s}.checkbox-group:hover{background:var(--background-light)}.checkbox-group input[type=checkbox]{width:18px;height:18px;cursor:pointer}.checkbox-group label{margin:0;cursor:pointer;font-weight:400;flex:1}.lookup-list{border:2px solid var(--border-color);border-radius:8px;padding:12px;margin-bottom:12px;background:#fff}.lookup-item{display:flex;align-items:center;gap:12px;padding:10px;border-bottom:1px solid var(--border-color);border-radius:6px;transition:background .2s}.lookup-item:hover{background:var(--background-light)}.lookup-item:last-child{border-bottom:none}.lookup-item label{flex:1;margin:0;font-weight:400}.lookup-item select{width:150px;padding:6px 10px;border:2px solid var(--border-color);border-radius:6px;font-size:13px;transition:border .2s}.lookup-item select:focus{outline:none;border-color:var(--primary-color)}.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.modal{background:var(--card-bg);padding:0;border-radius:16px;max-width:90vw;width:1000px;max-height:85vh;overflow:hidden;box-shadow:var(--shadow-lg);display:flex;flex-direction:column}.modal-header{display:flex;justify-content:space-between;align-items:center;padding:20px 24px;border-bottom:1px solid var(--border-color);background:var(--background-light)}.modal-header h3{margin:0;font-size:18px;font-weight:600}.modal-close{background:none;border:none;font-size:24px;cursor:pointer;padding:0;width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:50%;transition:background .2s}.modal-close:hover{background:#0000000d}.preview-info{margin:20px 24px;padding:12px;background-color:var(--primary-light);border-radius:8px;border-left:4px solid var(--primary-color)}.preview-table-container{overflow:auto;max-height:calc(85vh - 250px);margin:0 24px;border:2px solid var(--border-color);border-radius:8px}.preview-table{width:100%;border-collapse:collapse;font-size:13px}.preview-table thead{position:sticky;top:0;background-color:var(--background-light);z-index:1}.preview-table th{padding:12px 8px;text-align:left;border-bottom:2px solid var(--border-color);font-weight:600;white-space:nowrap}.preview-table td{padding:10px 8px;border-bottom:1px solid #f0f0f0;max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.preview-table tbody tr:hover{background-color:var(--background-light)}.action-badge{padding:4px 10px;border-radius:12px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.action-create{background-color:#e8f5e9;color:var(--success-color)}.action-update{background-color:#fff4e6;color:var(--warning-color)}.action-delete{background-color:#ffebee;color:var(--error-color)}.preview-note{text-align:center;padding:12px;color:var(--text-secondary);font-size:12px;background-color:var(--background-light)}.modal-footer{display:flex;justify-content:flex-end;gap:12px;padding:20px 24px;border-top:1px solid var(--border-color);background:var(--background-light)}.progress-panel{background:var(--card-bg);padding:24px;margin-bottom:16px;border-radius:12px;box-shadow:var(--shadow-md)}.progress-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.progress-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:16px;margin-bottom:24px}.stat-card{padding:16px;border:2px solid var(--border-color);border-radius:8px;text-align:center;background:#fff}.stat-value{font-size:28px;font-weight:700;margin-bottom:4px}.stat-label{font-size:11px;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.5px}.progress-bar{width:100%;height:10px;background-color:#e0e0e0;border-radius:10px;overflow:hidden;margin-bottom:24px}.progress-fill{height:100%;background:linear-gradient(90deg,var(--primary-color) 0%,#005a9e 100%);transition:width .3s ease;border-radius:10px}.record-list{max-height:400px;overflow-y:auto;border:2px solid var(--border-color);border-radius:8px;background:#fff}.record-item{display:flex;justify-content:space-between;align-items:center;padding:12px;border-bottom:1px solid #f0f0f0}.record-item:last-child{border-bottom:none}.record-status{display:flex;align-items:center;gap:8px}.status-badge{padding:4px 10px;border-radius:10px;font-size:11px;font-weight:600;text-transform:uppercase}.status-success{background-color:#e8f5e9;color:var(--success-color)}.status-error{background-color:#ffebee;color:var(--error-color)}.status-processing{background-color:#fff4e6;color:var(--warning-color)}.status-pending{background-color:#f5f5f5;color:var(--text-secondary)}.mapping-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:24px;margin-top:16px}@media(max-width:968px){.mapping-grid{grid-template-columns:1fr}}.mapping-section h4{margin:0 0 12px;font-size:14px;font-weight:600}.mapping-list{border:2px solid var(--border-color);border-radius:8px;max-height:300px;overflow-y:auto;background:#fff}.mapping-item{padding:10px 12px;border-bottom:1px solid #f0f0f0;font-size:13px}.mapping-item:last-child{border-bottom:none}.confidence-high{color:var(--success-color);font-weight:600}.confidence-medium{color:var(--warning-color);font-weight:600}.confidence-low{color:var(--error-color);font-weight:600}.lookup-item-container{border:2px solid var(--border-color);border-radius:8px;margin-bottom:12px;overflow:hidden;transition:all .2s ease}.lookup-item-container:hover{border-color:var(--primary-color);box-shadow:var(--shadow-sm)}.manual-mapping-panel{padding:16px;background:var(--background-light);border-top:1px solid var(--border-color);animation:slideDown .3s ease}@keyframes slideDown{0%{opacity:0;max-height:0}to{opacity:1;max-height:500px}}.manual-mapping-header{display:grid;grid-template-columns:1fr 1fr auto;gap:12px;padding:8px 12px;background:var(--card-bg);border-radius:6px;font-weight:600;font-size:12px;color:var(--text-secondary);text-transform:uppercase;margin-bottom:8px}.manual-mapping-row{display:grid;grid-template-columns:1fr 1fr auto;gap:12px;padding:10px 12px;background:var(--card-bg);border-radius:6px;align-items:center;margin-bottom:6px;transition:background .2s}.manual-mapping-row:hover{background:var(--primary-light)}.manual-mapping-row span{font-size:12px;font-family:Courier New,monospace;color:var(--text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.btn-delete{background:var(--error-color);color:#fff;border:none;border-radius:4px;padding:4px 12px;cursor:pointer;font-size:14px;font-weight:700;transition:all .2s;width:32px;height:32px;display:flex;align-items:center;justify-content:center}.btn-delete:hover{background:#a21c21;transform:scale(1.05)}.manual-mapping-add{display:grid;grid-template-columns:1fr 1fr auto;gap:12px;margin-top:12px;padding:12px;background:var(--card-bg);border-radius:6px;border:2px dashed var(--border-color)}.manual-mapping-hint{font-size:11px;color:var(--text-secondary);margin-top:8px;margin-bottom:0}.btn-add{background:var(--primary-color);color:#fff;border:none;border-radius:6px;padding:8px 20px;cursor:pointer;font-size:13px;font-weight:600;transition:all .2s;white-space:nowrap}.btn-add:hover:not(:disabled){background:var(--primary-hover);transform:translateY(-1px);box-shadow:var(--shadow-sm)}.btn-add:disabled{background:#ccc;cursor:not-allowed;opacity:.6}.btn-toggle{background:var(--primary-color);color:#fff;border:none;border-radius:6px;padding:6px 12px;cursor:pointer;font-size:16px;font-weight:700;transition:all .2s;min-width:36px;height:36px;display:flex;align-items:center;justify-content:center}.btn-toggle:hover{background:var(--primary-hover);transform:scale(1.05)}@media(max-width:768px){.content-fluid{padding:12px}.header-card{padding:16px}.app-title{font-size:22px}.connection-flow{width:100%}.step-content{padding:16px}.manual-mapping-header,.manual-mapping-row,.manual-mapping-add{grid-template-columns:1fr;gap:8px}.btn-delete{width:100%}}.btn-reset{background-color:#f3f2f1!important;color:var(--text-primary)!important;border:1px solid var(--border-color)!important}.btn-reset:hover:not(:disabled){background-color:#e1dfdd!important;color:var(--text-primary)!important}.progress-placeholder{text-align:center;padding:40px 20px;color:var(--text-secondary)}.progress-placeholder h3{font-size:18px;font-weight:600;margin:0 0 8px;color:var(--text-primary)}.progress-placeholder p{font-size:14px;margin:0;color:var(--text-secondary)}
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Data Migrator</title>
7
+ <script type="module" crossorigin src="/index.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/index.css">
9
+ </head>
10
+ <body>
11
+ <div id="root"></div>
12
+ </body>
13
+ </html>