Files
Microsoft-Rewards-Bot/src/dashboard/server.ts
LightZirconite ae4e34cd66 Add initial HTML structure and styles for Microsoft Rewards Bot dashboard
- Created index.html with a complete layout for the dashboard
- Added favicon.ico placeholder
- Implemented responsive design and styling using CSS variables
- Integrated React for dynamic data handling and real-time updates
- Set up WebSocket connection for live log updates
- Included functionality for starting/stopping the bot and managing accounts
2025-11-03 23:07:10 +01:00

182 lines
5.4 KiB
TypeScript

import express from 'express'
import { createServer } from 'http'
import { WebSocketServer, WebSocket } from 'ws'
import path from 'path'
import fs from 'fs'
import { apiRouter } from './routes'
import { dashboardState, DashboardLog } from './state'
import { log as botLog } from '../util/Logger'
const PORT = process.env.DASHBOARD_PORT ? parseInt(process.env.DASHBOARD_PORT) : 3000
const HOST = process.env.DASHBOARD_HOST || '127.0.0.1'
export class DashboardServer {
private app: express.Application
private server: ReturnType<typeof createServer>
private wss: WebSocketServer
private clients: Set<WebSocket> = new Set()
constructor() {
this.app = express()
this.server = createServer(this.app)
this.wss = new WebSocketServer({ server: this.server })
this.setupMiddleware()
this.setupRoutes()
this.setupWebSocket()
this.interceptBotLogs()
this.setupStateListener()
}
private setupStateListener(): void {
// Listen to dashboard state changes and broadcast to all clients
dashboardState.addChangeListener((type, data) => {
this.broadcastUpdate(type, data)
})
}
private setupMiddleware(): void {
this.app.use(express.json())
this.app.use('/assets', express.static(path.join(__dirname, '../../assets')))
this.app.use(express.static(path.join(__dirname, '../../public')))
}
private setupRoutes(): void {
this.app.use('/api', apiRouter)
// Health check
this.app.get('/health', (_req, res) => {
res.json({ status: 'ok', uptime: process.uptime() })
})
// Serve dashboard UI (with fallback if file doesn't exist)
this.app.get('/', (_req, res) => {
const dashboardPath = path.join(__dirname, '../../public/dashboard.html')
const indexPath = path.join(__dirname, '../../public/index.html')
if (fs.existsSync(dashboardPath)) {
res.sendFile(dashboardPath)
} else if (fs.existsSync(indexPath)) {
res.sendFile(indexPath)
} else {
res.status(200).send(`
<!DOCTYPE html>
<html><head><title>Dashboard - API Only Mode</title></head>
<body style="font-family: sans-serif; padding: 40px; text-align: center;">
<h1>Dashboard API Active</h1>
<p>Frontend UI not found. API endpoints are available:</p>
<ul style="list-style: none; padding: 0;">
<li><a href="/api/status">GET /api/status</a></li>
<li><a href="/api/accounts">GET /api/accounts</a></li>
<li><a href="/api/logs">GET /api/logs</a></li>
<li><a href="/api/metrics">GET /api/metrics</a></li>
<li><a href="/health">GET /health</a></li>
</ul>
</body></html>
`)
}
})
}
private setupWebSocket(): void {
this.wss.on('connection', (ws: WebSocket) => {
this.clients.add(ws)
console.log('[Dashboard] WebSocket client connected')
ws.on('close', () => {
this.clients.delete(ws)
console.log('[Dashboard] WebSocket client disconnected')
})
ws.on('error', (error) => {
console.error('[Dashboard] WebSocket error:', error)
})
// Send initial data on connect
const recentLogs = dashboardState.getLogs(100)
const status = dashboardState.getStatus()
const accounts = dashboardState.getAccounts()
ws.send(JSON.stringify({
type: 'init',
data: {
logs: recentLogs,
status,
accounts
}
}))
})
}
private interceptBotLogs(): void {
// Store reference to this.clients for closure
const clients = this.clients
// Intercept bot logs and forward to dashboard
const originalLog = botLog
;(global as Record<string, unknown>).botLog = function(
isMobile: boolean | 'main',
title: string,
message: string,
type: 'log' | 'warn' | 'error' = 'log'
) {
const result = originalLog(isMobile, title, message, type)
const logEntry: DashboardLog = {
timestamp: new Date().toISOString(),
level: type,
platform: isMobile === 'main' ? 'MAIN' : isMobile ? 'MOBILE' : 'DESKTOP',
title,
message
}
dashboardState.addLog(logEntry)
// Broadcast to WebSocket clients
const payload = JSON.stringify({ type: 'log', log: logEntry })
for (const client of clients) {
if (client.readyState === WebSocket.OPEN) {
try {
client.send(payload)
} catch (error) {
console.error('[Dashboard] Error sending to WebSocket client:', error)
}
}
}
return result
}
}
public broadcastUpdate(type: string, data: unknown): void {
const payload = JSON.stringify({ type, data })
for (const client of this.clients) {
if (client.readyState === WebSocket.OPEN) {
try {
client.send(payload)
} catch (error) {
console.error('[Dashboard] Error broadcasting update:', error)
}
}
}
}
public start(): void {
this.server.listen(PORT, HOST, () => {
console.log(`[Dashboard] Server running on http://${HOST}:${PORT}`)
console.log('[Dashboard] WebSocket ready for live logs')
})
}
public stop(): void {
this.wss.close()
this.server.close()
console.log('[Dashboard] Server stopped')
}
}
export function startDashboardServer(): DashboardServer {
const server = new DashboardServer()
server.start()
return server
}