payload-wordpress-migrator
Version:
A PayloadCMS plugin for WordPress migration - migrate and manage WordPress content directly in your Payload admin dashboard
944 lines (943 loc) • 54.1 kB
JavaScript
'use client';
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import React, { useEffect, useState } from 'react';
import styles from './MigrationDashboardClient.module.css';
export const MigrationDashboardClient = ({ summary: initialSummary })=>{
const [summary, setSummary] = useState(initialSummary);
const [loading, setLoading] = useState(false);
const [logs, setLogs] = useState([]);
// WordPress site configuration state
const [siteConfig, setSiteConfig] = useState({
connectionStatus: 'not-scanned',
discoveredContent: [],
siteName: '',
totalItems: 0,
wpPassword: '',
wpSiteUrl: '',
wpUsername: ''
});
const [configState, setConfigState] = useState({
isEditing: true,
isSaved: false
});
const [showSiteConfig, setShowSiteConfig] = useState(true) // Open by default for better UX
;
// Add log entry with timestamp
const addLog = (message)=>{
const timestamp = new Date().toLocaleTimeString();
const logEntry = `[${timestamp}] ${message}`;
setLogs((prev)=>[
...prev,
logEntry
]);
};
// Clear logs (called when starting new migration)
const clearLogs = ()=>{
setLogs([]);
};
// Reusable function to refresh dashboard data
const refreshDashboard = async ()=>{
try {
console.trace('Call stack:');
const response = await fetch('/api/wordpress/migration-summary?refresh=true');
const newSummary = await response.json();
setSummary(newSummary);
} catch (error) {
console.error('Failed to refresh dashboard:', error);
}
};
// Load initial data on mount
useEffect(()=>{
const loadInitialData = async ()=>{
await refreshDashboard();
};
// Load saved WordPress configuration from localStorage
const loadSavedConfig = ()=>{
try {
const savedConfig = localStorage.getItem('wp-site-config');
if (savedConfig) {
const parsed = JSON.parse(savedConfig);
setSiteConfig(parsed);
setConfigState({
isEditing: false,
isSaved: true
});
}
} catch (error) {
console.error('Failed to load saved config:', error);
}
};
void loadInitialData();
loadSavedConfig();
}, []);
// Smart periodic refresh - only when jobs are active
useEffect(()=>{
let refreshInterval = null;
if (summary.activeJobs > 0) {
// Only refresh when there are active jobs
refreshInterval = setInterval(()=>{
void refreshDashboard();
}, 15000); // Refresh every 15 seconds when jobs are active
}
return ()=>{
if (refreshInterval) {
clearInterval(refreshInterval);
}
};
}, [
summary.activeJobs
]); // Re-run when activeJobs count changes
// Auto-close site config when scan is successful AND config is saved (with delay to show results)
useEffect(()=>{
if (siteConfig.connectionStatus === 'scanned' && configState.isSaved && !configState.isEditing && siteConfig.discoveredContent && siteConfig.discoveredContent.length > 0) {
// Wait 5 seconds to show the discovered content before auto-closing
const timer = setTimeout(()=>{
setShowSiteConfig(false);
}, 5000);
return ()=>clearTimeout(timer);
}
}, [
siteConfig.connectionStatus,
configState.isSaved,
configState.isEditing,
siteConfig.discoveredContent
]);
// Only refresh on significant page visibility changes (not micro-focus events)
useEffect(()=>{
let lastVisibilityChange = Date.now();
const handleVisibilityChange = ()=>{
// Only refresh if page was hidden for more than 30 seconds (significant absence)
if (!document.hidden && Date.now() - lastVisibilityChange > 30000) {
void refreshDashboard();
}
if (document.hidden) {
lastVisibilityChange = Date.now();
}
};
document.addEventListener('visibilitychange', handleVisibilityChange);
return ()=>{
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, []);
// Listen for localStorage events to immediately refresh when jobs are changed
useEffect(()=>{
const handleStorageChange = (e)=>{
if (e.key === 'migration-event' && e.newValue) {
try {
const event = JSON.parse(e.newValue);
if (event.type?.startsWith('migration-job-')) {
void refreshDashboard();
}
} catch (error) {
// Ignore parsing errors
}
}
};
window.addEventListener('storage', handleStorageChange);
return ()=>window.removeEventListener('storage', handleStorageChange);
}, []);
const startMigrationJob = async (jobId)=>{
try {
setLoading(true);
// Validate that site configuration is saved and content is scanned
if (!configState.isSaved || !validateSiteConfig() || siteConfig.connectionStatus !== 'scanned') {
alert('Please save a valid WordPress site configuration and scan for content before starting migration jobs.');
return;
}
// Force refresh to clear any old server-side logs before starting
await refreshDashboard();
// Clear logs when starting new migration
clearLogs();
// Find job name for logging
const job = summary.recentJobs?.find((j)=>j.id === jobId);
const jobName = job?.jobName || `Job ${jobId}`;
addLog(`Migration "${jobName}" started`);
const response = await fetch(`/api/wordpress/migration-jobs`, {
body: JSON.stringify({
action: 'start',
jobId,
siteConfig: {
siteName: siteConfig.siteName,
wpPassword: siteConfig.wpPassword,
wpSiteUrl: siteConfig.wpSiteUrl,
wpUsername: siteConfig.wpUsername
}
}),
headers: {
'Content-Type': 'application/json'
},
method: 'POST'
});
if (!response.ok) {
const errorData = await response.json();
addLog(`Error starting migration "${jobName}": ${errorData.error || 'Unknown error'}`);
throw new Error(errorData.error || 'Failed to start job');
}
// Refresh dashboard immediately after starting job
await refreshDashboard();
} catch (error) {
console.error('Error starting migration:', error);
alert(`Failed to start migration: ${error instanceof Error ? error.message : 'Unknown error'}`);
} finally{
setLoading(false);
}
};
const pauseMigrationJob = async (jobId)=>{
try {
// Find job name for logging
const job = summary.recentJobs?.find((j)=>j.id === jobId);
const jobName = job?.jobName || `Job ${jobId}`;
addLog(`Migration "${jobName}" paused`);
const response = await fetch(`/api/wordpress/migration-jobs?jobId=${jobId}&action=pause`, {
method: 'PUT'
});
if (!response.ok) {
addLog(`Error pausing migration "${jobName}"`);
throw new Error('Failed to pause migration job');
}
// Refresh dashboard immediately after pausing job
await refreshDashboard();
} catch (error) {
console.error('Error pausing migration:', error);
alert('Failed to pause migration job');
}
};
const resumeMigrationJob = async (jobId)=>{
try {
setLoading(true);
// Validate that site configuration is saved and content is scanned
if (!configState.isSaved || !validateSiteConfig() || siteConfig.connectionStatus !== 'scanned') {
alert('Please save a valid WordPress site configuration and scan for content before resuming migration jobs.');
return;
}
// Find job name for logging
const job = summary.recentJobs?.find((j)=>j.id === jobId);
const jobName = job?.jobName || `Job ${jobId}`;
addLog(`Migration "${jobName}" resumed`);
const response = await fetch(`/api/wordpress/migration-jobs?jobId=${jobId}&action=resume`, {
method: 'PUT'
});
if (!response.ok) {
const errorData = await response.json();
addLog(`Error resuming migration "${jobName}": ${errorData.error || 'Unknown error'}`);
throw new Error(errorData.error || 'Failed to resume job');
}
// Start the migration process again (it will continue from where it left off)
const startResponse = await fetch(`/api/wordpress/migration-jobs`, {
body: JSON.stringify({
action: 'resume',
jobId,
siteConfig: {
siteName: siteConfig.siteName,
wpPassword: siteConfig.wpPassword,
wpSiteUrl: siteConfig.wpSiteUrl,
wpUsername: siteConfig.wpUsername
}
}),
headers: {
'Content-Type': 'application/json'
},
method: 'POST'
});
if (!startResponse.ok) {
const errorData = await startResponse.json();
addLog(`Error resuming migration "${jobName}": ${errorData.error || 'Unknown error'}`);
throw new Error(errorData.error || 'Failed to resume job');
}
// Refresh dashboard immediately after resuming job
await refreshDashboard();
} catch (error) {
console.error('Error resuming migration:', error);
alert(`Failed to resume migration: ${error instanceof Error ? error.message : 'Unknown error'}`);
} finally{
setLoading(false);
}
};
const retryMigrationJob = async (jobId)=>{
try {
setLoading(true);
// Validate that site configuration is saved and content is scanned
if (!configState.isSaved || !validateSiteConfig() || siteConfig.connectionStatus !== 'scanned') {
alert('Please save a valid WordPress site configuration and scan for content before retrying migration jobs.');
return;
}
// Find job name for logging
const job = summary.recentJobs?.find((j)=>j.id === jobId);
const jobName = job?.jobName || `Job ${jobId}`;
addLog(`Retrying migration "${jobName}"`);
const response = await fetch(`/api/wordpress/migration-jobs`, {
body: JSON.stringify({
action: 'retry',
jobId,
siteConfig: {
siteName: siteConfig.siteName,
wpPassword: siteConfig.wpPassword,
wpSiteUrl: siteConfig.wpSiteUrl,
wpUsername: siteConfig.wpUsername
}
}),
headers: {
'Content-Type': 'application/json'
},
method: 'POST'
});
if (!response.ok) {
const errorData = await response.json();
addLog(`Error retrying migration "${jobName}": ${errorData.error || 'Unknown error'}`);
throw new Error(errorData.error || 'Failed to retry job');
}
// Refresh dashboard immediately after retrying job
await refreshDashboard();
} catch (error) {
console.error('Error retrying migration:', error);
alert(`Failed to retry migration: ${error instanceof Error ? error.message : 'Unknown error'}`);
} finally{
setLoading(false);
}
};
const updateMigrationJob = async (jobId)=>{
try {
setLoading(true);
// Validate that site configuration is saved and content is scanned
if (!configState.isSaved || !validateSiteConfig() || siteConfig.connectionStatus !== 'scanned') {
alert('Please save a valid WordPress site configuration and scan for content before updating migration jobs.');
return;
}
// Find job name for logging
const job = summary.recentJobs?.find((j)=>j.id === jobId);
const jobName = job?.jobName || `Job ${jobId}`;
// Confirm update action
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.`);
if (!confirmed) {
return;
}
addLog(`Updating migration "${jobName}" with new field mappings`);
const response = await fetch(`/api/wordpress/migration-jobs`, {
body: JSON.stringify({
action: 'update',
jobId,
siteConfig: {
siteName: siteConfig.siteName,
wpPassword: siteConfig.wpPassword,
wpSiteUrl: siteConfig.wpSiteUrl,
wpUsername: siteConfig.wpUsername
}
}),
headers: {
'Content-Type': 'application/json'
},
method: 'POST'
});
if (!response.ok) {
const errorData = await response.json();
addLog(`Error updating migration "${jobName}": ${errorData.error || 'Unknown error'}`);
throw new Error(errorData.error || 'Failed to update job');
}
const result = await response.json();
addLog(`Successfully started update for "${jobName}"`);
// Refresh summary to show updated status
await refreshDashboard();
} catch (error) {
console.error('Error updating migration:', error);
alert(`Failed to update migration job: ${error instanceof Error ? error.message : 'Unknown error'}`);
} finally{
setLoading(false);
}
};
const restartMigrationJob = async (jobId)=>{
try {
setLoading(true);
// Validate that site configuration is saved and content is scanned
if (!configState.isSaved || !validateSiteConfig() || siteConfig.connectionStatus !== 'scanned') {
alert('Please save a valid WordPress site configuration and scan for content before restarting migration jobs.');
return;
}
// Find job name for logging
const job = summary.recentJobs?.find((j)=>j.id === jobId);
const jobName = job?.jobName || `Job ${jobId}`;
// Confirm restart action
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.`);
if (!confirmed) {
return;
}
addLog(`Restarting migration "${jobName}"`);
const response = await fetch(`/api/wordpress/migration-jobs`, {
body: JSON.stringify({
action: 'restart',
jobId,
siteConfig: {
siteName: siteConfig.siteName,
wpPassword: siteConfig.wpPassword,
wpSiteUrl: siteConfig.wpSiteUrl,
wpUsername: siteConfig.wpUsername
}
}),
headers: {
'Content-Type': 'application/json'
},
method: 'POST'
});
if (!response.ok) {
const errorData = await response.json();
addLog(`Error restarting migration "${jobName}": ${errorData.error || 'Unknown error'}`);
throw new Error(errorData.error || 'Failed to restart job');
}
// Refresh dashboard immediately after restarting job
await refreshDashboard();
} catch (error) {
console.error('Error restarting migration:', error);
alert(`Failed to restart migration: ${error instanceof Error ? error.message : 'Unknown error'}`);
} finally{
setLoading(false);
}
};
const scanWordPressContent = async ()=>{
if (!validateSiteConfig()) {
return;
}
setSiteConfig((prev)=>({
...prev,
connectionStatus: 'scanning'
}));
try {
const response = await fetch('/api/wordpress/discover-content', {
body: JSON.stringify({
wpPassword: siteConfig.wpPassword,
wpSiteUrl: siteConfig.wpSiteUrl,
wpUsername: siteConfig.wpUsername
}),
headers: {
'Content-Type': 'application/json'
},
method: 'POST'
});
const result = await response.json();
if (response.ok && result.success) {
const contentTypes = result.contentTypes || [];
const totalItems = result.totalItems || 0;
const updatedConfig = {
...siteConfig,
connectionStatus: 'scanned',
discoveredContent: contentTypes,
totalItems
};
setSiteConfig(updatedConfig);
// Save the updated config with discovered content to localStorage
try {
localStorage.setItem('wp-site-config', JSON.stringify(updatedConfig));
} catch (error) {
console.error('Failed to save discovered content to localStorage:', error);
}
// Show a message if no content was found
if (contentTypes.length === 0) {
alert('WordPress scan completed, but no content was found. This could be due to:\n\n' + '• Empty WordPress site (no posts, pages, media, etc.)\n' + '• Insufficient user permissions\n' + '• WordPress REST API restrictions\n\n' + 'Please check your WordPress site and user permissions.');
}
} else {
const updatedConfig = {
...siteConfig,
connectionStatus: 'failed',
discoveredContent: [],
totalItems: 0
};
setSiteConfig(updatedConfig);
// Save the updated config to localStorage
try {
localStorage.setItem('wp-site-config', JSON.stringify(updatedConfig));
} catch (error) {
console.error('Failed to save failed scan state to localStorage:', error);
}
alert(`WordPress scan failed: ${result.error || 'Unknown error'}`);
}
} catch (error) {
console.error('Content scan failed:', error);
const updatedConfig = {
...siteConfig,
connectionStatus: 'failed',
discoveredContent: [],
totalItems: 0
};
setSiteConfig(updatedConfig);
// Save the updated config to localStorage
try {
localStorage.setItem('wp-site-config', JSON.stringify(updatedConfig));
} catch (storageError) {
console.error('Failed to save network error state to localStorage:', storageError);
}
alert('WordPress scan failed: Network error');
}
};
const handleSiteConfigChange = (field, value)=>{
// Only reset connection status and content for fields that affect the WordPress connection
const connectionFields = [
'wpSiteUrl',
'wpUsername',
'wpPassword'
];
const shouldReset = connectionFields.includes(field);
setSiteConfig((prev)=>({
...prev,
[field]: value,
...shouldReset && {
connectionStatus: 'not-scanned',
discoveredContent: [],
totalItems: 0
}
}));
};
const validateSiteConfig = ()=>{
const { siteName, wpPassword, wpSiteUrl, wpUsername } = siteConfig;
if (!siteName.trim() || !wpSiteUrl.trim() || !wpUsername.trim() || !wpPassword.trim()) {
alert('Please fill in all WordPress site configuration fields');
return false;
}
return true;
};
const handleSaveConfig = async ()=>{
if (!validateSiteConfig()) {
return;
}
try {
localStorage.setItem('wp-site-config', JSON.stringify(siteConfig));
setConfigState({
isEditing: false,
isSaved: true
});
// Automatically scan for content after saving
await scanWordPressContent();
} catch (error) {
console.error('Failed to save configuration:', error);
alert('Failed to save configuration. Please try again.');
}
};
const handleEditConfig = ()=>{
setConfigState({
isEditing: true,
isSaved: true
});
};
const handleCancelEdit = ()=>{
// Reload saved config from localStorage
try {
const savedConfig = localStorage.getItem('wp-site-config');
if (savedConfig) {
const parsed = JSON.parse(savedConfig);
setSiteConfig((prev)=>({
...prev,
...parsed,
// Preserve discovered content if it exists
connectionStatus: parsed.connectionStatus || prev.connectionStatus || 'not-scanned',
discoveredContent: parsed.discoveredContent || prev.discoveredContent || [],
totalItems: parsed.totalItems || prev.totalItems || 0
}));
}
setConfigState({
isEditing: false,
isSaved: true
});
} catch (error) {
console.error('Failed to reload saved configuration:', error);
}
};
// Combine client-side action logs with server-side migration logs
const getAllLogs = ()=>{
const combinedLogs = [];
// Add client-side action logs (with current timestamp)
logs.forEach((log)=>{
// Extract timestamp from log if it exists, otherwise use current time
const timestampMatch = log.match(/^\[(\d{1,2}:\d{2}:\d{2}(?:\s?[AP]M)?)\]/);
let timestamp = new Date();
if (timestampMatch) {
// Use today's date with the extracted time
const timeStr = timestampMatch[1];
const today = new Date();
const timeWithDate = `${today.toDateString()} ${timeStr}`;
timestamp = new Date(timeWithDate);
}
combinedLogs.push({
message: log,
source: 'client',
timestamp
});
});
// Add server-side migration logs
if (summary.recentLogs) {
summary.recentLogs.forEach((log)=>{
const timestamp = new Date(log.timestamp);
const levelIcon = log.level === 'error' ? '❌' : log.level === 'warning' ? '⚠️' : 'ℹ️';
const formattedMessage = `[${timestamp.toLocaleTimeString()}] ${levelIcon} [${log.jobName}] ${log.message}`;
combinedLogs.push({
level: log.level,
message: formattedMessage,
source: 'server',
timestamp
});
});
}
// Sort by timestamp (NEWEST FIRST - latest at top)
return combinedLogs.sort((a, b)=>b.timestamp.getTime() - a.timestamp.getTime()).slice(0, 100) // Limit to 100 most recent logs
.map((log)=>log.message);
};
return /*#__PURE__*/ _jsxs("div", {
className: "gutter--left gutter--right collection-list__wrap",
children: [
/*#__PURE__*/ _jsx("div", {
className: styles.migrationDashboardHeader,
children: /*#__PURE__*/ _jsx("h1", {
children: "WordPress Migration Dashboard"
})
}),
/*#__PURE__*/ _jsxs("div", {
className: styles.siteConfigSection,
children: [
/*#__PURE__*/ _jsxs("div", {
className: styles.siteConfigHeader,
children: [
/*#__PURE__*/ _jsx("h2", {
children: "WordPress Site Configuration"
}),
/*#__PURE__*/ _jsx("button", {
className: styles.toggleButton,
onClick: ()=>setShowSiteConfig(!showSiteConfig),
children: showSiteConfig ? 'Hide Configuration' : 'Show Configuration'
})
]
}),
showSiteConfig && /*#__PURE__*/ _jsxs("div", {
className: styles.siteConfigForm,
children: [
/*#__PURE__*/ _jsxs("div", {
className: styles.formGrid,
children: [
/*#__PURE__*/ _jsxs("div", {
className: styles.formGroup,
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "siteName",
children: "Site Name"
}),
/*#__PURE__*/ _jsx("input", {
disabled: !configState.isEditing,
id: "siteName",
onChange: (e)=>handleSiteConfigChange('siteName', e.target.value),
placeholder: "My WordPress Site",
type: "text",
value: siteConfig.siteName
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: styles.formGroup,
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "wpSiteUrl",
children: "WordPress Site URL"
}),
/*#__PURE__*/ _jsx("input", {
disabled: !configState.isEditing,
id: "wpSiteUrl",
onChange: (e)=>handleSiteConfigChange('wpSiteUrl', e.target.value),
placeholder: "https://example.com",
type: "url",
value: siteConfig.wpSiteUrl
}),
/*#__PURE__*/ _jsx("small", {
children: "Base URL of your WordPress site"
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: styles.formGroup,
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "wpUsername",
children: "WordPress Username"
}),
/*#__PURE__*/ _jsx("input", {
disabled: !configState.isEditing,
id: "wpUsername",
onChange: (e)=>handleSiteConfigChange('wpUsername', e.target.value),
placeholder: "admin",
type: "text",
value: siteConfig.wpUsername
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: styles.formGroup,
children: [
/*#__PURE__*/ _jsx("label", {
htmlFor: "wpPassword",
children: "Application Password"
}),
/*#__PURE__*/ _jsx("input", {
disabled: !configState.isEditing,
id: "wpPassword",
onChange: (e)=>handleSiteConfigChange('wpPassword', e.target.value),
placeholder: "xxxx xxxx xxxx xxxx",
type: "password",
value: siteConfig.wpPassword
}),
/*#__PURE__*/ _jsx("small", {
children: "WordPress Application Password for REST API access"
})
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: styles.configActions,
children: [
/*#__PURE__*/ _jsx("div", {
className: styles.connectionStatus,
children: /*#__PURE__*/ _jsxs("div", {
className: styles.statusIndicator,
children: [
/*#__PURE__*/ _jsx("span", {
className: `${styles.statusDot} ${styles[siteConfig.connectionStatus]}`
}),
/*#__PURE__*/ _jsxs("span", {
className: styles.statusText,
children: [
siteConfig.connectionStatus === 'not-scanned' && 'Not Scanned',
siteConfig.connectionStatus === 'scanned' && siteConfig.discoveredContent && siteConfig.discoveredContent.length > 0 && /*#__PURE__*/ _jsxs("div", {
className: styles.discoveredContent,
children: [
/*#__PURE__*/ _jsxs("div", {
className: styles.discoveredHeader,
children: [
/*#__PURE__*/ _jsxs("h4", {
children: [
"Available Content (",
siteConfig.totalItems,
" total items)"
]
}),
/*#__PURE__*/ _jsx("small", {
children: "Ready for migration"
})
]
}),
/*#__PURE__*/ _jsx("div", {
className: styles.contentList,
children: siteConfig.discoveredContent.map((content)=>/*#__PURE__*/ _jsxs("div", {
className: styles.contentItem,
children: [
/*#__PURE__*/ _jsxs("span", {
className: styles.contentLabel,
children: [
content.label,
":"
]
}),
/*#__PURE__*/ _jsxs("span", {
className: styles.contentCount,
children: [
content.count,
" items"
]
}),
content.custom && /*#__PURE__*/ _jsx("span", {
className: styles.customBadge,
children: "Custom"
})
]
}, content.type))
})
]
}),
siteConfig.connectionStatus === 'scanned' && 'Content Discovered',
siteConfig.connectionStatus === 'failed' && 'Scan Failed'
]
})
]
})
}),
/*#__PURE__*/ _jsx("div", {
className: styles.formActions,
children: configState.isEditing ? /*#__PURE__*/ _jsxs(_Fragment, {
children: [
/*#__PURE__*/ _jsx("button", {
className: styles.saveButton,
disabled: loading,
onClick: handleSaveConfig,
children: "Save Configuration"
}),
configState.isSaved && /*#__PURE__*/ _jsx("button", {
className: styles.cancelButton,
disabled: loading,
onClick: handleCancelEdit,
children: "Cancel"
})
]
}) : /*#__PURE__*/ _jsx("button", {
className: styles.editButton,
disabled: loading,
onClick: handleEditConfig,
children: "Edit Configuration"
})
})
]
})
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: styles.statsGrid,
children: [
/*#__PURE__*/ _jsxs("div", {
className: styles.statCard,
children: [
/*#__PURE__*/ _jsx("h3", {
children: "Site Configured"
}),
/*#__PURE__*/ _jsx("p", {
className: styles.statValue,
children: siteConfig.connectionStatus !== 'scanned' && '✗ No'
}),
siteConfig.connectionStatus === 'scanned' && siteConfig.discoveredContent && /*#__PURE__*/ _jsxs("small", {
className: styles.statDetail,
children: [
siteConfig.discoveredContent.length,
" content types",
/*#__PURE__*/ _jsx("br", {}),
siteConfig.totalItems,
" items"
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: styles.statCard,
children: [
/*#__PURE__*/ _jsx("h3", {
children: "Total Jobs"
}),
/*#__PURE__*/ _jsx("p", {
className: styles.statValue,
children: summary.totalJobs || 0
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: styles.statCard,
children: [
/*#__PURE__*/ _jsx("h3", {
children: "Active Jobs"
}),
/*#__PURE__*/ _jsx("p", {
className: `${styles.statValue} ${(summary.activeJobs || 0) > 0 ? styles.active : ''}`,
children: summary.activeJobs || 0
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: styles.statCard,
children: [
/*#__PURE__*/ _jsx("h3", {
children: "Items Migrated"
}),
/*#__PURE__*/ _jsx("p", {
className: styles.statValue,
children: (summary.totalItemsMigrated || 0).toLocaleString()
})
]
})
]
}),
/*#__PURE__*/ _jsxs("div", {
className: styles.recentJobs,
children: [
/*#__PURE__*/ _jsx("h3", {
children: "Recent Migration Jobs"
}),
/*#__PURE__*/ _jsx("div", {
className: styles.jobsTable,
children: /*#__PURE__*/ _jsxs("table", {
children: [
/*#__PURE__*/ _jsx("thead", {
children: /*#__PURE__*/ _jsxs("tr", {
children: [
/*#__PURE__*/ _jsx("th", {
children: "Job Name"
}),
/*#__PURE__*/ _jsx("th", {
children: "Status"
}),
/*#__PURE__*/ _jsx("th", {
children: "Items"
}),
/*#__PURE__*/ _jsx("th", {
children: "Success Rate"
}),
/*#__PURE__*/ _jsx("th", {
children: "Actions"
})
]
})
}),
/*#__PURE__*/ _jsx("tbody", {
children: summary.recentJobs?.map((job)=>{
const successRate = job.progress.processedItems > 0 ? job.progress.successfulItems / job.progress.processedItems * 100 : 0;
return /*#__PURE__*/ _jsxs("tr", {
children: [
/*#__PURE__*/ _jsx("td", {
children: job.jobName
}),
/*#__PURE__*/ _jsx("td", {
children: /*#__PURE__*/ _jsx("span", {
className: `${styles.statusBadge} ${styles[job.status]}`,
children: job.status
})
}),
/*#__PURE__*/ _jsxs("td", {
children: [
job.progress.processedItems,
"/",
job.progress.totalItems
]
}),
/*#__PURE__*/ _jsxs("td", {
children: [
successRate.toFixed(1),
"%"
]
}),
/*#__PURE__*/ _jsx("td", {
children: /*#__PURE__*/ _jsxs("div", {
className: styles.jobActions,
children: [
job.status === 'ready' && /*#__PURE__*/ _jsx("button", {
className: styles.actionButton,
disabled: loading,
onClick: ()=>startMigrationJob(job.id),
children: "Start"
}),
job.status === 'running' && /*#__PURE__*/ _jsx("button", {
className: styles.actionButton,
onClick: ()=>pauseMigrationJob(job.id),
children: "Pause"
}),
job.status === 'paused' && /*#__PURE__*/ _jsxs(_Fragment, {
children: [
/*#__PURE__*/ _jsx("button", {
className: styles.actionButton,
disabled: loading,
onClick: ()=>resumeMigrationJob(job.id),
children: "Resume"
}),
/*#__PURE__*/ _jsx("button", {
className: `${styles.actionButton} ${styles.restartButton}`,
disabled: loading,
onClick: ()=>restartMigrationJob(job.id),
title: "Restart entire migration from beginning",
children: "Restart"
})
]
}),
job.status === 'failed' && /*#__PURE__*/ _jsx("button", {
className: `${styles.actionButton} ${styles.retryButton}`,
disabled: loading,
onClick: ()=>retryMigrationJob(job.id),
children: "Retry"
}),