Spaces:
Sleeping
Sleeping
| const elements = { | |
| startBtn: document.getElementById('startBtn'), | |
| stopBtn: document.getElementById('stopBtn'), | |
| clearLogsBtn: document.getElementById('clearLogsBtn'), | |
| errorBanner: document.getElementById('errorBanner'), | |
| successBanner: document.getElementById('successBanner'), | |
| connectionStatus: document.getElementById('connectionStatus'), | |
| currentState: document.getElementById('currentState'), | |
| totalItems: document.getElementById('totalItems'), | |
| currentPage: document.getElementById('currentPage'), | |
| lastRunTime: document.getElementById('lastRunTime'), | |
| progressBar: document.getElementById('progressBar'), | |
| progressLabel: document.getElementById('progressLabel'), | |
| csvLabel: document.getElementById('csvLabel'), | |
| logOutput: document.getElementById('logOutput'), | |
| }; | |
| let logCursor = 0; | |
| let pollTimer = null; | |
| let running = false; | |
| let screenshotTimer = null; | |
| function setBanner(element, message) { | |
| if (!message) { | |
| element.classList.add('hidden'); | |
| element.textContent = ''; | |
| return; | |
| } | |
| element.textContent = message; | |
| element.classList.remove('hidden'); | |
| } | |
| function showError(message) { | |
| setBanner(elements.errorBanner, message); | |
| setBanner(elements.successBanner, ''); | |
| } | |
| function showSuccess(message) { | |
| setBanner(elements.successBanner, message); | |
| setBanner(elements.errorBanner, ''); | |
| } | |
| function clearBanners() { | |
| setBanner(elements.errorBanner, ''); | |
| setBanner(elements.successBanner, ''); | |
| } | |
| function setRunningState(isRunning) { | |
| running = isRunning; | |
| elements.startBtn.disabled = isRunning; | |
| elements.stopBtn.disabled = !isRunning; | |
| elements.connectionStatus.textContent = isRunning ? 'Running' : 'Ready'; | |
| if (isRunning) { | |
| elements.connectionStatus.parentElement.querySelector('.dot').style.background = '#f59e0b'; | |
| } else { | |
| elements.connectionStatus.parentElement.querySelector('.dot').style.background = '#22c55e'; | |
| } | |
| } | |
| function updateStatusPanel(data) { | |
| elements.currentState.textContent = data.current_state || 'Idle'; | |
| elements.totalItems.textContent = String(data.total_items_found ?? 0); | |
| elements.currentPage.textContent = String(data.current_page ?? 0); | |
| elements.lastRunTime.textContent = data.last_run_time || '-'; | |
| const progress = Number(data.progress || 0); | |
| elements.progressBar.style.width = `${Math.max(0, Math.min(100, progress))}%`; | |
| elements.progressLabel.textContent = `${Math.max(0, Math.min(100, progress))}%`; | |
| if (data.last_error) { | |
| showError(data.last_error); | |
| } | |
| } | |
| function appendLogs(logs) { | |
| if (!Array.isArray(logs) || logs.length <= logCursor) { | |
| return; | |
| } | |
| const newLogs = logs.slice(logCursor); | |
| if (!newLogs.length) { | |
| return; | |
| } | |
| const current = elements.logOutput.textContent === 'Waiting for the next run...' ? '' : elements.logOutput.textContent; | |
| const merged = [current.trimEnd(), ...newLogs].filter(Boolean).join('\n'); | |
| elements.logOutput.textContent = merged; | |
| elements.logOutput.scrollTop = elements.logOutput.scrollHeight; | |
| logCursor = logs.length; | |
| } | |
| async function fetchStatus() { | |
| try { | |
| const response = await fetch('/status', { cache: 'no-store' }); | |
| const data = await response.json(); | |
| setRunningState(Boolean(data.running)); | |
| updateStatusPanel(data); | |
| appendLogs(data.logs || []); | |
| if (!data.running && pollTimer) { | |
| window.clearInterval(pollTimer); | |
| pollTimer = null; | |
| } | |
| } catch (error) { | |
| console.error(error); | |
| } | |
| } | |
| async function startBot() { | |
| clearBanners(); | |
| try { | |
| const response = await fetch('/start', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({}), | |
| }); | |
| const data = await response.json(); | |
| if (!response.ok || !data.ok) { | |
| showError(data.error || 'Failed to start automation.'); | |
| return; | |
| } | |
| showSuccess('Automation started.'); | |
| logCursor = 0; | |
| elements.logOutput.textContent = 'Waiting for the next run...'; | |
| setRunningState(true); | |
| if (!pollTimer) { | |
| pollTimer = window.setInterval(fetchStatus, 2000); | |
| } | |
| await fetchStatus(); | |
| } catch (error) { | |
| showError(error.message || 'Unable to start the bot.'); | |
| } | |
| } | |
| async function stopBot() { | |
| try { | |
| const response = await fetch('/stop', { method: 'POST' }); | |
| const data = await response.json(); | |
| showSuccess(data.message || 'Stop requested.'); | |
| await fetchStatus(); | |
| } catch (error) { | |
| showError(error.message || 'Unable to stop the bot.'); | |
| } | |
| } | |
| function clearLogs() { | |
| logCursor = 0; | |
| elements.logOutput.textContent = 'Waiting for the next run...'; | |
| } | |
| function boot() { | |
| elements.startBtn.addEventListener('click', startBot); | |
| elements.stopBtn.addEventListener('click', stopBot); | |
| elements.clearLogsBtn.addEventListener('click', clearLogs); | |
| setRunningState(false); | |
| fetchStatus(); | |
| pollTimer = window.setInterval(fetchStatus, 2000); | |
| const img = document.getElementById('screenshotImg'); | |
| if (img) { | |
| const refresh = () => { | |
| img.src = '/screenshot?ts=' + Date.now(); | |
| }; | |
| screenshotTimer = window.setInterval(refresh, 3000); | |
| refresh(); | |
| } | |
| } | |
| document.addEventListener('DOMContentLoaded', boot); | |