Added single-account execution functionality and historical data loading for charts

This commit is contained in:
2025-12-22 21:55:30 +01:00
parent 5b27d11c0d
commit 00f539ae04
4 changed files with 215 additions and 2 deletions

View File

@@ -220,6 +220,88 @@ function setChartPeriod(period, btn) {
function loadInitialData() {
fetch('/api/status').then((r) => { return r.json() }).then(updateBotStatus).catch(() => { })
fetch('/api/accounts').then((r) => { return r.json() }).then(renderAccounts).catch(() => { })
loadAccountHistoryData() // FIXED: Load real historical data for charts
}
// FIXED: Load account history from API to populate charts with real data
function loadAccountHistoryData() {
fetch('/api/account-history')
.then((r) => r.json())
.then((histories) => {
if (histories && Object.keys(histories).length > 0) {
updateChartsWithRealData(histories)
}
})
.catch((e) => {
console.warn('[History] Failed to load:', e)
})
}
// FIXED: Update charts with real historical data
function updateChartsWithRealData(histories) {
var last7Days = {}
var activityCounts = {
'Desktop Search': 0,
'Mobile Search': 0,
'Daily Set': 0,
'Quizzes': 0,
'Other': 0
}
// Process each account's history
Object.values(histories).forEach((accountHistory) => {
if (!accountHistory.history) return
accountHistory.history.forEach((entry) => {
var date = entry.date || entry.timestamp.split('T')[0]
// Aggregate points by day
if (!last7Days[date]) {
last7Days[date] = 0
}
last7Days[date] += entry.totalPoints || 0
// Count activity types
if (entry.completedActivities) {
entry.completedActivities.forEach((activity) => {
if (activity.includes('Search')) {
if (entry.desktopPoints > 0) activityCounts['Desktop Search']++
if (entry.mobilePoints > 0) activityCounts['Mobile Search']++
} else if (activity.includes('DailySet')) {
activityCounts['Daily Set']++
} else if (activity.includes('Quiz') || activity.includes('Poll')) {
activityCounts['Quizzes']++
} else {
activityCounts['Other']++
}
})
}
})
})
// Update points chart with last 7 days
if (pointsChart) {
var sortedDates = Object.keys(last7Days).sort().slice(-7)
var labels = sortedDates.map((d) => {
var date = new Date(d)
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
})
var data = sortedDates.map((d) => { return last7Days[d] })
pointsChart.data.labels = labels.length > 0 ? labels : generateDateLabels(7)
pointsChart.data.datasets[0].data = data.length > 0 ? data : generatePlaceholderData(7)
pointsChart.update()
}
// Update activity chart
if (activityChart) {
var total = Object.values(activityCounts).reduce((sum, val) => { return sum + val }, 0)
if (total > 0) {
activityChart.data.labels = Object.keys(activityCounts)
activityChart.data.datasets[0].data = Object.values(activityCounts)
activityChart.update()
}
}
}
function refreshData() {
@@ -531,8 +613,36 @@ function runSingleAccount() {
function executeSingleAccount() {
var select = document.getElementById('singleAccountSelect')
if (!select) return
var email = select.value
closeModal()
showToast('Running account: ' + maskEmail(select.value), 'info')
if (!email) {
showToast('No account selected', 'error')
return
}
showToast('Starting bot for: ' + maskEmail(email), 'info')
// Call API to run single account
fetch('/api/run-single', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: email })
})
.then((res) => res.json())
.then((data) => {
if (data.success) {
showToast('✓ Bot started for account: ' + maskEmail(email), 'success')
loadStatus()
} else {
showToast('✗ Failed to start: ' + (data.error || 'Unknown error'), 'error')
}
})
.catch((err) => {
console.error('[API] Run single failed:', err)
showToast('✗ Request failed: ' + err.message, 'error')
})
}
function exportLogs() {

View File

@@ -88,7 +88,7 @@
},
// === BROWSER ===
"browser": {
"headless": true,
"headless": false,
"globalTimeout": "30s"
},
"fingerprinting": {

View File

@@ -106,6 +106,81 @@ export class BotController {
return await this.start()
}
/**
* Run a single account (for dashboard "run single" feature)
* FIXED: Actually implement single account execution
*/
public async runSingle(email: string): Promise<{ success: boolean; error?: string; pid?: number }> {
if (this.botInstance) {
return { success: false, error: 'Bot is already running. Stop it first.' }
}
if (this.isStarting) {
return { success: false, error: 'Bot is currently starting, please wait' }
}
try {
this.isStarting = true
this.log(`🚀 Starting bot for single account: ${email}`, 'log')
const { MicrosoftRewardsBot } = await import('../index')
const { loadAccounts } = await import('../util/state/Load')
// Load all accounts and filter to just this one
const allAccounts = loadAccounts()
const targetAccount = allAccounts.find(a => a.email === email)
if (!targetAccount) {
return { success: false, error: `Account ${email} not found in accounts.jsonc` }
}
this.botInstance = new MicrosoftRewardsBot(false)
this.startTime = new Date()
dashboardState.setRunning(true)
dashboardState.setBotInstance(this.botInstance)
// Update account status
dashboardState.updateAccount(email, { status: 'running', errors: [] })
// Run bot asynchronously with single account
void (async () => {
try {
this.log(`✓ Bot initialized for ${email}, starting execution...`, 'log')
await this.botInstance!.initialize()
// Override accounts to run only this one
; (this.botInstance as any).accounts = [targetAccount]
await this.botInstance!.run()
this.log(`✓ Bot completed successfully for ${email}`, 'log')
dashboardState.updateAccount(email, { status: 'completed' })
} catch (error) {
const errMsg = error instanceof Error ? error.message : String(error)
this.log(`Bot error for ${email}: ${errMsg}`, 'error')
dashboardState.updateAccount(email, {
status: 'error',
errors: [errMsg]
})
} finally {
this.cleanup()
}
})()
return { success: true, pid: process.pid }
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error)
this.log(`Failed to start bot for ${email}: ${errorMsg}`, 'error')
dashboardState.updateAccount(email, { status: 'error', errors: [errorMsg] })
this.cleanup()
return { success: false, error: errorMsg }
} finally {
this.isStarting = false
}
}
private async wait(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}

View File

@@ -199,6 +199,34 @@ apiRouter.post('/restart', async (_req: Request, res: Response): Promise<void> =
}
})
// POST /api/run-single - Run a single account (dashboard feature)
apiRouter.post('/run-single', async (req: Request, res: Response): Promise<void> => {
try {
const { email } = req.body
if (!email) {
sendError(res, 400, 'Email is required')
return
}
const status = botController.getStatus()
if (status.running) {
sendError(res, 400, `Bot already running (PID: ${status.pid}). Stop it first.`)
return
}
const result = await botController.runSingle(email)
if (result.success) {
sendSuccess(res, { message: `Started bot for account ${email}`, pid: result.pid })
} else {
sendError(res, 500, result.error || 'Failed to start bot for account')
}
} catch (error) {
sendError(res, 500, getErr(error))
}
})
// GET /api/metrics - Basic metrics
apiRouter.get('/metrics', (_req: Request, res: Response) => {
try {