/** * Spaxel Dashboard - Integrations Panel * * Home Automation Integration settings for MQTT and webhooks */ (function() { 'use strict'; // ============================================ // Integrations State // ============================================ const integrationsState = { loading: false, saving: false, testing: false, currentSettings: null }; // ============================================ // Integrations API // ============================================ /** * Fetch integration settings from server */ function fetchIntegrations() { integrationsState.loading = true; renderContent(); return fetch('/api/settings/integration') .then(function(res) { if (!res.ok) { throw new Error('Failed to fetch integration settings: ' + res.status); } return res.json(); }) .then(function(data) { integrationsState.currentSettings = data; integrationsState.loading = false; renderContent(); return data; }) .catch(function(err) { integrationsState.loading = false; console.error('[IntegrationsPanel] Error fetching settings:', err); renderContent(); throw err; }); } /** * Save integration settings to server * @param {Object} updates - Settings to update (partial) */ function saveIntegrations(updates) { if (!integrationsState.currentSettings) { return Promise.reject(new Error('No settings loaded')); } integrationsState.saving = true; renderContent(); return fetch('/api/settings/integration', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updates) }) .then(function(res) { if (!res.ok) { return res.json().then(function(err) { throw new Error(err.error || 'Failed to save settings'); }); } return res.json(); }) .then(function(data) { integrationsState.currentSettings = data; integrationsState.saving = false; renderContent(); SpaxelPanels.showSuccess('Integration settings saved successfully'); return data; }) .catch(function(err) { integrationsState.saving = false; console.error('[IntegrationsPanel] Error saving settings:', err); renderContent(); SpaxelPanels.showError('Failed to save settings: ' + err.message); throw err; }); } /** * Test integration connection * @param {string} type - "mqtt" or "webhook" */ function testIntegration(type) { integrationsState.testing = true; renderContent(); return fetch('/api/settings/integration/test', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: type }) }) .then(function(res) { if (!res.ok) { return res.json().then(function(err) { throw new Error(err.error || 'Test failed'); }); } return res.json(); }) .then(function(data) { integrationsState.testing = false; renderContent(); SpaxelPanels.showSuccess(data.message || 'Test successful'); return data; }) .catch(function(err) { integrationsState.testing = false; console.error('[IntegrationsPanel] Test failed:', err); renderContent(); SpaxelPanels.showError('Test failed: ' + err.message); throw err; }); } // ============================================ // Panel Content Rendering // ============================================ /** * Open the integrations panel */ function openIntegrationsPanel() { fetchIntegrations().then(function() { SpaxelPanels.openSidebar({ title: 'Integrations', content: '
', width: '450px', onOpen: function() { renderContent(); } }); }); } /** * Render the integrations panel content */ function renderContent() { const content = document.getElementById('integrations-panel-content'); if (!content) return; if (integrationsState.loading) { content.innerHTML = renderLoading(); return; } const settings = integrationsState.currentSettings || {}; content.innerHTML = ` ${renderMQTTSection(settings.mqtt)} ${renderWebhookSection(settings.webhook)} `; // Attach event listeners attachEventListeners(); } function renderLoading() { return `
Loading integration settings...
`; } function renderMQTTSection(mqttCfg) { if (!mqttCfg) mqttCfg = {}; const broker = mqttCfg.broker || ''; const username = mqttCfg.username || ''; const connected = mqttCfg.connected || false; const discoveryPrefix = mqttCfg.discovery_prefix || 'homeassistant'; const tlsEnabled = mqttCfg.tls || false; const statusIndicator = connected ? '● Connected' : '● Disconnected'; return `
MQTT Integration ${statusIndicator}
Home Assistant Auto-Discovery
Automatically configure Spaxel entities in Home Assistant via MQTT
Examples: tcp://broker.local:1883, mqtt://broker.local:1883, mqtts://broker.local:8883
Leave blank to keep existing password
Home Assistant MQTT discovery topic prefix (default: homeassistant)
`; } function renderWebhookSection(webhookCfg) { if (!webhookCfg) webhookCfg = {}; const url = webhookCfg.url || ''; const enabled = webhookCfg.enabled || false; return `
System Webhook
Event Streaming
Send all Spaxel events to a custom webhook URL for integration with external services
Events will be POSTed as JSON with X-Spaxel-Event header
`; } /** * Attach event listeners to the rendered content */ function attachEventListeners() { // MQTT save button const mqttSaveBtn = document.getElementById('save-mqtt-btn'); if (mqttSaveBtn) { mqttSaveBtn.addEventListener('click', saveMQTTSettings); } // MQTT test button const mqttTestBtn = document.getElementById('test-mqtt-btn'); if (mqttTestBtn) { mqttTestBtn.addEventListener('click', function() { testIntegration('mqtt'); }); } // Publish discovery button const publishDiscoveryBtn = document.getElementById('publish-discovery-btn'); if (publishDiscoveryBtn) { publishDiscoveryBtn.addEventListener('click', publishDiscovery); } // Webhook enabled toggle const webhookEnabled = document.getElementById('webhook-enabled'); if (webhookEnabled) { webhookEnabled.addEventListener('change', function() { const urlGroup = document.getElementById('webhook-url-group'); if (urlGroup) { urlGroup.style.display = this.checked ? '' : 'none'; } }); } // Webhook save button const webhookSaveBtn = document.getElementById('save-webhook-btn'); if (webhookSaveBtn) { webhookSaveBtn.addEventListener('click', saveWebhookSettings); } // Webhook test button const webhookTestBtn = document.getElementById('test-webhook-btn'); if (webhookTestBtn) { webhookTestBtn.addEventListener('click', function() { testIntegration('webhook'); }); } } /** * Save MQTT settings */ function saveMQTTSettings() { const broker = document.getElementById('mqtt-broker').value.trim(); const username = document.getElementById('mqtt-username').value.trim(); const password = document.getElementById('mqtt-password').value; const tls = document.getElementById('mqtt-tls').checked; const discoveryPrefix = document.getElementById('mqtt-discovery-prefix').value.trim(); const updates = { mqtt: { broker: broker, username: username, tls: tls, discovery_prefix: discoveryPrefix || 'homeassistant' } }; // Only include password if it's not empty if (password) { updates.mqtt.password = password; } saveIntegrations(updates); } /** * Save webhook settings */ function saveWebhookSettings() { const enabled = document.getElementById('webhook-enabled').checked; const url = document.getElementById('webhook-url').value.trim(); const updates = { webhook: { enabled: enabled, url: enabled ? url : '' } }; saveIntegrations(updates); } /** * Publish Home Assistant discovery configs */ function publishDiscovery() { const settings = integrationsState.currentSettings; if (!settings || !settings.mqtt || !settings.mqtt.connected) { SpaxelPanels.showError('MQTT must be connected to publish discovery'); return; } // Trigger discovery publish SpaxelPanels.showSuccess('Publishing discovery configurations...'); // In a full implementation, this would call an API to trigger discovery // For now, just show a success message setTimeout(function() { SpaxelPanels.showSuccess('Discovery configurations published to Home Assistant'); }, 500); } /** * Escape HTML special characters */ function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // ============================================ // Public API // ============================================ window.SpaxelIntegrations = { open: openIntegrationsPanel, fetch: fetchIntegrations, save: saveIntegrations, test: testIntegration }; // Register with SpaxelPanels if available if (window.SpaxelPanels) { SpaxelPanels.register('integrations', openIntegrationsPanel); } // Add menu item if available if (window.SpaxelMenu) { SpaxelMenu.addItem({ id: 'integrations', label: 'Integrations', icon: 'plug', action: openIntegrationsPanel, order: 50 }); } console.log('[IntegrationsPanel] Loaded'); })();