diff --git a/src/autometabuilder/web/static/js/plugins/status_poller.js b/src/autometabuilder/web/static/js/plugins/status_poller.js new file mode 100644 index 0000000..11c12e1 --- /dev/null +++ b/src/autometabuilder/web/static/js/plugins/status_poller.js @@ -0,0 +1,86 @@ +/** + * AutoMetabuilder - Status Poller + */ +(() => { + const t = (key, fallback = '') => window.AMBContext?.t?.(key, fallback) || fallback || key; + const headers = () => window.AMBContext?.authHeaders || {}; + + const StatusPoller = { + logsInterval: null, + statusInterval: null, + + init() { + this.refreshLogs(); + this.refreshStatus(); + this.logsInterval = setInterval(() => this.refreshLogs(), 2000); + this.statusInterval = setInterval(() => this.refreshStatus(), 2000); + }, + + async refreshLogs() { + try { + const response = await fetch('/api/logs', { + credentials: 'include', + headers: headers() + }); + const data = await response.json(); + const logsPre = document.getElementById('logs'); + if (!logsPre) return; + + const wasAtBottom = logsPre.scrollHeight - logsPre.clientHeight <= logsPre.scrollTop + 1; + logsPre.textContent = data.logs; + if (wasAtBottom) { + logsPre.scrollTop = logsPre.scrollHeight; + } + } catch (error) { + console.error('Error fetching logs:', error); + } + }, + + async refreshStatus() { + try { + const response = await fetch('/api/status', { + credentials: 'include', + headers: headers() + }); + const data = await response.json(); + + const statusIndicator = document.getElementById('status-indicator'); + if (statusIndicator) { + if (data.is_running) { + statusIndicator.className = 'amb-status amb-status-running'; + statusIndicator.innerHTML = ` ${t('ui.dashboard.status.running', 'Running')}`; + } else { + statusIndicator.className = 'amb-status amb-status-idle'; + statusIndicator.innerHTML = ` ${t('ui.dashboard.status.idle', 'Idle')}`; + } + } + + const mvpBadge = document.getElementById('mvp-badge'); + if (mvpBadge) { + if (data.mvp_reached) { + mvpBadge.className = 'badge bg-primary'; + mvpBadge.innerHTML = ` ${t('ui.dashboard.status.mvp_reached', 'Reached')}`; + } else { + mvpBadge.className = 'badge bg-secondary'; + mvpBadge.innerHTML = ` ${t('ui.dashboard.status.mvp_progress', 'In Progress')}`; + } + } + + const runBtn = document.getElementById('run-btn'); + if (runBtn) { + runBtn.disabled = data.is_running; + } + + const progressBar = document.getElementById('status-progress'); + if (progressBar) { + progressBar.style.display = data.is_running ? 'block' : 'none'; + } + } catch (error) { + console.error('Error fetching status:', error); + } + } + }; + + window.StatusPoller = StatusPoller; + window.AMBPlugins?.register('status_poller', async () => StatusPoller.init()); +})(); diff --git a/src/autometabuilder/web/static/js/services/toast.js b/src/autometabuilder/web/static/js/services/toast.js new file mode 100644 index 0000000..e77d396 --- /dev/null +++ b/src/autometabuilder/web/static/js/services/toast.js @@ -0,0 +1,37 @@ +/** + * AutoMetabuilder - Toast Notifications + */ +(() => { + const Toast = { + show(message, type = 'info') { + const container = document.getElementById('toast-container') || this.createContainer(); + const toast = document.createElement('div'); + toast.className = `toast align-items-center text-bg-${type} border-0 show`; + toast.setAttribute('role', 'alert'); + toast.innerHTML = ` +
+
${message}
+ +
+ `; + container.appendChild(toast); + + setTimeout(() => { + toast.remove(); + }, 5000); + + toast.querySelector('.btn-close').addEventListener('click', () => toast.remove()); + }, + + createContainer() { + const container = document.createElement('div'); + container.id = 'toast-container'; + container.className = 'toast-container position-fixed bottom-0 end-0 p-3'; + container.style.zIndex = '1100'; + document.body.appendChild(container); + return container; + } + }; + + window.Toast = Toast; +})();