[Chore]: Misc. V2 patches

This commit is contained in:
ClaraCrazy 2026-01-03 17:00:39 +01:00
parent 2f58eacfa7
commit c56ec92ce5
No known key found for this signature in database
GPG key ID: EBBC896ACB497011
7 changed files with 62 additions and 31 deletions

BIN
.github/assets/stats.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

View file

@ -42,9 +42,9 @@ All data is being removed 48hrs after they have reached the mail server.
## Screenshots
| Homepage | Account Panel |
|:---:|:---:|
| <img src=".github/assets/home.png" width="500px" height="300px" style="object-fit: fit;"> | <img src=".github/assets/account.png" width="500px" height="300px" style="object-fit: fit;"> |
| Homepage | Account Panel | Stats Page |
|:---:|:---:|:---:|
| <img src=".github/assets/home.png" width="500px" height="300px" style="object-fit: fit;"> | <img src=".github/assets/account.png" width="500px" height="300px" style="object-fit: fit;"> | <img src=".github/assets/stats.png" width="500px" height="300px" style="object-fit: fit;"> |
| Inbox | Email using HTML and CSS | Attachments and Cryptographic Keys view |
|:---:|:---:|:---:|

10
app.js
View file

@ -5,7 +5,7 @@
const config = require('./application/config')
const debug = require('debug')('48hr-email:app')
const Helper = require('./application/helper')
const helper = new(Helper)
const { app, io, server } = require('./infrastructure/web/web')
const ClientNotification = require('./infrastructure/web/client-notification')
const ImapService = require('./application/imap-service')
@ -95,10 +95,14 @@ const mailProcessingService = new MailProcessingService(
debug('Mail processing service initialized')
// Initialize statistics with current count
imapService.on(ImapService.EVENT_INITIAL_LOAD_DONE, () => {
imapService.on(ImapService.EVENT_INITIAL_LOAD_DONE, async() => {
const count = mailProcessingService.getCount()
statisticsStore.initialize(count)
debug(`Statistics initialized with ${count} emails`)
// Get and set the largest UID for all-time total
const largestUid = await helper.getLargestUid(imapService)
statisticsStore.updateLargestUid(largestUid)
debug(`Statistics initialized with ${count} emails, largest UID: ${largestUid}`)
})
// Set up timer sync broadcasting after IMAP is ready

View file

@ -172,7 +172,8 @@ class Helper {
}
async getLargestUid(imapService) {
return await imapService.getLargestUid();
const uid = await imapService.getLargestUid();
return uid || 0;
}
/**

View file

@ -8,15 +8,15 @@ class StatisticsStore {
constructor() {
// Current totals
this.currentCount = 0
this.historicalTotal = 0
this.largestUid = 0
// 24-hour rolling data (one entry per minute = 1440 entries)
this.hourlyData = []
this.maxDataPoints = 24 * 60 // 24 hours * 60 minutes
// Track last cleanup to avoid too frequent operations
this.lastCleanup = Date.now()
debug('Statistics store initialized')
}
@ -26,18 +26,27 @@ class StatisticsStore {
*/
initialize(count) {
this.currentCount = count
this.historicalTotal = count
debug(`Initialized with ${count} emails`)
}
/**
* Update largest UID (all-time total emails processed)
* @param {number} uid - Largest UID from mailbox (0 if no emails)
*/
updateLargestUid(uid) {
if (uid >= 0 && uid > this.largestUid) {
this.largestUid = uid
debug(`Largest UID updated to ${uid}`)
}
}
/**
* Record an email received event
*/
recordReceive() {
this.currentCount++
this.historicalTotal++
this._addDataPoint('receive')
debug(`Email received. Current: ${this.currentCount}, Historical: ${this.historicalTotal}`)
this._addDataPoint('receive')
debug(`Email received. Current: ${this.currentCount}`)
}
/**
@ -79,12 +88,12 @@ class StatisticsStore {
*/
getStats() {
this._cleanup()
const last24h = this._getLast24Hours()
return {
currentCount: this.currentCount,
historicalTotal: this.historicalTotal,
allTimeTotal: this.largestUid,
last24Hours: {
receives: last24h.receives,
deletes: last24h.deletes,
@ -102,7 +111,7 @@ class StatisticsStore {
_addDataPoint(type) {
const now = Date.now()
const minute = Math.floor(now / 60000) * 60000 // Round to minute
// Find or create entry for this minute
let entry = this.hourlyData.find(e => e.timestamp === minute)
if (!entry) {
@ -114,10 +123,10 @@ class StatisticsStore {
}
this.hourlyData.push(entry)
}
entry[type + 's']++
this._cleanup()
this._cleanup()
}
/**
@ -126,20 +135,20 @@ class StatisticsStore {
*/
_cleanup() {
const now = Date.now()
// Only cleanup every 5 minutes to avoid constant filtering
if (now - this.lastCleanup < 5 * 60 * 1000) {
return
}
const cutoff = now - (24 * 60 * 60 * 1000)
const beforeCount = this.hourlyData.length
this.hourlyData = this.hourlyData.filter(entry => entry.timestamp >= cutoff)
if (beforeCount !== this.hourlyData.length) {
debug(`Cleaned up ${beforeCount - this.hourlyData.length} old data points`)
}
this.lastCleanup = now
}
@ -151,7 +160,7 @@ class StatisticsStore {
_getLast24Hours() {
const cutoff = Date.now() - (24 * 60 * 60 * 1000)
const recent = this.hourlyData.filter(e => e.timestamp >= cutoff)
return {
receives: recent.reduce((sum, e) => sum + e.receives, 0),
deletes: recent.reduce((sum, e) => sum + e.deletes, 0),
@ -168,7 +177,7 @@ class StatisticsStore {
const now = Date.now()
const cutoff = now - (24 * 60 * 60 * 1000)
const hourly = {}
// Aggregate by hour
this.hourlyData
.filter(e => e.timestamp >= cutoff)
@ -181,7 +190,7 @@ class StatisticsStore {
hourly[hour].deletes += entry.deletes
hourly[hour].forwards += entry.forwards
})
// Convert to sorted array
return Object.values(hourly).sort((a, b) => a.timestamp - b.timestamp)
}

View file

@ -7,13 +7,20 @@ router.get('/', async(req, res) => {
try {
const config = req.app.get('config')
const statisticsStore = req.app.get('statisticsStore')
const imapService = req.app.get('imapService')
const Helper = require('../../../application/helper')
const helper = new Helper()
// Update largest UID before getting stats (if IMAP is ready)
if (imapService) {
const largestUid = await helper.getLargestUid(imapService)
statisticsStore.updateLargestUid(largestUid)
}
const stats = statisticsStore.getStats()
const purgeTime = helper.purgeTimeElemetBuilder()
debug(`Stats page requested: ${stats.currentCount} current, ${stats.historicalTotal} historical`)
debug(`Stats page requested: ${stats.currentCount} current, ${stats.allTimeTotal} all-time total`)
res.render('stats', {
title: `Statistics | ${config.http.branding[0]}`,
@ -34,6 +41,16 @@ router.get('/', async(req, res) => {
router.get('/api', async(req, res) => {
try {
const statisticsStore = req.app.get('statisticsStore')
const imapService = req.app.get('imapService')
const Helper = require('../../../application/helper')
const helper = new Helper()
// Update largest UID before getting stats (if IMAP is ready)
if (imapService) {
const largestUid = await helper.getLargestUid(imapService)
statisticsStore.updateLargestUid(largestUid)
}
const stats = statisticsStore.getStats()
res.json(stats)

View file

@ -45,7 +45,7 @@
<!-- Historical Total -->
<div class="stat-card">
<div class="stat-value" id="historicalTotal">{{ stats.historicalTotal }}</div>
<div class="stat-value" id="historicalTotal">{{ stats.allTimeTotal }}</div>
<div class="stat-label">All Time Total</div>
</div>