payload-wordpress-migrator 0.0.23 → 0.0.24
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/dist/components/MigrationDashboardClient.js +2 -8
- package/dist/components/MigrationDashboardClient.js.map +1 -1
- package/dist/components/dashboard/SiteConfigPanel.d.ts +3 -9
- package/dist/components/dashboard/SiteConfigPanel.js +60 -170
- package/dist/components/dashboard/SiteConfigPanel.js.map +1 -1
- package/dist/components/dashboard/index.d.ts +1 -1
- package/dist/components/dashboard/types.d.ts +0 -8
- package/dist/components/dashboard/useMigrationDashboard.d.ts +2 -8
- package/dist/components/dashboard/useMigrationDashboard.js +33 -195
- package/dist/components/dashboard/useMigrationDashboard.js.map +1 -1
- package/dist/utils/endpoints/handlers.js +26 -15
- package/dist/utils/endpoints/handlers.js.map +1 -1
- package/dist/utils/migration/jobCrud.js +4 -4
- package/dist/utils/migration/jobCrud.js.map +1 -1
- package/dist/utils/migration/orchestrator.d.ts +2 -2
- package/dist/utils/migration/orchestrator.js +5 -5
- package/dist/utils/migration/orchestrator.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useState, useEffect } from 'react';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
const persistScanResult = (config)=>{
|
|
5
|
+
localStorage.setItem('wp-scan-result', JSON.stringify(config));
|
|
6
|
+
};
|
|
7
|
+
const loadScanResult = ()=>{
|
|
8
|
+
try {
|
|
9
|
+
const saved = localStorage.getItem('wp-scan-result');
|
|
10
|
+
return saved ? JSON.parse(saved) : null;
|
|
11
|
+
} catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
8
14
|
};
|
|
9
15
|
const useMigrationDashboard = (initialSummary)=>{
|
|
10
16
|
const [summary, setSummary] = useState(initialSummary);
|
|
@@ -13,17 +19,8 @@ const useMigrationDashboard = (initialSummary)=>{
|
|
|
13
19
|
const [siteConfig, setSiteConfig] = useState({
|
|
14
20
|
connectionStatus: 'not-scanned',
|
|
15
21
|
discoveredContent: [],
|
|
16
|
-
|
|
17
|
-
totalItems: 0,
|
|
18
|
-
wpPassword: '',
|
|
19
|
-
wpSiteUrl: '',
|
|
20
|
-
wpUsername: ''
|
|
21
|
-
});
|
|
22
|
-
const [configState, setConfigState] = useState({
|
|
23
|
-
isEditing: true,
|
|
24
|
-
isSaved: false
|
|
22
|
+
totalItems: 0
|
|
25
23
|
});
|
|
26
|
-
const [showSiteConfig, setShowSiteConfig] = useState(true);
|
|
27
24
|
const addLog = (message)=>{
|
|
28
25
|
const timestamp = new Date().toLocaleTimeString();
|
|
29
26
|
const logEntry = `[${timestamp}] ${message}`;
|
|
@@ -32,12 +29,8 @@ const useMigrationDashboard = (initialSummary)=>{
|
|
|
32
29
|
logEntry
|
|
33
30
|
]);
|
|
34
31
|
};
|
|
35
|
-
const clearLogs = ()=>{
|
|
36
|
-
setLogs([]);
|
|
37
|
-
};
|
|
38
32
|
const refreshDashboard = async ()=>{
|
|
39
33
|
try {
|
|
40
|
-
console.trace('Call stack:');
|
|
41
34
|
const response = await fetch('/api/wordpress/migration-summary?refresh=true');
|
|
42
35
|
const newSummary = await response.json();
|
|
43
36
|
setSummary(newSummary);
|
|
@@ -47,26 +40,11 @@ const useMigrationDashboard = (initialSummary)=>{
|
|
|
47
40
|
};
|
|
48
41
|
// Load initial data on mount
|
|
49
42
|
useEffect(()=>{
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const savedConfig = localStorage.getItem('wp-site-config');
|
|
56
|
-
if (savedConfig) {
|
|
57
|
-
const parsed = JSON.parse(savedConfig);
|
|
58
|
-
setSiteConfig(parsed);
|
|
59
|
-
setConfigState({
|
|
60
|
-
isEditing: false,
|
|
61
|
-
isSaved: true
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
} catch (error) {
|
|
65
|
-
console.error('Failed to load saved config:', error);
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
void loadInitialData();
|
|
69
|
-
loadSavedConfig();
|
|
43
|
+
void refreshDashboard();
|
|
44
|
+
const saved = loadScanResult();
|
|
45
|
+
if (saved) {
|
|
46
|
+
setSiteConfig(saved);
|
|
47
|
+
}
|
|
70
48
|
}, []);
|
|
71
49
|
// Smart periodic refresh - only when jobs are active
|
|
72
50
|
useEffect(()=>{
|
|
@@ -84,20 +62,6 @@ const useMigrationDashboard = (initialSummary)=>{
|
|
|
84
62
|
}, [
|
|
85
63
|
summary.activeJobs
|
|
86
64
|
]);
|
|
87
|
-
// Auto-close site config when scan is successful AND config is saved
|
|
88
|
-
useEffect(()=>{
|
|
89
|
-
if (siteConfig.connectionStatus === 'scanned' && configState.isSaved && !configState.isEditing && siteConfig.discoveredContent && siteConfig.discoveredContent.length > 0) {
|
|
90
|
-
const timer = setTimeout(()=>{
|
|
91
|
-
setShowSiteConfig(false);
|
|
92
|
-
}, 5000);
|
|
93
|
-
return ()=>clearTimeout(timer);
|
|
94
|
-
}
|
|
95
|
-
}, [
|
|
96
|
-
siteConfig.connectionStatus,
|
|
97
|
-
configState.isSaved,
|
|
98
|
-
configState.isEditing,
|
|
99
|
-
siteConfig.discoveredContent
|
|
100
|
-
]);
|
|
101
65
|
// Only refresh on significant page visibility changes
|
|
102
66
|
useEffect(()=>{
|
|
103
67
|
let lastVisibilityChange = Date.now();
|
|
@@ -123,7 +87,7 @@ const useMigrationDashboard = (initialSummary)=>{
|
|
|
123
87
|
if (event.type?.startsWith('migration-job-')) {
|
|
124
88
|
void refreshDashboard();
|
|
125
89
|
}
|
|
126
|
-
} catch
|
|
90
|
+
} catch {
|
|
127
91
|
// Ignore parsing errors
|
|
128
92
|
}
|
|
129
93
|
}
|
|
@@ -131,27 +95,6 @@ const useMigrationDashboard = (initialSummary)=>{
|
|
|
131
95
|
window.addEventListener('storage', handleStorageChange);
|
|
132
96
|
return ()=>window.removeEventListener('storage', handleStorageChange);
|
|
133
97
|
}, []);
|
|
134
|
-
const validateSiteConfig = ()=>{
|
|
135
|
-
const { siteName, wpPassword, wpSiteUrl, wpUsername } = siteConfig;
|
|
136
|
-
if (!siteName.trim() || !wpSiteUrl.trim() || !wpUsername.trim() || !wpPassword.trim()) {
|
|
137
|
-
alert('Please fill in all WordPress site configuration fields');
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
return true;
|
|
141
|
-
};
|
|
142
|
-
const requireSiteConfig = (actionVerb)=>{
|
|
143
|
-
if (!configState.isSaved || !validateSiteConfig() || siteConfig.connectionStatus !== 'scanned') {
|
|
144
|
-
alert(`Please save a valid WordPress site configuration and scan for content before ${actionVerb} migration jobs.`);
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
return true;
|
|
148
|
-
};
|
|
149
|
-
const getSiteCredentials = ()=>({
|
|
150
|
-
siteName: siteConfig.siteName,
|
|
151
|
-
wpPassword: siteConfig.wpPassword,
|
|
152
|
-
wpSiteUrl: siteConfig.wpSiteUrl,
|
|
153
|
-
wpUsername: siteConfig.wpUsername
|
|
154
|
-
});
|
|
155
98
|
const getJobName = (jobId)=>{
|
|
156
99
|
const job = summary.recentJobs?.find((j)=>j.id === jobId);
|
|
157
100
|
return job?.jobName || `Job ${jobId}`;
|
|
@@ -160,16 +103,14 @@ const useMigrationDashboard = (initialSummary)=>{
|
|
|
160
103
|
const startMigrationJob = async (jobId)=>{
|
|
161
104
|
try {
|
|
162
105
|
setLoading(true);
|
|
163
|
-
if (!requireSiteConfig('starting')) return;
|
|
164
106
|
await refreshDashboard();
|
|
165
|
-
|
|
107
|
+
setLogs([]);
|
|
166
108
|
const jobName = getJobName(jobId);
|
|
167
109
|
addLog(`Migration "${jobName}" started`);
|
|
168
110
|
const response = await fetch(`/api/wordpress/migration-jobs`, {
|
|
169
111
|
body: JSON.stringify({
|
|
170
112
|
action: 'start',
|
|
171
|
-
jobId
|
|
172
|
-
siteConfig: getSiteCredentials()
|
|
113
|
+
jobId
|
|
173
114
|
}),
|
|
174
115
|
headers: {
|
|
175
116
|
'Content-Type': 'application/json'
|
|
@@ -209,7 +150,6 @@ const useMigrationDashboard = (initialSummary)=>{
|
|
|
209
150
|
const resumeMigrationJob = async (jobId)=>{
|
|
210
151
|
try {
|
|
211
152
|
setLoading(true);
|
|
212
|
-
if (!requireSiteConfig('resuming')) return;
|
|
213
153
|
const jobName = getJobName(jobId);
|
|
214
154
|
addLog(`Migration "${jobName}" resumed`);
|
|
215
155
|
const response = await fetch(`/api/wordpress/migration-jobs?jobId=${jobId}&action=resume`, {
|
|
@@ -223,8 +163,7 @@ const useMigrationDashboard = (initialSummary)=>{
|
|
|
223
163
|
const startResponse = await fetch(`/api/wordpress/migration-jobs`, {
|
|
224
164
|
body: JSON.stringify({
|
|
225
165
|
action: 'resume',
|
|
226
|
-
jobId
|
|
227
|
-
siteConfig: getSiteCredentials()
|
|
166
|
+
jobId
|
|
228
167
|
}),
|
|
229
168
|
headers: {
|
|
230
169
|
'Content-Type': 'application/json'
|
|
@@ -247,14 +186,12 @@ const useMigrationDashboard = (initialSummary)=>{
|
|
|
247
186
|
const retryMigrationJob = async (jobId)=>{
|
|
248
187
|
try {
|
|
249
188
|
setLoading(true);
|
|
250
|
-
if (!requireSiteConfig('retrying')) return;
|
|
251
189
|
const jobName = getJobName(jobId);
|
|
252
190
|
addLog(`Retrying migration "${jobName}"`);
|
|
253
191
|
const response = await fetch(`/api/wordpress/migration-jobs`, {
|
|
254
192
|
body: JSON.stringify({
|
|
255
193
|
action: 'retry',
|
|
256
|
-
jobId
|
|
257
|
-
siteConfig: getSiteCredentials()
|
|
194
|
+
jobId
|
|
258
195
|
}),
|
|
259
196
|
headers: {
|
|
260
197
|
'Content-Type': 'application/json'
|
|
@@ -277,18 +214,14 @@ const useMigrationDashboard = (initialSummary)=>{
|
|
|
277
214
|
const updateMigrationJob = async (jobId)=>{
|
|
278
215
|
try {
|
|
279
216
|
setLoading(true);
|
|
280
|
-
if (!requireSiteConfig('updating')) return;
|
|
281
217
|
const jobName = getJobName(jobId);
|
|
282
218
|
const confirmed = confirm(`Are you sure you want to update the "${jobName}" migration? This will update existing items with new field mappings without re-processing all content.`);
|
|
283
|
-
if (!confirmed)
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
219
|
+
if (!confirmed) return;
|
|
286
220
|
addLog(`Updating migration "${jobName}" with new field mappings`);
|
|
287
221
|
const response = await fetch(`/api/wordpress/migration-jobs`, {
|
|
288
222
|
body: JSON.stringify({
|
|
289
223
|
action: 'update',
|
|
290
|
-
jobId
|
|
291
|
-
siteConfig: getSiteCredentials()
|
|
224
|
+
jobId
|
|
292
225
|
}),
|
|
293
226
|
headers: {
|
|
294
227
|
'Content-Type': 'application/json'
|
|
@@ -313,18 +246,14 @@ const useMigrationDashboard = (initialSummary)=>{
|
|
|
313
246
|
const restartMigrationJob = async (jobId)=>{
|
|
314
247
|
try {
|
|
315
248
|
setLoading(true);
|
|
316
|
-
if (!requireSiteConfig('restarting')) return;
|
|
317
249
|
const jobName = getJobName(jobId);
|
|
318
250
|
const confirmed = confirm(`Are you sure you want to restart the "${jobName}" migration? This will re-process ALL items, including those that were already successfully migrated.`);
|
|
319
|
-
if (!confirmed)
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
251
|
+
if (!confirmed) return;
|
|
322
252
|
addLog(`Restarting migration "${jobName}"`);
|
|
323
253
|
const response = await fetch(`/api/wordpress/migration-jobs`, {
|
|
324
254
|
body: JSON.stringify({
|
|
325
255
|
action: 'restart',
|
|
326
|
-
jobId
|
|
327
|
-
siteConfig: getSiteCredentials()
|
|
256
|
+
jobId
|
|
328
257
|
}),
|
|
329
258
|
headers: {
|
|
330
259
|
'Content-Type': 'application/json'
|
|
@@ -349,9 +278,7 @@ const useMigrationDashboard = (initialSummary)=>{
|
|
|
349
278
|
setLoading(true);
|
|
350
279
|
const jobName = getJobName(jobId);
|
|
351
280
|
const confirmed = confirm(`Are you sure you want to rollback the "${jobName}" migration? This will DELETE all items that were migrated by this job.`);
|
|
352
|
-
if (!confirmed)
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
281
|
+
if (!confirmed) return;
|
|
355
282
|
addLog(`Rolling back migration "${jobName}"`);
|
|
356
283
|
const response = await fetch(`/api/wordpress/migration-jobs`, {
|
|
357
284
|
body: JSON.stringify({
|
|
@@ -396,22 +323,14 @@ const useMigrationDashboard = (initialSummary)=>{
|
|
|
396
323
|
return rollbackMigrationJob(jobId);
|
|
397
324
|
}
|
|
398
325
|
};
|
|
399
|
-
// ---
|
|
326
|
+
// --- Content discovery ---
|
|
400
327
|
const scanWordPressContent = async ()=>{
|
|
401
|
-
if (!validateSiteConfig()) {
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
328
|
setSiteConfig((prev)=>({
|
|
405
329
|
...prev,
|
|
406
330
|
connectionStatus: 'scanning'
|
|
407
331
|
}));
|
|
408
332
|
try {
|
|
409
333
|
const response = await fetch('/api/wordpress/discover-content', {
|
|
410
|
-
body: JSON.stringify({
|
|
411
|
-
wpPassword: siteConfig.wpPassword,
|
|
412
|
-
wpSiteUrl: siteConfig.wpSiteUrl,
|
|
413
|
-
wpUsername: siteConfig.wpUsername
|
|
414
|
-
}),
|
|
415
334
|
headers: {
|
|
416
335
|
'Content-Type': 'application/json'
|
|
417
336
|
},
|
|
@@ -422,112 +341,37 @@ const useMigrationDashboard = (initialSummary)=>{
|
|
|
422
341
|
const contentTypes = result.contentTypes || [];
|
|
423
342
|
const totalItems = result.totalItems || 0;
|
|
424
343
|
const updatedConfig = {
|
|
425
|
-
...siteConfig,
|
|
426
344
|
connectionStatus: 'scanned',
|
|
427
345
|
discoveredContent: contentTypes,
|
|
428
346
|
totalItems
|
|
429
347
|
};
|
|
430
348
|
setSiteConfig(updatedConfig);
|
|
431
|
-
|
|
432
|
-
persistConfig(updatedConfig);
|
|
433
|
-
} catch (error) {
|
|
434
|
-
console.error('Failed to save discovered content to localStorage:', error);
|
|
435
|
-
}
|
|
349
|
+
persistScanResult(updatedConfig);
|
|
436
350
|
if (contentTypes.length === 0) {
|
|
437
|
-
alert('WordPress scan completed, but no content was found. This could be due to:\n\n' + '
|
|
351
|
+
alert('WordPress scan completed, but no content was found. This could be due to:\n\n' + '- Empty WordPress site\n' + '- Insufficient user permissions\n' + '- WordPress REST API restrictions\n\n' + 'Please check your WordPress site and user permissions.');
|
|
438
352
|
}
|
|
439
353
|
} else {
|
|
440
354
|
const updatedConfig = {
|
|
441
|
-
...siteConfig,
|
|
442
355
|
connectionStatus: 'failed',
|
|
443
356
|
discoveredContent: [],
|
|
444
357
|
totalItems: 0
|
|
445
358
|
};
|
|
446
359
|
setSiteConfig(updatedConfig);
|
|
447
|
-
|
|
448
|
-
persistConfig(updatedConfig);
|
|
449
|
-
} catch (error) {
|
|
450
|
-
console.error('Failed to save failed scan state to localStorage:', error);
|
|
451
|
-
}
|
|
360
|
+
persistScanResult(updatedConfig);
|
|
452
361
|
alert(`WordPress scan failed: ${result.error || 'Unknown error'}`);
|
|
453
362
|
}
|
|
454
363
|
} catch (error) {
|
|
455
364
|
console.error('Content scan failed:', error);
|
|
456
365
|
const updatedConfig = {
|
|
457
|
-
...siteConfig,
|
|
458
366
|
connectionStatus: 'failed',
|
|
459
367
|
discoveredContent: [],
|
|
460
368
|
totalItems: 0
|
|
461
369
|
};
|
|
462
370
|
setSiteConfig(updatedConfig);
|
|
463
|
-
|
|
464
|
-
persistConfig(updatedConfig);
|
|
465
|
-
} catch (storageError) {
|
|
466
|
-
console.error('Failed to save network error state to localStorage:', storageError);
|
|
467
|
-
}
|
|
371
|
+
persistScanResult(updatedConfig);
|
|
468
372
|
alert('WordPress scan failed: Network error');
|
|
469
373
|
}
|
|
470
374
|
};
|
|
471
|
-
const handleSiteConfigChange = (field, value)=>{
|
|
472
|
-
const connectionFields = [
|
|
473
|
-
'wpSiteUrl',
|
|
474
|
-
'wpUsername',
|
|
475
|
-
'wpPassword'
|
|
476
|
-
];
|
|
477
|
-
const shouldReset = connectionFields.includes(field);
|
|
478
|
-
setSiteConfig((prev)=>({
|
|
479
|
-
...prev,
|
|
480
|
-
[field]: value,
|
|
481
|
-
...shouldReset && {
|
|
482
|
-
connectionStatus: 'not-scanned',
|
|
483
|
-
discoveredContent: [],
|
|
484
|
-
totalItems: 0
|
|
485
|
-
}
|
|
486
|
-
}));
|
|
487
|
-
};
|
|
488
|
-
const handleSaveConfig = async ()=>{
|
|
489
|
-
if (!validateSiteConfig()) {
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
try {
|
|
493
|
-
persistConfig(siteConfig);
|
|
494
|
-
setConfigState({
|
|
495
|
-
isEditing: false,
|
|
496
|
-
isSaved: true
|
|
497
|
-
});
|
|
498
|
-
await scanWordPressContent();
|
|
499
|
-
} catch (error) {
|
|
500
|
-
console.error('Failed to save configuration:', error);
|
|
501
|
-
alert('Failed to save configuration. Please try again.');
|
|
502
|
-
}
|
|
503
|
-
};
|
|
504
|
-
const handleEditConfig = ()=>{
|
|
505
|
-
setConfigState({
|
|
506
|
-
isEditing: true,
|
|
507
|
-
isSaved: true
|
|
508
|
-
});
|
|
509
|
-
};
|
|
510
|
-
const handleCancelEdit = ()=>{
|
|
511
|
-
try {
|
|
512
|
-
const savedConfig = localStorage.getItem('wp-site-config');
|
|
513
|
-
if (savedConfig) {
|
|
514
|
-
const parsed = JSON.parse(savedConfig);
|
|
515
|
-
setSiteConfig((prev)=>({
|
|
516
|
-
...prev,
|
|
517
|
-
...parsed,
|
|
518
|
-
connectionStatus: parsed.connectionStatus || prev.connectionStatus || 'not-scanned',
|
|
519
|
-
discoveredContent: parsed.discoveredContent || prev.discoveredContent || [],
|
|
520
|
-
totalItems: parsed.totalItems || prev.totalItems || 0
|
|
521
|
-
}));
|
|
522
|
-
}
|
|
523
|
-
setConfigState({
|
|
524
|
-
isEditing: false,
|
|
525
|
-
isSaved: true
|
|
526
|
-
});
|
|
527
|
-
} catch (error) {
|
|
528
|
-
console.error('Failed to reload saved configuration:', error);
|
|
529
|
-
}
|
|
530
|
-
};
|
|
531
375
|
// Combine client-side action logs with server-side migration logs
|
|
532
376
|
const getAllLogs = ()=>{
|
|
533
377
|
const combinedLogs = [];
|
|
@@ -562,19 +406,13 @@ const useMigrationDashboard = (initialSummary)=>{
|
|
|
562
406
|
return combinedLogs.sort((a, b)=>b.timestamp.getTime() - a.timestamp.getTime()).slice(0, 100).map((log)=>log.message);
|
|
563
407
|
};
|
|
564
408
|
return {
|
|
565
|
-
configState,
|
|
566
409
|
loading,
|
|
567
|
-
showSiteConfig,
|
|
568
410
|
siteConfig,
|
|
569
411
|
summary,
|
|
570
|
-
// Config actions
|
|
571
|
-
handleCancelEdit,
|
|
572
|
-
handleEditConfig,
|
|
573
|
-
handleSaveConfig,
|
|
574
|
-
handleSiteConfigChange,
|
|
575
|
-
setShowSiteConfig,
|
|
576
412
|
// Job actions
|
|
577
413
|
handleJobAction,
|
|
414
|
+
// Content discovery
|
|
415
|
+
scanWordPressContent,
|
|
578
416
|
// Utilities
|
|
579
417
|
getAllLogs
|
|
580
418
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMigrationDashboard.js","sources":["../../../src/components/dashboard/useMigrationDashboard.ts"],"sourcesContent":["'use client'\n\nimport { useEffect, useState } from 'react'\n\nimport type { JobActionType, MigrationSummary, SiteConfigState, WordPressSiteConfig } from './types'\n\n// Persist config without sensitive credentials (password never stored in localStorage)\nconst persistConfig = (config: WordPressSiteConfig) => {\n const { wpPassword, ...safeConfig } = config\n localStorage.setItem('wp-site-config', JSON.stringify(safeConfig))\n}\n\nexport const useMigrationDashboard = (initialSummary: MigrationSummary) => {\n const [summary, setSummary] = useState<MigrationSummary>(initialSummary)\n const [loading, setLoading] = useState(false)\n const [logs, setLogs] = useState<string[]>([])\n\n const [siteConfig, setSiteConfig] = useState<WordPressSiteConfig>({\n connectionStatus: 'not-scanned',\n discoveredContent: [],\n siteName: '',\n totalItems: 0,\n wpPassword: '',\n wpSiteUrl: '',\n wpUsername: '',\n })\n const [configState, setConfigState] = useState<SiteConfigState>({\n isEditing: true,\n isSaved: false,\n })\n const [showSiteConfig, setShowSiteConfig] = useState(true)\n\n const addLog = (message: string) => {\n const timestamp = new Date().toLocaleTimeString()\n const logEntry = `[${timestamp}] ${message}`\n setLogs((prev) => [...prev, logEntry])\n }\n\n const clearLogs = () => {\n setLogs([])\n }\n\n const refreshDashboard = async () => {\n try {\n console.trace('Call stack:')\n const response = await fetch('/api/wordpress/migration-summary?refresh=true')\n const newSummary = await response.json()\n setSummary(newSummary)\n } catch (error) {\n console.error('Failed to refresh dashboard:', error)\n }\n }\n\n // Load initial data on mount\n useEffect(() => {\n const loadInitialData = async () => {\n await refreshDashboard()\n }\n\n const loadSavedConfig = () => {\n try {\n const savedConfig = localStorage.getItem('wp-site-config')\n if (savedConfig) {\n const parsed = JSON.parse(savedConfig)\n setSiteConfig(parsed)\n setConfigState({ isEditing: false, isSaved: true })\n }\n } catch (error) {\n console.error('Failed to load saved config:', error)\n }\n }\n\n void loadInitialData()\n loadSavedConfig()\n }, [])\n\n // Smart periodic refresh - only when jobs are active\n useEffect(() => {\n let refreshInterval: NodeJS.Timeout | null = null\n\n if (summary.activeJobs > 0) {\n refreshInterval = setInterval(() => {\n void refreshDashboard()\n }, 15000)\n }\n\n return () => {\n if (refreshInterval) {\n clearInterval(refreshInterval)\n }\n }\n }, [summary.activeJobs])\n\n // Auto-close site config when scan is successful AND config is saved\n useEffect(() => {\n if (\n siteConfig.connectionStatus === 'scanned' &&\n configState.isSaved &&\n !configState.isEditing &&\n siteConfig.discoveredContent &&\n siteConfig.discoveredContent.length > 0\n ) {\n const timer = setTimeout(() => {\n setShowSiteConfig(false)\n }, 5000)\n\n return () => clearTimeout(timer)\n }\n }, [\n siteConfig.connectionStatus,\n configState.isSaved,\n configState.isEditing,\n siteConfig.discoveredContent,\n ])\n\n // Only refresh on significant page visibility changes\n useEffect(() => {\n let lastVisibilityChange = Date.now()\n\n const handleVisibilityChange = () => {\n if (!document.hidden && Date.now() - lastVisibilityChange > 30000) {\n void refreshDashboard()\n }\n\n if (document.hidden) {\n lastVisibilityChange = Date.now()\n }\n }\n\n document.addEventListener('visibilitychange', handleVisibilityChange)\n\n return () => {\n document.removeEventListener('visibilitychange', handleVisibilityChange)\n }\n }, [])\n\n // Listen for localStorage events to immediately refresh when jobs are changed\n useEffect(() => {\n const handleStorageChange = (e: StorageEvent) => {\n if (e.key === 'migration-event' && e.newValue) {\n try {\n const event = JSON.parse(e.newValue)\n if (event.type?.startsWith('migration-job-')) {\n void refreshDashboard()\n }\n } catch (error) {\n // Ignore parsing errors\n }\n }\n }\n\n window.addEventListener('storage', handleStorageChange)\n return () => window.removeEventListener('storage', handleStorageChange)\n }, [])\n\n const validateSiteConfig = () => {\n const { siteName, wpPassword, wpSiteUrl, wpUsername } = siteConfig\n if (!siteName.trim() || !wpSiteUrl.trim() || !wpUsername.trim() || !wpPassword.trim()) {\n alert('Please fill in all WordPress site configuration fields')\n return false\n }\n return true\n }\n\n const requireSiteConfig = (actionVerb: string): boolean => {\n if (\n !configState.isSaved ||\n !validateSiteConfig() ||\n siteConfig.connectionStatus !== 'scanned'\n ) {\n alert(\n `Please save a valid WordPress site configuration and scan for content before ${actionVerb} migration jobs.`,\n )\n return false\n }\n return true\n }\n\n const getSiteCredentials = () => ({\n siteName: siteConfig.siteName,\n wpPassword: siteConfig.wpPassword,\n wpSiteUrl: siteConfig.wpSiteUrl,\n wpUsername: siteConfig.wpUsername,\n })\n\n const getJobName = (jobId: string) => {\n const job = summary.recentJobs?.find((j) => j.id === jobId)\n return job?.jobName || `Job ${jobId}`\n }\n\n // --- Job action handlers ---\n\n const startMigrationJob = async (jobId: string) => {\n try {\n setLoading(true)\n if (!requireSiteConfig('starting')) return\n\n await refreshDashboard()\n clearLogs()\n\n const jobName = getJobName(jobId)\n addLog(`Migration \"${jobName}\" started`)\n\n const response = await fetch(`/api/wordpress/migration-jobs`, {\n body: JSON.stringify({\n action: 'start',\n jobId,\n siteConfig: getSiteCredentials(),\n }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n addLog(`Error starting migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to start job')\n }\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error starting migration:', error)\n alert(\n `Failed to start migration: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n } finally {\n setLoading(false)\n }\n }\n\n const pauseMigrationJob = async (jobId: string) => {\n try {\n const jobName = getJobName(jobId)\n addLog(`Migration \"${jobName}\" paused`)\n\n const response = await fetch(`/api/wordpress/migration-jobs?jobId=${jobId}&action=pause`, {\n method: 'PUT',\n })\n\n if (!response.ok) {\n addLog(`Error pausing migration \"${jobName}\"`)\n throw new Error('Failed to pause migration job')\n }\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error pausing migration:', error)\n alert('Failed to pause migration job')\n }\n }\n\n const resumeMigrationJob = async (jobId: string) => {\n try {\n setLoading(true)\n if (!requireSiteConfig('resuming')) return\n\n const jobName = getJobName(jobId)\n addLog(`Migration \"${jobName}\" resumed`)\n\n const response = await fetch(`/api/wordpress/migration-jobs?jobId=${jobId}&action=resume`, {\n method: 'PUT',\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n addLog(`Error resuming migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to resume job')\n }\n\n const startResponse = await fetch(`/api/wordpress/migration-jobs`, {\n body: JSON.stringify({\n action: 'resume',\n jobId,\n siteConfig: getSiteCredentials(),\n }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!startResponse.ok) {\n const errorData = await startResponse.json()\n addLog(`Error resuming migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to resume job')\n }\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error resuming migration:', error)\n alert(\n `Failed to resume migration: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n } finally {\n setLoading(false)\n }\n }\n\n const retryMigrationJob = async (jobId: string) => {\n try {\n setLoading(true)\n if (!requireSiteConfig('retrying')) return\n\n const jobName = getJobName(jobId)\n addLog(`Retrying migration \"${jobName}\"`)\n\n const response = await fetch(`/api/wordpress/migration-jobs`, {\n body: JSON.stringify({\n action: 'retry',\n jobId,\n siteConfig: getSiteCredentials(),\n }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n addLog(`Error retrying migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to retry job')\n }\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error retrying migration:', error)\n alert(\n `Failed to retry migration: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n } finally {\n setLoading(false)\n }\n }\n\n const updateMigrationJob = async (jobId: string) => {\n try {\n setLoading(true)\n if (!requireSiteConfig('updating')) return\n\n const jobName = getJobName(jobId)\n\n const confirmed = confirm(\n `Are you sure you want to update the \"${jobName}\" migration? This will update existing items with new field mappings without re-processing all content.`,\n )\n\n if (!confirmed) {\n return\n }\n\n addLog(`Updating migration \"${jobName}\" with new field mappings`)\n\n const response = await fetch(`/api/wordpress/migration-jobs`, {\n body: JSON.stringify({\n action: 'update',\n jobId,\n siteConfig: getSiteCredentials(),\n }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n addLog(`Error updating migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to update job')\n }\n\n await response.json()\n addLog(`Successfully started update for \"${jobName}\"`)\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error updating migration:', error)\n alert(\n `Failed to update migration job: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n } finally {\n setLoading(false)\n }\n }\n\n const restartMigrationJob = async (jobId: string) => {\n try {\n setLoading(true)\n if (!requireSiteConfig('restarting')) return\n\n const jobName = getJobName(jobId)\n\n const confirmed = confirm(\n `Are you sure you want to restart the \"${jobName}\" migration? This will re-process ALL items, including those that were already successfully migrated.`,\n )\n\n if (!confirmed) {\n return\n }\n\n addLog(`Restarting migration \"${jobName}\"`)\n\n const response = await fetch(`/api/wordpress/migration-jobs`, {\n body: JSON.stringify({\n action: 'restart',\n jobId,\n siteConfig: getSiteCredentials(),\n }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n addLog(`Error restarting migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to restart job')\n }\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error restarting migration:', error)\n alert(\n `Failed to restart migration: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n } finally {\n setLoading(false)\n }\n }\n\n const rollbackMigrationJob = async (jobId: string) => {\n try {\n setLoading(true)\n\n const jobName = getJobName(jobId)\n\n const confirmed = confirm(\n `Are you sure you want to rollback the \"${jobName}\" migration? This will DELETE all items that were migrated by this job.`,\n )\n\n if (!confirmed) {\n return\n }\n\n addLog(`Rolling back migration \"${jobName}\"`)\n\n const response = await fetch(`/api/wordpress/migration-jobs`, {\n body: JSON.stringify({\n action: 'rollback',\n jobId,\n }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n addLog(`Error rolling back migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to rollback job')\n }\n\n const result = await response.json()\n addLog(`Rollback complete for \"${jobName}\": ${result.data?.deleted ?? 0} items deleted`)\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error rolling back migration:', error)\n alert(\n `Failed to rollback migration: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n } finally {\n setLoading(false)\n }\n }\n\n const handleJobAction = async (action: JobActionType, jobId: string) => {\n switch (action) {\n case 'start':\n return startMigrationJob(jobId)\n case 'pause':\n return pauseMigrationJob(jobId)\n case 'resume':\n return resumeMigrationJob(jobId)\n case 'retry':\n return retryMigrationJob(jobId)\n case 'update':\n return updateMigrationJob(jobId)\n case 'restart':\n return restartMigrationJob(jobId)\n case 'rollback':\n return rollbackMigrationJob(jobId)\n }\n }\n\n // --- Config handlers ---\n\n const scanWordPressContent = async () => {\n if (!validateSiteConfig()) {\n return\n }\n\n setSiteConfig((prev) => ({ ...prev, connectionStatus: 'scanning' }))\n\n try {\n const response = await fetch('/api/wordpress/discover-content', {\n body: JSON.stringify({\n wpPassword: siteConfig.wpPassword,\n wpSiteUrl: siteConfig.wpSiteUrl,\n wpUsername: siteConfig.wpUsername,\n }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n const result = await response.json()\n\n if (response.ok && result.success) {\n const contentTypes = result.contentTypes || []\n const totalItems = result.totalItems || 0\n\n const updatedConfig = {\n ...siteConfig,\n connectionStatus: 'scanned' as const,\n discoveredContent: contentTypes,\n totalItems,\n }\n\n setSiteConfig(updatedConfig)\n\n try {\n persistConfig(updatedConfig)\n } catch (error) {\n console.error('Failed to save discovered content to localStorage:', error)\n }\n\n if (contentTypes.length === 0) {\n alert(\n 'WordPress scan completed, but no content was found. This could be due to:\\n\\n' +\n '• Empty WordPress site (no posts, pages, media, etc.)\\n' +\n '• Insufficient user permissions\\n' +\n '• WordPress REST API restrictions\\n\\n' +\n 'Please check your WordPress site and user permissions.',\n )\n }\n } else {\n const updatedConfig = {\n ...siteConfig,\n connectionStatus: 'failed' as const,\n discoveredContent: [],\n totalItems: 0,\n }\n\n setSiteConfig(updatedConfig)\n\n try {\n persistConfig(updatedConfig)\n } catch (error) {\n console.error('Failed to save failed scan state to localStorage:', error)\n }\n\n alert(`WordPress scan failed: ${result.error || 'Unknown error'}`)\n }\n } catch (error) {\n console.error('Content scan failed:', error)\n\n const updatedConfig = {\n ...siteConfig,\n connectionStatus: 'failed' as const,\n discoveredContent: [],\n totalItems: 0,\n }\n\n setSiteConfig(updatedConfig)\n\n try {\n persistConfig(updatedConfig)\n } catch (storageError) {\n console.error('Failed to save network error state to localStorage:', storageError)\n }\n\n alert('WordPress scan failed: Network error')\n }\n }\n\n const handleSiteConfigChange = (field: keyof WordPressSiteConfig, value: string) => {\n const connectionFields = ['wpSiteUrl', 'wpUsername', 'wpPassword']\n const shouldReset = connectionFields.includes(field)\n\n setSiteConfig((prev) => ({\n ...prev,\n [field]: value,\n ...(shouldReset && {\n connectionStatus: 'not-scanned',\n discoveredContent: [],\n totalItems: 0,\n }),\n }))\n }\n\n const handleSaveConfig = async () => {\n if (!validateSiteConfig()) {\n return\n }\n\n try {\n persistConfig(siteConfig)\n setConfigState({\n isEditing: false,\n isSaved: true,\n })\n\n await scanWordPressContent()\n } catch (error) {\n console.error('Failed to save configuration:', error)\n alert('Failed to save configuration. Please try again.')\n }\n }\n\n const handleEditConfig = () => {\n setConfigState({\n isEditing: true,\n isSaved: true,\n })\n }\n\n const handleCancelEdit = () => {\n try {\n const savedConfig = localStorage.getItem('wp-site-config')\n if (savedConfig) {\n const parsed = JSON.parse(savedConfig)\n setSiteConfig((prev) => ({\n ...prev,\n ...parsed,\n connectionStatus: parsed.connectionStatus || prev.connectionStatus || 'not-scanned',\n discoveredContent: parsed.discoveredContent || prev.discoveredContent || [],\n totalItems: parsed.totalItems || prev.totalItems || 0,\n }))\n }\n setConfigState({\n isEditing: false,\n isSaved: true,\n })\n } catch (error) {\n console.error('Failed to reload saved configuration:', error)\n }\n }\n\n // Combine client-side action logs with server-side migration logs\n const getAllLogs = (): string[] => {\n const combinedLogs: Array<{\n level?: string\n message: string\n source: 'client' | 'server'\n timestamp: Date\n }> = []\n\n logs.forEach((log) => {\n const timestampMatch = log.match(/^\\[(\\d{1,2}:\\d{2}:\\d{2}(?:\\s?[AP]M)?)\\]/)\n let timestamp = new Date()\n\n if (timestampMatch) {\n const timeStr = timestampMatch[1]\n const today = new Date()\n const timeWithDate = `${today.toDateString()} ${timeStr}`\n timestamp = new Date(timeWithDate)\n }\n\n combinedLogs.push({\n message: log,\n source: 'client',\n timestamp,\n })\n })\n\n if (summary.recentLogs) {\n summary.recentLogs.forEach((log) => {\n const timestamp = new Date(log.timestamp)\n const levelIcon = log.level === 'error' ? '❌' : log.level === 'warning' ? '⚠️' : 'ℹ️'\n const formattedMessage = `[${timestamp.toLocaleTimeString()}] ${levelIcon} [${log.jobName}] ${log.message}`\n\n combinedLogs.push({\n level: log.level,\n message: formattedMessage,\n source: 'server',\n timestamp,\n })\n })\n }\n\n return combinedLogs\n .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())\n .slice(0, 100)\n .map((log) => log.message)\n }\n\n return {\n configState,\n loading,\n showSiteConfig,\n siteConfig,\n summary,\n\n // Config actions\n handleCancelEdit,\n handleEditConfig,\n handleSaveConfig,\n handleSiteConfigChange,\n setShowSiteConfig,\n\n // Job actions\n handleJobAction,\n\n // Utilities\n getAllLogs,\n }\n}\n"],"names":["localStorage","discoveredContent","setLogs","prev","logEntry","console","loadSavedConfig","refreshInterval","summary","siteConfig","configState","lastVisibilityChange","alert","siteName","wpPassword","wpSiteUrl","wpUsername","clearLogs","addLog","jobId","totalItems","connectionStatus","timestamp","combinedLogs","level","loading","showSiteConfig","handleCancelEdit","handleEditConfig","handleSaveConfig","handleSiteConfigChange","setShowSiteConfig","handleJobAction","getAllLogs"],"mappings":";;;AAMA;AACA;AACE;AACAA;AACF;AAEO;AACL;AACA;AACA;AAEA;;AAEEC;;;;;;AAMF;AACA;;;AAGA;AACA;AAEA;;AAEE;AACAC;AAAsBC;AAAMC;AAAS;AACvC;AAEA;AACEF;AACF;AAEA;;AAEIG;;;;AAIF;;AAEA;AACF;;;AAIE;;AAEA;AAEA;;;AAGI;;;;;;AAGmD;AACnD;AACF;;AAEA;AACF;;AAGAC;AACF;;;AAIE;;AAGEC;;;AAGF;;AAGE;;AAEA;AACF;;AACEC;AAAmB;;;AAIrB;AAOE;;;AAIA;AACF;;AAEAC;AACAC;AACAA;AACAD;AACD;;;;AAMC;;;AAGE;;AAGEE;AACF;AACF;;;;AAMA;AACF;;;AAIE;AACE;;AAEI;AACA;;AAEA;AACF;;AAEA;AACF;AACF;;AAGA;AACF;AAEA;;AAEE;;;AAGA;;AAEF;AAEA;;AAMIC;;AAIF;;AAEF;;AAGEC;AACAC;AACAC;AACAC;;AAGF;;AAEE;AACF;;AAIA;;;;;AAMIC;AAEA;AACAC;AAEA;;;AAGIC;;AAEF;;;AAC8C;;AAEhD;;;;AAKE;AACF;;AAGF;;;;;AAOA;AACF;AAEA;;AAEI;AACAD;;;AAIA;;AAGEA;AACA;AACF;;AAGF;;;AAGA;AACF;AAEA;;;;AAKI;AACAA;;;AAIA;;;;AAKE;AACF;AAEA;;;AAGIC;;AAEF;;;AAC8C;;AAEhD;;;;AAKE;AACF;;AAGF;;;;;AAOA;AACF;AAEA;;;;AAKI;AACAD;AAEA;;;AAGIC;;AAEF;;;AAC8C;;AAEhD;;;;AAKE;AACF;;AAGF;;;;;AAOA;AACF;AAEA;;;;AAKI;AAEA;AAIA;AACE;AACF;AAEAD;AAEA;;;AAGIC;;AAEF;;;AAC8C;;AAEhD;;;;AAKE;AACF;AAEA;AACAD;;AAGF;;;;;AAOA;AACF;AAEA;;;;AAKI;AAEA;AAIA;AACE;AACF;AAEAA;AAEA;;;AAGIC;;AAEF;;;AAC8C;;AAEhD;;;;AAKE;AACF;;AAGF;;;;;AAOA;AACF;AAEA;;;AAII;AAEA;AAIA;AACE;AACF;AAEAD;AAEA;;;AAGIC;AACF;;;AAC8C;;AAEhD;;;;AAKE;AACF;;AAGAD;;AAGF;;;;;AAOA;AACF;;;;AAKM;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;AACJ;AACF;;AAIA;AACE;AACE;AACF;;AAE2B;;;;;;AAKrBJ;AACAC;AACAC;AACF;;;AAC8C;;AAEhD;;AAIA;AACE;;AAGA;AACE;;;AAGAI;AACF;;;;AAMA;;AAEA;;;AAUA;;AAEA;AACE;;AAEAnB;;AAEF;;;;AAMA;;AAEA;AAEAW;AACF;AACF;;AAGE;AACE;;AAEAX;;AAEF;;;;AAMA;;AAEA;;AAGF;AACF;;AAGE;AAA0B;AAAa;AAAc;AAAa;;;AAIhE;AACA;AACA;;AAEEA;;;;AAIN;AAEA;AACE;AACE;AACF;;;;;;AAOE;;AAGF;;;AAGA;AACF;AAEA;;;;AAIE;AACF;AAEA;;;AAGI;;;AAGI;AACA;AACAoB;AACApB;AACAmB;;AAEJ;;;;AAIA;AACF;;AAEA;AACF;;AAGA;AACE;;;AASE;AAEA;;AAEE;AACA;AACAE;AACF;AAEAC;;;AAGED;AACF;AACF;;AAGEd;AACE;;AAEA;AAEAe;AACEC;;;AAGAF;AACF;AACF;AACF;;AAMF;;AAGEZ;AACAe;AACAC;AACAjB;AACAD;;AAGAmB;AACAC;AACAC;AACAC;AACAC;;AAGAC;;AAGAC;AACF;AACF;;"}
|
|
1
|
+
{"version":3,"file":"useMigrationDashboard.js","sources":["../../../src/components/dashboard/useMigrationDashboard.ts"],"sourcesContent":["'use client'\n\nimport { useEffect, useState } from 'react'\n\nimport type { JobActionType, MigrationSummary, WordPressSiteConfig } from './types'\n\nconst persistScanResult = (config: WordPressSiteConfig) => {\n localStorage.setItem('wp-scan-result', JSON.stringify(config))\n}\n\nconst loadScanResult = (): WordPressSiteConfig | null => {\n try {\n const saved = localStorage.getItem('wp-scan-result')\n return saved ? JSON.parse(saved) : null\n } catch {\n return null\n }\n}\n\nexport const useMigrationDashboard = (initialSummary: MigrationSummary) => {\n const [summary, setSummary] = useState<MigrationSummary>(initialSummary)\n const [loading, setLoading] = useState(false)\n const [logs, setLogs] = useState<string[]>([])\n\n const [siteConfig, setSiteConfig] = useState<WordPressSiteConfig>({\n connectionStatus: 'not-scanned',\n discoveredContent: [],\n totalItems: 0,\n })\n\n const addLog = (message: string) => {\n const timestamp = new Date().toLocaleTimeString()\n const logEntry = `[${timestamp}] ${message}`\n setLogs((prev) => [...prev, logEntry])\n }\n\n const refreshDashboard = async () => {\n try {\n const response = await fetch('/api/wordpress/migration-summary?refresh=true')\n const newSummary = await response.json()\n setSummary(newSummary)\n } catch (error) {\n console.error('Failed to refresh dashboard:', error)\n }\n }\n\n // Load initial data on mount\n useEffect(() => {\n void refreshDashboard()\n\n const saved = loadScanResult()\n if (saved) {\n setSiteConfig(saved)\n }\n }, [])\n\n // Smart periodic refresh - only when jobs are active\n useEffect(() => {\n let refreshInterval: NodeJS.Timeout | null = null\n\n if (summary.activeJobs > 0) {\n refreshInterval = setInterval(() => {\n void refreshDashboard()\n }, 15000)\n }\n\n return () => {\n if (refreshInterval) {\n clearInterval(refreshInterval)\n }\n }\n }, [summary.activeJobs])\n\n // Only refresh on significant page visibility changes\n useEffect(() => {\n let lastVisibilityChange = Date.now()\n\n const handleVisibilityChange = () => {\n if (!document.hidden && Date.now() - lastVisibilityChange > 30000) {\n void refreshDashboard()\n }\n\n if (document.hidden) {\n lastVisibilityChange = Date.now()\n }\n }\n\n document.addEventListener('visibilitychange', handleVisibilityChange)\n\n return () => {\n document.removeEventListener('visibilitychange', handleVisibilityChange)\n }\n }, [])\n\n // Listen for localStorage events to immediately refresh when jobs are changed\n useEffect(() => {\n const handleStorageChange = (e: StorageEvent) => {\n if (e.key === 'migration-event' && e.newValue) {\n try {\n const event = JSON.parse(e.newValue)\n if (event.type?.startsWith('migration-job-')) {\n void refreshDashboard()\n }\n } catch {\n // Ignore parsing errors\n }\n }\n }\n\n window.addEventListener('storage', handleStorageChange)\n return () => window.removeEventListener('storage', handleStorageChange)\n }, [])\n\n const getJobName = (jobId: string) => {\n const job = summary.recentJobs?.find((j) => j.id === jobId)\n return job?.jobName || `Job ${jobId}`\n }\n\n // --- Job action handlers ---\n\n const startMigrationJob = async (jobId: string) => {\n try {\n setLoading(true)\n await refreshDashboard()\n setLogs([])\n\n const jobName = getJobName(jobId)\n addLog(`Migration \"${jobName}\" started`)\n\n const response = await fetch(`/api/wordpress/migration-jobs`, {\n body: JSON.stringify({ action: 'start', jobId }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n addLog(`Error starting migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to start job')\n }\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error starting migration:', error)\n alert(\n `Failed to start migration: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n } finally {\n setLoading(false)\n }\n }\n\n const pauseMigrationJob = async (jobId: string) => {\n try {\n const jobName = getJobName(jobId)\n addLog(`Migration \"${jobName}\" paused`)\n\n const response = await fetch(`/api/wordpress/migration-jobs?jobId=${jobId}&action=pause`, {\n method: 'PUT',\n })\n\n if (!response.ok) {\n addLog(`Error pausing migration \"${jobName}\"`)\n throw new Error('Failed to pause migration job')\n }\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error pausing migration:', error)\n alert('Failed to pause migration job')\n }\n }\n\n const resumeMigrationJob = async (jobId: string) => {\n try {\n setLoading(true)\n\n const jobName = getJobName(jobId)\n addLog(`Migration \"${jobName}\" resumed`)\n\n const response = await fetch(`/api/wordpress/migration-jobs?jobId=${jobId}&action=resume`, {\n method: 'PUT',\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n addLog(`Error resuming migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to resume job')\n }\n\n const startResponse = await fetch(`/api/wordpress/migration-jobs`, {\n body: JSON.stringify({ action: 'resume', jobId }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!startResponse.ok) {\n const errorData = await startResponse.json()\n addLog(`Error resuming migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to resume job')\n }\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error resuming migration:', error)\n alert(\n `Failed to resume migration: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n } finally {\n setLoading(false)\n }\n }\n\n const retryMigrationJob = async (jobId: string) => {\n try {\n setLoading(true)\n\n const jobName = getJobName(jobId)\n addLog(`Retrying migration \"${jobName}\"`)\n\n const response = await fetch(`/api/wordpress/migration-jobs`, {\n body: JSON.stringify({ action: 'retry', jobId }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n addLog(`Error retrying migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to retry job')\n }\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error retrying migration:', error)\n alert(\n `Failed to retry migration: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n } finally {\n setLoading(false)\n }\n }\n\n const updateMigrationJob = async (jobId: string) => {\n try {\n setLoading(true)\n\n const jobName = getJobName(jobId)\n\n const confirmed = confirm(\n `Are you sure you want to update the \"${jobName}\" migration? This will update existing items with new field mappings without re-processing all content.`,\n )\n\n if (!confirmed) return\n\n addLog(`Updating migration \"${jobName}\" with new field mappings`)\n\n const response = await fetch(`/api/wordpress/migration-jobs`, {\n body: JSON.stringify({ action: 'update', jobId }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n addLog(`Error updating migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to update job')\n }\n\n await response.json()\n addLog(`Successfully started update for \"${jobName}\"`)\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error updating migration:', error)\n alert(\n `Failed to update migration job: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n } finally {\n setLoading(false)\n }\n }\n\n const restartMigrationJob = async (jobId: string) => {\n try {\n setLoading(true)\n\n const jobName = getJobName(jobId)\n\n const confirmed = confirm(\n `Are you sure you want to restart the \"${jobName}\" migration? This will re-process ALL items, including those that were already successfully migrated.`,\n )\n\n if (!confirmed) return\n\n addLog(`Restarting migration \"${jobName}\"`)\n\n const response = await fetch(`/api/wordpress/migration-jobs`, {\n body: JSON.stringify({ action: 'restart', jobId }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n addLog(`Error restarting migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to restart job')\n }\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error restarting migration:', error)\n alert(\n `Failed to restart migration: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n } finally {\n setLoading(false)\n }\n }\n\n const rollbackMigrationJob = async (jobId: string) => {\n try {\n setLoading(true)\n\n const jobName = getJobName(jobId)\n\n const confirmed = confirm(\n `Are you sure you want to rollback the \"${jobName}\" migration? This will DELETE all items that were migrated by this job.`,\n )\n\n if (!confirmed) return\n\n addLog(`Rolling back migration \"${jobName}\"`)\n\n const response = await fetch(`/api/wordpress/migration-jobs`, {\n body: JSON.stringify({ action: 'rollback', jobId }),\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n if (!response.ok) {\n const errorData = await response.json()\n addLog(`Error rolling back migration \"${jobName}\": ${errorData.error || 'Unknown error'}`)\n throw new Error(errorData.error || 'Failed to rollback job')\n }\n\n const result = await response.json()\n addLog(`Rollback complete for \"${jobName}\": ${result.data?.deleted ?? 0} items deleted`)\n\n await refreshDashboard()\n } catch (error) {\n console.error('Error rolling back migration:', error)\n alert(\n `Failed to rollback migration: ${error instanceof Error ? error.message : 'Unknown error'}`,\n )\n } finally {\n setLoading(false)\n }\n }\n\n const handleJobAction = async (action: JobActionType, jobId: string) => {\n switch (action) {\n case 'start':\n return startMigrationJob(jobId)\n case 'pause':\n return pauseMigrationJob(jobId)\n case 'resume':\n return resumeMigrationJob(jobId)\n case 'retry':\n return retryMigrationJob(jobId)\n case 'update':\n return updateMigrationJob(jobId)\n case 'restart':\n return restartMigrationJob(jobId)\n case 'rollback':\n return rollbackMigrationJob(jobId)\n }\n }\n\n // --- Content discovery ---\n\n const scanWordPressContent = async () => {\n setSiteConfig((prev) => ({ ...prev, connectionStatus: 'scanning' }))\n\n try {\n const response = await fetch('/api/wordpress/discover-content', {\n headers: { 'Content-Type': 'application/json' },\n method: 'POST',\n })\n\n const result = await response.json()\n\n if (response.ok && result.success) {\n const contentTypes = result.contentTypes || []\n const totalItems = result.totalItems || 0\n\n const updatedConfig: WordPressSiteConfig = {\n connectionStatus: 'scanned',\n discoveredContent: contentTypes,\n totalItems,\n }\n\n setSiteConfig(updatedConfig)\n persistScanResult(updatedConfig)\n\n if (contentTypes.length === 0) {\n alert(\n 'WordPress scan completed, but no content was found. This could be due to:\\n\\n' +\n '- Empty WordPress site\\n' +\n '- Insufficient user permissions\\n' +\n '- WordPress REST API restrictions\\n\\n' +\n 'Please check your WordPress site and user permissions.',\n )\n }\n } else {\n const updatedConfig: WordPressSiteConfig = {\n connectionStatus: 'failed',\n discoveredContent: [],\n totalItems: 0,\n }\n\n setSiteConfig(updatedConfig)\n persistScanResult(updatedConfig)\n\n alert(`WordPress scan failed: ${result.error || 'Unknown error'}`)\n }\n } catch (error) {\n console.error('Content scan failed:', error)\n\n const updatedConfig: WordPressSiteConfig = {\n connectionStatus: 'failed',\n discoveredContent: [],\n totalItems: 0,\n }\n\n setSiteConfig(updatedConfig)\n persistScanResult(updatedConfig)\n\n alert('WordPress scan failed: Network error')\n }\n }\n\n // Combine client-side action logs with server-side migration logs\n const getAllLogs = (): string[] => {\n const combinedLogs: Array<{\n level?: string\n message: string\n source: 'client' | 'server'\n timestamp: Date\n }> = []\n\n logs.forEach((log) => {\n const timestampMatch = log.match(/^\\[(\\d{1,2}:\\d{2}:\\d{2}(?:\\s?[AP]M)?)\\]/)\n let timestamp = new Date()\n\n if (timestampMatch) {\n const timeStr = timestampMatch[1]\n const today = new Date()\n const timeWithDate = `${today.toDateString()} ${timeStr}`\n timestamp = new Date(timeWithDate)\n }\n\n combinedLogs.push({\n message: log,\n source: 'client',\n timestamp,\n })\n })\n\n if (summary.recentLogs) {\n summary.recentLogs.forEach((log) => {\n const timestamp = new Date(log.timestamp)\n const levelIcon = log.level === 'error' ? '❌' : log.level === 'warning' ? '⚠️' : 'ℹ️'\n const formattedMessage = `[${timestamp.toLocaleTimeString()}] ${levelIcon} [${log.jobName}] ${log.message}`\n\n combinedLogs.push({\n level: log.level,\n message: formattedMessage,\n source: 'server',\n timestamp,\n })\n })\n }\n\n return combinedLogs\n .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())\n .slice(0, 100)\n .map((log) => log.message)\n }\n\n return {\n loading,\n siteConfig,\n summary,\n\n // Job actions\n handleJobAction,\n\n // Content discovery\n scanWordPressContent,\n\n // Utilities\n getAllLogs,\n }\n}\n"],"names":["localStorage","discoveredContent","setLogs","prev","logEntry","refreshInterval","summary","lastVisibilityChange","addLog","jobId","totalItems","alert","timestamp","combinedLogs","level","loading","siteConfig","handleJobAction","scanWordPressContent","getAllLogs"],"mappings":";;;AAMA;AACEA;AACF;AAEA;;;AAGI;AACF;;AAEA;AACF;AAEO;AACL;AACA;AACA;AAEA;;AAEEC;;AAEF;AAEA;;AAEE;AACAC;AAAsBC;AAAMC;AAAS;AACvC;AAEA;;;;;AAKE;;AAEA;AACF;;;;AAME;AACA;;AAEA;AACF;;;AAIE;;AAGEC;;;AAGF;;AAGE;;AAEA;AACF;;AACEC;AAAmB;;;;AAMrB;;;AAGE;;AAGEC;AACF;AACF;;;;AAMA;AACF;;;AAIE;AACE;;AAEI;AACA;;AAEA;AACF;;AAEA;AACF;AACF;;AAGA;AACF;AAEA;;AAEE;AACF;;AAIA;;;;AAIIL;AAEA;AACAM;AAEA;;;AAC0CC;AAAM;;;AACA;;AAEhD;;;;AAKE;AACF;;AAGF;;;;;AAOA;AACF;AAEA;;AAEI;AACAD;;;AAIA;;AAGEA;AACA;AACF;;AAGF;;;AAGA;AACF;AAEA;;;AAII;AACAA;;;AAIA;;;;AAKE;AACF;AAEA;;;AAC2CC;AAAM;;;AACD;;AAEhD;;;;AAKE;AACF;;AAGF;;;;;AAOA;AACF;AAEA;;;AAII;AACAD;AAEA;;;AAC0CC;AAAM;;;AACA;;AAEhD;;;;AAKE;AACF;;AAGF;;;;;AAOA;AACF;AAEA;;;AAII;AAEA;AAIA;AAEAD;AAEA;;;AAC2CC;AAAM;;;AACD;;AAEhD;;;;AAKE;AACF;AAEA;AACAD;;AAGF;;;;;AAOA;AACF;AAEA;;;AAII;AAEA;AAIA;AAEAA;AAEA;;;AAC4CC;AAAM;;;AACF;;AAEhD;;;;AAKE;AACF;;AAGF;;;;;AAOA;AACF;AAEA;;;AAII;AAEA;AAIA;AAEAD;AAEA;;;AAC6CC;AAAM;;;AACH;;AAEhD;;;;AAKE;AACF;;AAGAD;;AAGF;;;;;AAOA;AACF;;;;AAKM;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;AACJ;AACF;;AAIA;;AAC6B;;;;;;;AAIuB;;AAEhD;;AAIA;AACE;;AAGA;;;AAGEE;AACF;;;;;AAaA;;AAEA;;AAEET;;AAEF;;;AAKAU;AACF;AACF;;AAGE;;AAEEV;;AAEF;;;;AAMF;AACF;;AAGA;AACE;;;AASE;AAEA;;AAEE;AACA;AACAW;AACF;AAEAC;;;AAGED;AACF;AACF;;AAGEN;AACE;;AAEA;AAEAO;AACEC;;;AAGAF;AACF;AACF;AACF;;AAMF;;AAGEG;AACAC;AACAV;;AAGAW;;AAGAC;;AAGAC;AACF;AACF;;"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { analyzePayloadFields } from '../fields/analyzer.js';
|
|
2
2
|
import { MIGRATION_COLLECTION } from '../types.js';
|
|
3
3
|
import { WordPressClient } from '../wordpress/client.js';
|
|
4
|
-
import { requireMigrationAccess,
|
|
5
|
-
import {
|
|
4
|
+
import { requireMigrationAccess, requireAuth, parseRequestBody } from '../helpers/auth.js';
|
|
5
|
+
import { createErrorResponse, createSuccessResponse } from '../helpers/responses.js';
|
|
6
6
|
import { getContentCache, isCacheValid, setContentCache } from '../helpers/cache.js';
|
|
7
7
|
import { checkRateLimit } from '../helpers/rateLimiter.js';
|
|
8
8
|
|
|
@@ -15,9 +15,15 @@ const wordpressConnectionHandler = async (req, pluginOptions)=>{
|
|
|
15
15
|
return createErrorResponse('Too many connection attempts. Try again in 1 minute.', 429);
|
|
16
16
|
}
|
|
17
17
|
try {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
const { wpSiteUrl, wpUsername, wpPassword } = pluginOptions;
|
|
19
|
+
if (!wpSiteUrl || !wpUsername || !wpPassword) {
|
|
20
|
+
return createErrorResponse('WordPress credentials not configured. Set wpSiteUrl, wpUsername, and wpPassword in the plugin config.', 400);
|
|
21
|
+
}
|
|
22
|
+
const client = new WordPressClient({
|
|
23
|
+
wpSiteUrl,
|
|
24
|
+
wpUsername,
|
|
25
|
+
wpPassword
|
|
26
|
+
});
|
|
21
27
|
const result = await client.testConnection();
|
|
22
28
|
return createSuccessResponse(result, result.success ? 200 : 400);
|
|
23
29
|
} catch (error) {
|
|
@@ -29,9 +35,15 @@ const discoverWordPressContent = async (req, pluginOptions)=>{
|
|
|
29
35
|
const authError = await requireMigrationAccess(req, pluginOptions.access);
|
|
30
36
|
if (authError) return authError;
|
|
31
37
|
try {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
const { wpSiteUrl, wpUsername, wpPassword } = pluginOptions;
|
|
39
|
+
if (!wpSiteUrl || !wpUsername || !wpPassword) {
|
|
40
|
+
return createErrorResponse('WordPress credentials not configured. Set wpSiteUrl, wpUsername, and wpPassword in the plugin config.', 400);
|
|
41
|
+
}
|
|
42
|
+
const client = new WordPressClient({
|
|
43
|
+
wpSiteUrl,
|
|
44
|
+
wpUsername,
|
|
45
|
+
wpPassword
|
|
46
|
+
});
|
|
35
47
|
const result = await client.discoverContent();
|
|
36
48
|
if (result.success) {
|
|
37
49
|
return createSuccessResponse({
|
|
@@ -52,16 +64,15 @@ const fetchWordPressContentFields = async (req, pluginOptions)=>{
|
|
|
52
64
|
try {
|
|
53
65
|
const body = await parseRequestBody(req);
|
|
54
66
|
const { contentType } = body;
|
|
55
|
-
// Credentials from request body take priority, then fall back to plugin config (env vars)
|
|
56
|
-
const wpSiteUrl = body.wpSiteUrl || pluginOptions.wpSiteUrl;
|
|
57
|
-
const wpUsername = body.wpUsername || pluginOptions.wpUsername;
|
|
58
|
-
const wpPassword = body.wpPassword || pluginOptions.wpPassword;
|
|
59
|
-
if (!wpSiteUrl || !wpUsername || !wpPassword) {
|
|
60
|
-
throw new Error('WordPress credentials are required. Configure them in the plugin config or the dashboard Site Configuration panel.');
|
|
61
|
-
}
|
|
62
67
|
if (!contentType) {
|
|
63
68
|
throw new Error('contentType is required');
|
|
64
69
|
}
|
|
70
|
+
const wpSiteUrl = pluginOptions.wpSiteUrl;
|
|
71
|
+
const wpUsername = pluginOptions.wpUsername;
|
|
72
|
+
const wpPassword = pluginOptions.wpPassword;
|
|
73
|
+
if (!wpSiteUrl || !wpUsername || !wpPassword) {
|
|
74
|
+
throw new Error('WordPress credentials not configured. Set wpSiteUrl, wpUsername, and wpPassword in the plugin config.');
|
|
75
|
+
}
|
|
65
76
|
const credentials = {
|
|
66
77
|
wpPassword,
|
|
67
78
|
wpSiteUrl,
|