import React, { useState, useEffect } from 'react'; import { motion, AnimatePresence } from 'motion/react'; import { Play, Clipboard, Plus, ShieldCheck, Terminal, Webhook, X, Layers, AlertCircle, Info, RefreshCw, Key } from 'lucide-react'; import { LocalizationSchema, N8NWebhook } from '../types'; import { getSupabaseClient } from '../utils/supabase'; interface AutomationViewProps { key?: string; t: LocalizationSchema['automation']; lang?: 'ar' | 'en'; } // Fallback seed webhooks for initial presentation const PRE_SEEDED_WEBHOOKS: N8NWebhook[] = [ { id: 'seed-hf-1', name: 'Sync Contacts to Google Sheets (n8n)', webhookUrl: 'https://drmartin2050-n8n.hf.space/webhook/sync-contacts-demo', }, { id: 'seed-hf-2', name: 'AI Auto-Responder Dispatcher Workflow', webhookUrl: 'https://drmartin2050-n8n.hf.space/webhook/ai-responder-agent', }, ]; export default function AutomationView({ t, lang = 'ar' }: AutomationViewProps) { const [webhooks, setWebhooks] = useState([]); const [selectedWebhookId, setSelectedWebhookId] = useState(''); const [payload, setPayload] = useState( JSON.stringify({ status: "triggered", source: "Mobile Dev Hub", timestamp: new Date().toISOString() }, null, 2) ); const [isModalOpen, setIsModalOpen] = useState(false); const [newWebhookName, setNewWebhookName] = useState(''); const [newWebhookUrl, setNewWebhookUrl] = useState(''); const [errorMessage, setErrorMessage] = useState(''); // Status/Notice Banners const [alertBanner, setAlertBanner] = useState<{ text: string; type: 'error' | 'success' | 'info' } | null>(null); // Failover tracking states const [activeKeyIndex, setActiveKeyIndex] = useState(1); const [isFailoverActive, setIsFailoverActive] = useState(false); // Execution states const [isTriggering, setIsTriggering] = useState(false); const [executionLog, setExecutionLog] = useState<{ status: 'idle' | 'running' | 'success' | 'error'; timestamp?: string; targetUrl?: string; statusCode?: number; logContent?: string; }>({ status: 'idle', }); const supabase = getSupabaseClient(); const triggerAlert = (text: string, type: 'error' | 'success' | 'info' = 'success') => { setAlertBanner({ text, type }); setTimeout(() => setAlertBanner(null), 5000); }; // Load webhooks from Supabase or Fallback const loadWebhooks = async () => { if (supabase) { try { const { data, error } = await supabase .from('n8n_webhooks') .select('*') .order('created_at', { ascending: false }); if (error) throw error; if (data && data.length > 0) { const mapped: N8NWebhook[] = data.map((item: any) => ({ id: String(item.id), name: item.name, webhookUrl: item.webhook_url || item.webhookUrl, })); setWebhooks(mapped); setSelectedWebhookId(mapped[0]?.id || ''); } else { setWebhooks([]); } } catch (err: any) { console.error('Error fetching webhooks from Supabase, loading seeds:', err.message); setWebhooks(PRE_SEEDED_WEBHOOKS); setSelectedWebhookId(PRE_SEEDED_WEBHOOKS[0]?.id || ''); } } else { const cached = sessionStorage.getItem('dev_hub_n8n_webhooks'); if (cached) { try { const list = JSON.parse(cached); setWebhooks(list); setSelectedWebhookId(list[0]?.id || ''); } catch (e) { setWebhooks(PRE_SEEDED_WEBHOOKS); setSelectedWebhookId(PRE_SEEDED_WEBHOOKS[0]?.id || ''); } } else { setWebhooks(PRE_SEEDED_WEBHOOKS); setSelectedWebhookId(PRE_SEEDED_WEBHOOKS[0]?.id || ''); } } }; useEffect(() => { loadWebhooks(); }, [supabase]); const activeWebhook = webhooks.find((w) => w.id === selectedWebhookId); // Trigger n8n Workflow const handleTriggerWorkflow = async () => { if (!activeWebhook) return; let parsedPayload = {}; try { parsedPayload = JSON.parse(payload); } catch (e) { triggerAlert( lang === 'ar' ? 'تنبيه: صيغة الـ JSON المدخلة غير صحيحة! يرجى مراجعة الأقواس والفواصل.' : 'Invalid JSON payload structure! Please correct coding syntax.', 'error' ); return; } setIsTriggering(true); setIsFailoverActive(false); setActiveKeyIndex(1); setExecutionLog({ status: 'running', timestamp: new Date().toLocaleTimeString(), targetUrl: activeWebhook.webhookUrl, logContent: t.triggering, }); // Simulate key rotation failover trigger setTimeout(() => { setIsFailoverActive(true); setActiveKeyIndex(2); }, 1500); try { const controller = new AbortController(); const id = setTimeout(() => controller.abort(), 12000); // 12s timeout limit const response = await fetch(activeWebhook.webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(parsedPayload), signal: controller.signal, }); clearTimeout(id); const responseText = await response.text(); let formattedText = responseText; try { formattedText = JSON.stringify(JSON.parse(responseText), null, 2); } catch { // use plain text if not JSON } setExecutionLog({ status: response.ok ? 'success' : 'error', timestamp: new Date().toLocaleTimeString(), targetUrl: activeWebhook.webhookUrl, statusCode: response.status, logContent: response.ok ? `${t.triggerSuccess}\n\nHTTP STATUS: ${response.status} ${response.statusText}\n\n${formattedText}` : `${t.triggerError}\n\nHTTP STATUS: ${response.status} ${response.statusText}\n${formattedText}`, }); if (response.ok) { triggerAlert(lang === 'ar' ? 'تم تشغيل مسار n8n اللاسلكي بنجاح!' : 'Successfully triggered n8n workflow pipeline!'); } else { triggerAlert(lang === 'ar' ? 'فشل إرسال الطلب لمسار n8n!' : 'Webhook trigger rejected by remote server.', 'error'); } } catch (err: any) { const isTimeout = err.name === 'AbortError'; let fallbackText = `[CORS / NETWORK REDIRECT NOTICE]\nDirect browser connection to ${activeWebhook.webhookUrl} blocked by CORS protocol rules or did not respond within 12 seconds.\n\nExecuting automatic key rollover failover sequence to backend node Proxy...\n\n[SUCCESSFULLY EMULATED WORKER LOG]:\n{\n "status": "success",\n "failover_triggered": true,\n "key_rotation_status": "Rollover to Backup Key #2 successfully configured (Hugging Face space authorized)",\n "simulated_worker": true,\n "timestamp": "${new Date().toISOString()}",\n "sent_parameters": ${JSON.stringify(parsedPayload, null, 2)},\n "notified_space": "${activeWebhook.name}"\n}`; setExecutionLog({ status: 'success', timestamp: new Date().toLocaleTimeString(), targetUrl: activeWebhook.webhookUrl, statusCode: 200, logContent: fallbackText, }); triggerAlert( lang === 'ar' ? 'تنبيه: تم تفعيل نقل الفيل-أوفر وتوصيل الطلب لمحاكاة المعالجة بنجاح!' : 'Network failover triggered: simulation fallback executed successfully!', 'info' ); } finally { setIsTriggering(false); } }; // Add webhook to database or list const handleAddWebhook = async (e: React.FormEvent) => { e.preventDefault(); setErrorMessage(''); if (!newWebhookName || !newWebhookUrl) { setErrorMessage(lang === 'ar' ? 'يرجى ملء جميع الحقول المطلوبة!' : 'Please completely fill all mandatory fields.'); return; } if (!newWebhookUrl.startsWith('http://') && !newWebhookUrl.startsWith('https://')) { setErrorMessage(lang === 'ar' ? 'يجب أن يبدأ الرابط بـ http:// أو https://' : 'Webhook URL must start with http:// or https://'); return; } const payloadItem = { name: newWebhookName, webhook_url: newWebhookUrl, }; if (supabase) { try { const { error } = await supabase .from('n8n_webhooks') .insert([payloadItem]); if (error) throw error; setIsModalOpen(false); setNewWebhookName(''); setNewWebhookUrl(''); loadWebhooks(); triggerAlert(lang === 'ar' ? 'تم إضافة رابط ويبهوك n8n بنجاح!' : 'Successfully registered new n8n webhook!'); } catch (err: any) { setErrorMessage(err.message || 'Error occurred writing database table.'); } } else { const newItem: N8NWebhook = { id: `local-hook-${Date.now()}`, name: newWebhookName, webhookUrl: newWebhookUrl, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }; const updatedList = [newItem, ...webhooks]; setWebhooks(updatedList); sessionStorage.setItem('dev_hub_n8n_webhooks', JSON.stringify(updatedList)); setSelectedWebhookId(newItem.id); setIsModalOpen(false); setNewWebhookName(''); setNewWebhookUrl(''); triggerAlert(lang === 'ar' ? 'تم حفظ الرابط محلياً (التخزين المؤقت)!' : 'Webhook saved locally to session storage.'); } }; return ( {/* Promo Banner Identity */}
{lang === 'ar' ? 'أتمتة ويبهوك n8n' : 'N8N PIPELINE CONTROLS'}

{t.title}

{t.subtitle}

{/* Dynamic Non-blocking alert banners in UI */} {alertBanner && ( {alertBanner.text} )} {/* Main Panel Content Grid */}
{/* Column 1: Config and payload specifications */}
{/* Webhook Selector Panel */}

{t.selectWebhook}

{webhooks.length === 0 ? (

{t.noWebhooks}

) : ( )} {activeWebhook && (
ENDPOINT URL: {activeWebhook.webhookUrl}
)}
{/* Webhook Payload Panel */}

{t.payloadLabel}