Refactor WordPress integration service to use API key for connection testing
- Updated the `IntegrationService` to perform connection tests using only the API key, removing reliance on username and app password. - Simplified health check logic and improved error messaging for better clarity. - Added functionality to revoke API keys in the `WordPressIntegrationForm` component. - Enhanced site settings page with a site selector and improved integration status display. - Cleaned up unused code and improved overall structure for better maintainability.
This commit is contained in:
@@ -16,7 +16,8 @@ import {
|
||||
AlertIcon,
|
||||
DownloadIcon,
|
||||
PlusIcon,
|
||||
CopyIcon
|
||||
CopyIcon,
|
||||
TrashBinIcon
|
||||
} from '../../icons';
|
||||
import { Globe, Key, RefreshCw } from 'lucide-react';
|
||||
|
||||
@@ -40,15 +41,6 @@ export default function WordPressIntegrationForm({
|
||||
const [generatingKey, setGeneratingKey] = useState(false);
|
||||
const [apiKey, setApiKey] = useState<string>('');
|
||||
const [apiKeyVisible, setApiKeyVisible] = useState(false);
|
||||
const [isActive, setIsActive] = useState(integration?.is_active ?? true);
|
||||
const [syncEnabled, setSyncEnabled] = useState(integration?.sync_enabled ?? true);
|
||||
|
||||
useEffect(() => {
|
||||
if (integration) {
|
||||
setIsActive(integration.is_active ?? true);
|
||||
setSyncEnabled(integration.sync_enabled ?? true);
|
||||
}
|
||||
}, [integration]);
|
||||
|
||||
// Load API key from site settings on mount
|
||||
useEffect(() => {
|
||||
@@ -121,6 +113,40 @@ export default function WordPressIntegrationForm({
|
||||
}
|
||||
};
|
||||
|
||||
const handleRevokeApiKey = async () => {
|
||||
if (!confirm('Are you sure you want to revoke the API key? Your WordPress plugin will stop working until you generate a new key.')) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setGeneratingKey(true);
|
||||
// Clear API key from site settings by setting it to empty string
|
||||
await fetchAPI(`/v1/auth/sites/${siteId}/`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ wp_api_key: '' }),
|
||||
});
|
||||
|
||||
setApiKey('');
|
||||
setApiKeyVisible(false);
|
||||
|
||||
// Trigger integration update to reload the integration state
|
||||
if (onIntegrationUpdate && integration) {
|
||||
await loadApiKeyFromSite();
|
||||
// Reload integration to reflect changes
|
||||
const integrations = await integrationApi.getSiteIntegrations(siteId);
|
||||
const wp = integrations.find(i => i.platform === 'wordpress');
|
||||
if (wp) {
|
||||
onIntegrationUpdate(wp);
|
||||
}
|
||||
}
|
||||
|
||||
toast.success('API key revoked successfully');
|
||||
} catch (error: any) {
|
||||
toast.error(`Failed to revoke API key: ${error.message}`);
|
||||
} finally {
|
||||
setGeneratingKey(false);
|
||||
}
|
||||
};
|
||||
|
||||
const saveApiKeyToSite = async (key: string) => {
|
||||
try {
|
||||
await fetchAPI(`/v1/auth/sites/${siteId}/`, {
|
||||
@@ -146,108 +172,103 @@ export default function WordPressIntegrationForm({
|
||||
toast.success('Plugin download started');
|
||||
};
|
||||
|
||||
const handleSaveSettings = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Update integration with active/sync settings
|
||||
if (integration) {
|
||||
await integrationApi.updateIntegration(integration.id, {
|
||||
is_active: isActive,
|
||||
sync_enabled: syncEnabled,
|
||||
} as any);
|
||||
} else {
|
||||
// Create integration if it doesn't exist
|
||||
await integrationApi.saveWordPressIntegration(siteId, {
|
||||
url: siteUrl || '',
|
||||
username: '',
|
||||
app_password: '',
|
||||
api_key: apiKey,
|
||||
is_active: isActive,
|
||||
sync_enabled: syncEnabled,
|
||||
});
|
||||
}
|
||||
|
||||
// Reload integration
|
||||
const updated = await integrationApi.getWordPressIntegration(siteId);
|
||||
if (onIntegrationUpdate && updated) {
|
||||
onIntegrationUpdate(updated);
|
||||
}
|
||||
|
||||
toast.success('Integration settings saved successfully');
|
||||
} catch (error: any) {
|
||||
toast.error(`Failed to save settings: ${error.message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTestConnection = async () => {
|
||||
if (!apiKey || !siteUrl) {
|
||||
toast.error('Please ensure API key and site URL are configured');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
// Test connection using API key and site URL
|
||||
const result = await fetchAPI(`/v1/integration/integrations/test-connection/`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
site_id: siteId,
|
||||
api_key: apiKey,
|
||||
site_url: siteUrl,
|
||||
}),
|
||||
});
|
||||
|
||||
// Check for fully functional connection (includes bidirectional communication)
|
||||
if (result?.fully_functional) {
|
||||
toast.success('✅ Connection is fully functional! Plugin is connected and can communicate with IGNY8.');
|
||||
} else if (result?.success) {
|
||||
// Basic connection works but not fully functional
|
||||
const issues = result?.issues || [];
|
||||
const healthChecks = result?.health_checks || {};
|
||||
|
||||
// Show specific warning based on what's missing
|
||||
if (!healthChecks.plugin_connected) {
|
||||
toast.warning('⚠️ WordPress is reachable but the plugin is not configured with an API key. Please add the API key in your WordPress plugin settings.');
|
||||
} else if (!healthChecks.bidirectional_communication) {
|
||||
toast.warning('⚠️ Plugin is configured but cannot reach IGNY8 backend. Please check your WordPress site\'s outbound connections and firewall settings.');
|
||||
} else {
|
||||
toast.warning(`⚠️ ${result?.message || 'Connection partially working. Some features may not function correctly.'}`);
|
||||
}
|
||||
} else {
|
||||
// Connection completely failed
|
||||
toast.error(`❌ Connection test failed: ${result?.message || 'Unknown error'}`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(`Connection test failed: ${error.message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const maskApiKey = (key: string) => {
|
||||
if (!key) return '';
|
||||
if (key.length <= 12) return key;
|
||||
return key.substring(0, 8) + '**********' + key.substring(key.length - 4);
|
||||
};
|
||||
|
||||
// Toggle integration enabled status
|
||||
const [integrationEnabled, setIntegrationEnabled] = useState(integration?.is_active ?? true);
|
||||
|
||||
const handleToggleIntegration = async (enabled: boolean) => {
|
||||
try {
|
||||
setIntegrationEnabled(enabled);
|
||||
|
||||
if (integration) {
|
||||
// Update existing integration
|
||||
await integrationApi.updateIntegration(integration.id, {
|
||||
is_active: enabled,
|
||||
} as any);
|
||||
toast.success(enabled ? 'Integration enabled' : 'Integration disabled');
|
||||
|
||||
// Reload integration
|
||||
const updated = await integrationApi.getWordPressIntegration(siteId);
|
||||
if (onIntegrationUpdate && updated) {
|
||||
onIntegrationUpdate(updated);
|
||||
}
|
||||
} else if (enabled && apiKey) {
|
||||
// Create integration when enabling for first time
|
||||
await integrationApi.saveWordPressIntegration(siteId, {
|
||||
url: siteUrl || '',
|
||||
username: '',
|
||||
app_password: '',
|
||||
api_key: apiKey,
|
||||
is_active: enabled,
|
||||
sync_enabled: true,
|
||||
});
|
||||
toast.success('Integration created and enabled');
|
||||
|
||||
// Reload integration
|
||||
const updated = await integrationApi.getWordPressIntegration(siteId);
|
||||
if (onIntegrationUpdate && updated) {
|
||||
onIntegrationUpdate(updated);
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(`Failed to update integration: ${error.message}`);
|
||||
// Revert on error
|
||||
setIntegrationEnabled(!enabled);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (integration) {
|
||||
setIntegrationEnabled(integration.is_active ?? true);
|
||||
}
|
||||
}, [integration]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-3 bg-indigo-100 dark:bg-indigo-900/30 rounded-lg">
|
||||
<Globe className="w-6 h-6 text-indigo-600 dark:text-indigo-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
|
||||
WordPress Integration
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
Connect your WordPress site using the IGNY8 WP Bridge plugin
|
||||
</p>
|
||||
{/* Header with Toggle */}
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-3 bg-indigo-100 dark:bg-indigo-900/30 rounded-lg">
|
||||
<Globe className="w-6 h-6 text-indigo-600 dark:text-indigo-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
|
||||
WordPress Integration
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
Connect your WordPress site using the IGNY8 WP Bridge plugin
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Toggle Switch */}
|
||||
{apiKey && (
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{integrationEnabled ? 'Enabled' : 'Disabled'}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleToggleIntegration(!integrationEnabled)}
|
||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-brand-500 focus:ring-offset-2 ${
|
||||
integrationEnabled ? 'bg-brand-600' : 'bg-gray-300 dark:bg-gray-600'
|
||||
}`}
|
||||
role="switch"
|
||||
aria-checked={integrationEnabled}
|
||||
>
|
||||
<span
|
||||
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
|
||||
integrationEnabled ? 'translate-x-6' : 'translate-x-1'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* API Keys Table */}
|
||||
@@ -366,11 +387,19 @@ export default function WordPressIntegrationForm({
|
||||
<button
|
||||
onClick={handleRegenerateApiKey}
|
||||
disabled={generatingKey}
|
||||
className="text-gray-500 hover:text-error-500 dark:text-gray-400 dark:hover:text-error-500 disabled:opacity-50"
|
||||
title="Regenerate"
|
||||
className="text-gray-500 hover:text-brand-500 dark:text-gray-400 dark:hover:text-brand-400 disabled:opacity-50 transition-colors"
|
||||
title="Regenerate API key"
|
||||
>
|
||||
<RefreshCw className={`w-5 h-5 ${generatingKey ? 'animate-spin' : ''}`} />
|
||||
</button>
|
||||
<button
|
||||
onClick={handleRevokeApiKey}
|
||||
disabled={generatingKey}
|
||||
className="text-gray-500 hover:text-error-500 dark:text-gray-400 dark:hover:text-error-400 disabled:opacity-50 transition-colors"
|
||||
title="Revoke API key"
|
||||
>
|
||||
<TrashBinIcon className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -407,104 +436,6 @@ export default function WordPressIntegrationForm({
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Integration Settings */}
|
||||
<Card className="p-6">
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||
Integration Settings
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{/* Checkboxes at the top */}
|
||||
<div className="space-y-4 pb-4 border-b border-gray-200 dark:border-gray-700">
|
||||
<Checkbox
|
||||
id="is_active"
|
||||
checked={isActive}
|
||||
onChange={(checked) => setIsActive(checked)}
|
||||
label="Enable Integration"
|
||||
/>
|
||||
<Checkbox
|
||||
id="sync_enabled"
|
||||
checked={syncEnabled}
|
||||
onChange={(checked) => setSyncEnabled(checked)}
|
||||
label="Enable Two-Way Sync"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-3 pt-4">
|
||||
{apiKey && siteUrl && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handleTestConnection}
|
||||
disabled={loading}
|
||||
>
|
||||
<RefreshCw className={`w-4 h-4 mr-2 ${loading ? 'animate-spin' : ''}`} />
|
||||
Test Connection
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
type="button"
|
||||
variant="solid"
|
||||
onClick={handleSaveSettings}
|
||||
disabled={loading || !apiKey}
|
||||
>
|
||||
{loading ? 'Saving...' : 'Save Settings'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Integration Status */}
|
||||
{integration && (
|
||||
<Card className="p-6">
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
Integration Status
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">Status</p>
|
||||
<div className="flex items-center gap-2">
|
||||
{integration.is_active ? (
|
||||
<CheckCircleIcon className="w-4 h-4 text-green-500" />
|
||||
) : (
|
||||
<AlertIcon className="w-4 h-4 text-gray-400" />
|
||||
)}
|
||||
<span className="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{integration.is_active ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">Sync Status</p>
|
||||
<div className="flex items-center gap-2">
|
||||
{integration.sync_status === 'success' ? (
|
||||
<CheckCircleIcon className="w-4 h-4 text-green-500" />
|
||||
) : integration.sync_status === 'failed' ? (
|
||||
<AlertIcon className="w-4 h-4 text-red-500" />
|
||||
) : (
|
||||
<RefreshCw className="w-4 h-4 text-yellow-500 animate-spin" />
|
||||
)}
|
||||
<span className="text-sm font-medium text-gray-900 dark:text-white capitalize">
|
||||
{integration.sync_status || 'Pending'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{integration.last_sync_at && (
|
||||
<div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">Last Sync</p>
|
||||
<p className="text-sm text-gray-900 dark:text-white">
|
||||
{new Date(integration.last_sync_at).toLocaleString()}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user