Files
Microsoft-Rewards-Bot/public/app.js
LightZirconite 4c5d5ef9a8 feat: Implement main application logic and UI styling
- Added app.js to manage global state, WebSocket connections, and API interactions.
- Implemented functions for theme toggling, toast notifications, and updating UI elements.
- Created functions to fetch and display account metrics, logs, and status updates.
- Added event handlers for starting, stopping, and restarting the bot.
- Introduced WebSocket handling for real-time updates.
- Added style.css for consistent theming and responsive design across the application.
- Included styles for various UI components such as buttons, cards, logs, and empty states.
- Implemented light theme support with appropriate styles.
2025-11-08 23:56:21 +01:00

274 lines
7.5 KiB
JavaScript

// Global state
let ws = null
let logs = []
let accounts = []
let status = { running: false }
// Theme
function toggleTheme() {
document.body.classList.toggle('light-theme')
const icon = document.querySelector('.theme-toggle i')
if (document.body.classList.contains('light-theme')) {
icon.className = 'fas fa-sun'
localStorage.setItem('theme', 'light')
} else {
icon.className = 'fas fa-moon'
localStorage.setItem('theme', 'dark')
}
}
// Load theme
const savedTheme = localStorage.getItem('theme')
if (savedTheme === 'light') {
document.body.classList.add('light-theme')
document.querySelector('.theme-toggle i').className = 'fas fa-sun'
}
// Toast notification
function showToast(message, type = 'success') {
const container = document.getElementById('toastContainer')
const toast = document.createElement('div')
toast.className = `toast toast-${type}`
const iconMap = {
success: 'fa-check-circle',
error: 'fa-exclamation-circle',
info: 'fa-info-circle'
}
toast.innerHTML = `
<i class="fas ${iconMap[type]}"></i>
<span>${message}</span>
`
container.appendChild(toast)
setTimeout(() => {
toast.remove()
}, 5000)
}
// Update UI
function updateStatus(data) {
status = data
const badge = document.getElementById('statusBadge')
const btnStart = document.getElementById('btnStart')
const btnStop = document.getElementById('btnStop')
if (data.running) {
badge.className = 'status-badge status-running'
badge.textContent = 'RUNNING'
btnStart.disabled = true
btnStop.disabled = false
} else {
badge.className = 'status-badge status-stopped'
badge.textContent = 'STOPPED'
btnStart.disabled = false
btnStop.disabled = true
}
}
function updateMetrics(data) {
document.getElementById('totalAccounts').textContent = data.totalAccounts || 0
document.getElementById('totalPoints').textContent = (data.totalPoints || 0).toLocaleString()
document.getElementById('completed').textContent = data.accountsCompleted || 0
document.getElementById('errors').textContent = data.accountsWithErrors || 0
}
function updateAccounts(data) {
accounts = data
const container = document.getElementById('accountsList')
if (data.length === 0) {
container.innerHTML = '<div class="empty-state"><i class="fas fa-inbox"></i><p>No accounts configured</p></div>'
return
}
container.innerHTML = data.map(acc => `
<div class="account-item">
<div class="account-info">
<div class="account-avatar">${acc.maskedEmail.charAt(0).toUpperCase()}</div>
<div class="account-details">
<div class="account-email">${acc.maskedEmail}</div>
<div class="account-status-text">
${acc.lastSync ? `Last sync: ${new Date(acc.lastSync).toLocaleString()}` : 'Never synced'}
</div>
</div>
</div>
<div class="account-stats">
<div class="account-points">
<div class="account-points-value">${acc.points !== undefined ? acc.points.toLocaleString() : 'N/A'}</div>
<div class="account-points-label">Points</div>
</div>
<span class="account-badge badge-${acc.status}">${acc.status.toUpperCase()}</span>
</div>
</div>
`).join('')
}
function addLog(log) {
logs.push(log)
if (logs.length > 200) {
logs.shift()
}
renderLogs()
}
function renderLogs() {
const container = document.getElementById('logsContainer')
if (logs.length === 0) {
container.innerHTML = '<div class="empty-state"><i class="fas fa-stream"></i><p>No logs yet...</p></div>'
return
}
container.innerHTML = logs.map(log => `
<div class="log-entry log-level-${log.level}">
<span class="log-timestamp">[${new Date(log.timestamp).toLocaleTimeString()}]</span>
<span class="log-platform platform-${log.platform}">${log.platform}</span>
<span class="log-title">[${log.title}]</span>
<span>${log.message}</span>
</div>
`).join('')
// Auto-scroll to bottom
container.scrollTop = container.scrollHeight
}
// API calls
async function fetchData() {
try {
const [statusRes, accountsRes, metricsRes, logsRes] = await Promise.all([
fetch('/api/status'),
fetch('/api/accounts'),
fetch('/api/metrics'),
fetch('/api/logs?limit=100')
])
updateStatus(await statusRes.json())
updateAccounts(await accountsRes.json())
updateMetrics(await metricsRes.json())
logs = await logsRes.json()
renderLogs()
} catch (error) {
showToast('Failed to fetch data: ' + error.message, 'error')
}
}
async function startBot() {
try {
showToast('Starting bot...', 'info')
const res = await fetch('/api/start', { method: 'POST' })
const data = await res.json()
if (data.success) {
showToast(`Bot started! (PID: ${data.pid})`)
setTimeout(fetchData, 1000)
} else {
showToast(data.error || 'Failed to start bot', 'error')
}
} catch (error) {
showToast('Failed to start bot: ' + error.message, 'error')
}
}
async function stopBot() {
try {
showToast('Stopping bot...', 'info')
const res = await fetch('/api/stop', { method: 'POST' })
const data = await res.json()
if (data.success) {
showToast('Bot stopped')
setTimeout(fetchData, 1000)
} else {
showToast(data.error || 'Failed to stop bot', 'error')
}
} catch (error) {
showToast('Failed to stop bot: ' + error.message, 'error')
}
}
async function restartBot() {
try {
showToast('Restarting bot...', 'info')
const res = await fetch('/api/restart', { method: 'POST' })
const data = await res.json()
if (data.success) {
showToast(`Bot restarted! (PID: ${data.pid})`)
setTimeout(fetchData, 1000)
} else {
showToast(data.error || 'Failed to restart bot', 'error')
}
} catch (error) {
showToast('Failed to restart bot: ' + error.message, 'error')
}
}
async function clearLogs() {
try {
await fetch('/api/logs', { method: 'DELETE' })
logs = []
renderLogs()
showToast('Logs cleared')
} catch (error) {
showToast('Failed to clear logs', 'error')
}
}
function refreshData() {
fetchData()
showToast('Data refreshed')
}
// WebSocket
function connectWebSocket() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
ws = new WebSocket(`${protocol}//${window.location.host}`)
ws.onopen = () => {
console.log('WebSocket connected')
}
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data)
if (data.type === 'init') {
logs = data.data.logs || []
renderLogs()
if (data.data.status) updateStatus(data.data.status)
if (data.data.accounts) updateAccounts(data.data.accounts)
} else if (data.type === 'log') {
if (data.log) addLog(data.log)
} else if (data.type === 'status') {
updateStatus(data.data)
} else if (data.type === 'accounts') {
updateAccounts(data.data)
} else if (data.type === 'account_update') {
const index = accounts.findIndex(acc => acc.email === data.data.email)
if (index >= 0) {
accounts[index] = data.data
} else {
accounts.push(data.data)
}
updateAccounts(accounts)
}
} catch (error) {
console.error('WebSocket message error:', error)
}
}
ws.onclose = () => {
console.log('WebSocket disconnected, reconnecting...')
setTimeout(connectWebSocket, 3000)
}
ws.onerror = (error) => {
console.error('WebSocket error:', error)
}
}
// Initialize
fetchData()
connectWebSocket()
setInterval(fetchData, 10000)