Data Sources

Connect your data warehouses

let currentProvider = 'redshift'; function formatRelativeTime(dateStr) { if (!dateStr) return 'Never synced'; const date = new Date(dateStr); const now = new Date(); const seconds = Math.floor((now - date) / 1000); if (seconds < 60) return 'just now'; const minutes = Math.floor(seconds / 60); if (minutes < 60) return minutes + ' minute' + (minutes > 1 ? 's' : '') + ' ago'; const hours = Math.floor(minutes / 60); if (hours < 24) return hours + ' hour' + (hours > 1 ? 's' : '') + ' ago'; const days = Math.floor(hours / 24); return days + ' day' + (days > 1 ? 's' : '') + ' ago'; } function openModal() { document.getElementById('modalOverlay').classList.remove('hidden'); selectProvider('redshift'); document.getElementById('connectorForm').reset(); } function closeModal() { document.getElementById('modalOverlay').classList.add('hidden'); } function selectProvider(provider) { currentProvider = provider; const redshiftEl = document.getElementById('redshiftProvider'); const athenaEl = document.getElementById('athenaProvider'); const redshiftFieldsEl = document.getElementById('redshiftFields'); const athenaFieldsEl = document.getElementById('athenaFields'); if (provider === 'redshift') { redshiftEl.classList.add('border-2', 'border-secondary', 'shadow-[4px_4px_0px_0px_rgba(45,45,45,1)]'); redshiftEl.classList.remove('border', 'border-outline/30', 'opacity-70'); athenaEl.classList.remove('border-2', 'border-secondary', 'shadow-[4px_4px_0px_0px_rgba(45,45,45,1)]'); athenaEl.classList.add('border', 'border-outline/30', 'opacity-70'); redshiftFieldsEl.classList.remove('hidden'); athenaFieldsEl.classList.add('hidden'); } else { athenaEl.classList.add('border-2', 'border-secondary', 'shadow-[4px_4px_0px_0px_rgba(45,45,45,1)]'); athenaEl.classList.remove('border', 'border-outline/30', 'opacity-70'); redshiftEl.classList.remove('border-2', 'border-secondary', 'shadow-[4px_4px_0px_0px_rgba(45,45,45,1)]'); redshiftEl.classList.add('border', 'border-outline/30', 'opacity-70'); athenaFieldsEl.classList.remove('hidden'); redshiftFieldsEl.classList.add('hidden'); } } async function loadConnectors() { if (!requireAuth()) return; try { const res = await apiFetch('/api/connectors'); if (!res) return; const connectors = await res.json(); renderConnectors(connectors); } catch (err) { console.error('Error loading connectors:', err); } } function renderConnectors(connectors) { const grid = document.getElementById('connectorsGrid'); grid.innerHTML = ''; if (connectors.length === 0) { grid.innerHTML = `
add_circle

Connect another source

`; return; } connectors.forEach(connector => { const card = document.createElement('div'); card.id = 'connector-' + connector.id; card.className = 'relative bg-surface-container-lowest p-8 shadow-[4px_4px_0px_0px_rgba(45,45,45,1)] wobbly-border rotate-1 transition-transform hover:rotate-0'; const typeColor = connector.type === 'redshift' ? 'bg-secondary-fixed text-on-secondary-fixed-variant' : 'bg-secondary-fixed text-on-secondary-fixed-variant'; const lastSyncText = formatRelativeTime(connector.synced_at); card.innerHTML = `
push_pin

${connector.name}

${connector.type === 'redshift' ? 'Redshift' : 'Athena'}
check_circle Connected

Data source connected

Last sync: ${lastSyncText}

`; grid.appendChild(card); }); // Add empty card const emptyCard = document.createElement('div'); emptyCard.className = 'relative border-2 border-dashed border-outline p-8 wobbly-border flex flex-col items-center justify-center opacity-60 hover:opacity-100 cursor-pointer transition-all'; emptyCard.onclick = openModal; emptyCard.innerHTML = ` add_circle

Connect another source

`; grid.appendChild(emptyCard); } async function handleFormSubmit(e) { e.preventDefault(); if (!requireAuth()) return; const name = document.getElementById('nameInput').value; if (!name.trim()) { alert('Please enter a data source name'); return; } let payload; if (currentProvider === 'redshift') { const host = document.getElementById('hostInput').value; const port = document.getElementById('portInput').value; const database = document.getElementById('databaseInput').value; const user = document.getElementById('userInput').value; const password = document.getElementById('passwordInput').value; if (!host || !port || !database || !user || !password) { alert('Please fill in all Redshift fields'); return; } payload = { type: 'redshift', name: name, config: { host, port: parseInt(port), database, user, password } }; } else { const region = document.getElementById('regionInput').value; const access_key_id = document.getElementById('accessKeyInput').value; const secret_access_key = document.getElementById('secretKeyInput').value; const s3_output_location = document.getElementById('s3LocationInput').value; const catalog_name = document.getElementById('catalogNameInput').value; if (!region || !access_key_id || !secret_access_key || !s3_output_location) { alert('Please fill in all Athena fields'); return; } payload = { type: 'athena', name: name, config: { region, access_key_id, secret_access_key, s3_output_location, catalog_name } }; } try { const res = await apiFetch('/api/connectors', { method: 'POST', body: JSON.stringify(payload) }); if (!res) return; if (res.status === 201) { closeModal(); await loadConnectors(); } else { const err = await res.json(); alert('Error: ' + (err.message || 'Failed to create connector')); } } catch (err) { console.error('Error creating connector:', err); alert('Error: ' + err.message); } } async function syncConnector(id) { if (!requireAuth()) return; try { const res = await apiFetch('/api/connectors/' + id + '/sync', { method: 'POST' }); if (!res) return; if (res.status === 200) { await loadConnectors(); } else { alert('Sync failed'); } } catch (err) { console.error('Error syncing connector:', err); alert('Error: ' + err.message); } } async function removeConnector(id) { if (!confirm('Are you sure you want to remove this connector?')) return; if (!requireAuth()) return; try { const res = await apiFetch('/api/connectors/' + id, { method: 'DELETE' }); if (!res) return; if (res.status === 204) { const card = document.getElementById('connector-' + id); if (card) card.remove(); await loadConnectors(); } else { alert('Failed to remove connector'); } } catch (err) { console.error('Error removing connector:', err); alert('Error: ' + err.message); } } function testConnection() { alert('Test connection feature coming soon'); } // Initialize on page load if (requireAuth()) { loadConnectors(); }