/home/techb158/balavpn.abdallabala.com/src/app/integrations/[integrationId]
NameSizeModeActions
page.jsx261390644editdlrm
Edit: /home/techb158/balavpn.abdallabala.com/src/app/integrations/[integrationId]/page.jsx (26139B)
'use client'; import React, { useEffect, useState, useCallback } from 'react'; import { useRouter, useParams } from 'next/navigation'; import { api } from '../../../lib/api-client'; const PROVIDER_LABELS = { TRELLO: 'Trello', JIRA: 'Jira', ASANA: 'Asana', MICROSOFT_PLANNER: 'Microsoft Planner' }; const PROVIDER_ICONS = { TRELLO: 'T', JIRA: 'J', ASANA: 'A', MICROSOFT_PLANNER: 'P' }; function credentialExample(provider) { if (provider === 'TRELLO') return '{\n "apiKey": "...",\n "token": "...",\n "listId": "trello-list-id"\n}'; if (provider === 'JIRA') return '{\n "apiEmail": "you@example.com",\n "apiToken": "...",\n "baseUrl": "https://your-domain.atlassian.net",\n "projectKey": "COS"\n}'; if (provider === 'ASANA') return '{\n "accessToken": "...",\n "projectGid": "asana-project-gid"\n}'; return '{\n "accessToken": "...",\n "planId": "planner-plan-id",\n "bucketId": "planner-bucket-id"\n}'; } function useToast() { const [toasts, setToasts] = useState([]); const add = useCallback((message, type = 'success') => { const id = Date.now() + Math.random(); setToasts(prev => [...prev, { id, message, type }]); setTimeout(() => setToasts(prev => prev.filter(t => t.id !== id)), 3000); }, []); return { toasts, add }; } function ToastContainer({ toasts }) { if (!toasts.length) return null; return (
{toasts.map(t => (
{t.message}
))}
); } function statusBadgeClass(status) { if (status === 'CONNECTED') return 'badge badge-green'; if (status === 'NEEDS_CONFIGURATION') return 'badge badge-warn'; return 'badge'; } export default function IntegrationManagePage() { const router = useRouter(); const params = useParams(); const integrationId = params.integrationId; const { toasts, add } = useToast(); const [integration, setIntegration] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [busyId, setBusyId] = useState(null); const [saving, setSaving] = useState(false); const [credentialsText, setCredentialsText] = useState(''); const [expandedRun, setExpandedRun] = useState(null); const [discovering, setDiscovering] = useState(false); const [discoveredResources, setDiscoveredResources] = useState(null); const [statusMap, setStatusMap] = useState({}); const [selectedLists, setSelectedLists] = useState({}); const [form, setForm] = useState({ workspaceName: '', externalProjectKey: '', baseUrl: '', authMode: 'API_KEY', liveEnabled: false }); const set = key => value => setForm(f => ({ ...f, [key]: value })); const load = useCallback(async () => { setLoading(true); setError(null); try { const data = await api.get(`/api/integrations/${integrationId}`); const int = data.integration; setIntegration(int); setForm({ workspaceName: int.workspaceName || '', externalProjectKey: int.externalProjectKey || '', baseUrl: int.baseUrl || '', authMode: int.authMode || 'API_KEY', liveEnabled: int.liveEnabled || false }); const savedMap = int.liveConfig?.statusListMap || {}; setStatusMap(savedMap); setSelectedLists(savedMap); } catch (e) { setError(e.message); } finally { setLoading(false); } }, [integrationId]); useEffect(() => { load(); }, [load]); const handleSaveSettings = async () => { setSaving(true); try { const data = await api.patch(`/api/integrations/${integrationId}`, { workspaceName: form.workspaceName, externalProjectKey: form.externalProjectKey, baseUrl: form.baseUrl, authMode: form.authMode, liveEnabled: form.liveEnabled }); setIntegration(data.integration); add('Settings saved'); } catch (e) { add(e.message || 'Failed to save settings', 'error'); } finally { setSaving(false); } }; const handleSaveCredentials = async () => { if (!credentialsText.trim()) { add('No credentials to save', 'error'); return; } let parsed; try { parsed = JSON.parse(credentialsText); } catch (_error) { add('Credential JSON is invalid', 'error'); return; } setSaving(true); try { await api.patch(`/api/integrations/${integrationId}`, { credentials: parsed }); setCredentialsText(''); add('Credentials saved (encrypted)'); } catch (e) { add(e.message || 'Failed to save credentials', 'error'); } finally { setSaving(false); } }; const runLiveAction = async (action) => { setBusyId(action); try { const path = action === 'test' ? `/api/integrations/${integrationId}/live/test` : action === 'live-sync' ? `/api/integrations/${integrationId}/live/sync` : `/api/integrations/${integrationId}/sync`; const result = await api.post(path, {}); const summary = result?.syncRun?.summary || result?.account || `${action} completed`; add(summary); await load(); } catch (e) { add(e.message || `${action} failed`, 'error'); } finally { setBusyId(null); } }; const handleDiscover = async () => { setDiscovering(true); setDiscoveredResources(null); try { const data = await api.get(`/api/integrations/${integrationId}/discover`); setDiscoveredResources(data.resources || []); if (!data.resources || data.resources.length === 0) add('No resources found. Check your credentials.', 'error'); } catch (e) { add(e.message || 'Discovery failed', 'error'); } finally { setDiscovering(false); } }; const selectList = (listId, listName) => { setForm(f => ({ ...f, externalProjectKey: listId })); add(`Selected list: ${listName}`); }; const setStatusList = (status, listId) => { setSelectedLists(prev => ({ ...prev, [status]: listId })); }; const saveStatusMapping = async () => { setSaving(true); try { await api.patch(`/api/integrations/${integrationId}`, { liveConfig: { ...(integration.liveConfig || {}), statusListMap: selectedLists } }); setStatusMap(selectedLists); add('Status→List mapping saved'); await load(); } catch (e) { add(e.message || 'Failed to save mapping', 'error'); } finally { setSaving(false); } }; const handleDelete = async () => { if (!window.confirm('Delete this integration? This cannot be undone.')) return; setBusyId('delete'); try { await api.del(`/api/integrations/${integrationId}`); add('Integration deleted'); router.push('/dashboard?tab=Integrations'); } catch (e) { add(e.message || 'Failed to delete', 'error'); } finally { setBusyId(null); } }; if (loading) { return
; } if (error) { return (

{error}

); } const provider = integration.provider; const icon = PROVIDER_ICONS[provider] || '?'; const providerLabel = PROVIDER_LABELS[provider] || provider; const mappings = integration.mappings || []; const syncRuns = integration.syncRuns || []; return (
{ e.preventDefault(); router.push('/dashboard?tab=Integrations'); }} style={{ color: 'var(--muted)', textDecoration: 'none', fontSize: 14, display: 'inline-flex', alignItems: 'center', gap: 4 }} > ← Back to Integrations
{icon}

{providerLabel} Integration

{integration.workspaceName || integration.workspace?.name || ''}

{integration.connectionStatus} {integration.liveEnabled ? Live API : Simulated Mapping}
{[ { label: '1. Project', done: true }, { label: '2. Risks', done: (integration.mappings || []).length > 0 || (integration.syncRuns || []).length > 0 }, { label: '3. Integration', done: true }, { label: integration.liveEnabled ? '4. Live API' : '4. Simulated Sync', done: (integration.syncRuns || []).length > 0 } ].map((step, i) => (
{step.done ? '✓' : '○'} {step.label}
))}

Settings

set('workspaceName')(e.target.value)} style={{ width: '100%' }} />
set('externalProjectKey')(e.target.value)} style={{ width: '100%' }} />
{integration.liveEnabled && ( <>
set('baseUrl')(e.target.value)} style={{ width: '100%' }} />
)}
{!integration.liveEnabled && (

Simulated Sync

Map COSMIC risks into external work item records. No real API calls are made — only database mapping records are created.

)} {provider === 'TRELLO' && (

Trello Boards

Discover your Trello boards and lists to find the correct list ID. Works in both modes.

{discoveredResources && discoveredResources.length > 0 && (
{discoveredResources.map(board => (
{board.name}
{(board.lists || []).map(list => ( ))}
))}

Click a list name to set it as the External Project / List ID.

)}
)} {provider === 'TRELLO' && (

Status → List Mapping

Map each COSMIC risk status to a Trello list. Click "Discover Boards" above to populate the dropdowns.

{(() => { const allLists = discoveredResources ? discoveredResources.flatMap(b => (b.lists || [])) : []; const statuses = ['OPEN', 'IN_MITIGATION', 'ACCEPTED', 'CLOSED']; return statuses.map(status => (
{status}
)); })()}
{Object.keys(statusMap).length > 0 && ( <> Current: {Object.entries(statusMap).map(([s, l]) => { const found = discoveredResources?.flatMap(b => b.lists || []).find(lst => lst.id === l); return `${s}→${found ? found.name : l.slice(0, 8) + '...'}`; }).join(', ')} )}
)} {integration.liveEnabled && ( <>

Live Credentials

Paste provider credential JSON. Values are encrypted via AES-256-GCM and stored in OAuthToken records.