mirror of
https://github.com/Crazyco-xyz/48hr.email.git
synced 2026-01-09 11:19:36 +01:00
[Chore]: Misc. V2 patches
This commit is contained in:
parent
2f58eacfa7
commit
c56ec92ce5
7 changed files with 62 additions and 31 deletions
BIN
.github/assets/stats.png
vendored
Normal file
BIN
.github/assets/stats.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 250 KiB |
|
|
@ -42,9 +42,9 @@ All data is being removed 48hrs after they have reached the mail server.
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
| Homepage | Account Panel |
|
| 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/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 |
|
| Inbox | Email using HTML and CSS | Attachments and Cryptographic Keys view |
|
||||||
|:---:|:---:|:---:|
|
|:---:|:---:|:---:|
|
||||||
|
|
|
||||||
10
app.js
10
app.js
|
|
@ -5,7 +5,7 @@
|
||||||
const config = require('./application/config')
|
const config = require('./application/config')
|
||||||
const debug = require('debug')('48hr-email:app')
|
const debug = require('debug')('48hr-email:app')
|
||||||
const Helper = require('./application/helper')
|
const Helper = require('./application/helper')
|
||||||
|
const helper = new(Helper)
|
||||||
const { app, io, server } = require('./infrastructure/web/web')
|
const { app, io, server } = require('./infrastructure/web/web')
|
||||||
const ClientNotification = require('./infrastructure/web/client-notification')
|
const ClientNotification = require('./infrastructure/web/client-notification')
|
||||||
const ImapService = require('./application/imap-service')
|
const ImapService = require('./application/imap-service')
|
||||||
|
|
@ -95,10 +95,14 @@ const mailProcessingService = new MailProcessingService(
|
||||||
debug('Mail processing service initialized')
|
debug('Mail processing service initialized')
|
||||||
|
|
||||||
// Initialize statistics with current count
|
// Initialize statistics with current count
|
||||||
imapService.on(ImapService.EVENT_INITIAL_LOAD_DONE, () => {
|
imapService.on(ImapService.EVENT_INITIAL_LOAD_DONE, async() => {
|
||||||
const count = mailProcessingService.getCount()
|
const count = mailProcessingService.getCount()
|
||||||
statisticsStore.initialize(count)
|
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
|
// Set up timer sync broadcasting after IMAP is ready
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,8 @@ class Helper {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLargestUid(imapService) {
|
async getLargestUid(imapService) {
|
||||||
return await imapService.getLargestUid();
|
const uid = await imapService.getLargestUid();
|
||||||
|
return uid || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,15 @@ class StatisticsStore {
|
||||||
constructor() {
|
constructor() {
|
||||||
// Current totals
|
// Current totals
|
||||||
this.currentCount = 0
|
this.currentCount = 0
|
||||||
this.historicalTotal = 0
|
this.largestUid = 0
|
||||||
|
|
||||||
// 24-hour rolling data (one entry per minute = 1440 entries)
|
// 24-hour rolling data (one entry per minute = 1440 entries)
|
||||||
this.hourlyData = []
|
this.hourlyData = []
|
||||||
this.maxDataPoints = 24 * 60 // 24 hours * 60 minutes
|
this.maxDataPoints = 24 * 60 // 24 hours * 60 minutes
|
||||||
|
|
||||||
// Track last cleanup to avoid too frequent operations
|
// Track last cleanup to avoid too frequent operations
|
||||||
this.lastCleanup = Date.now()
|
this.lastCleanup = Date.now()
|
||||||
|
|
||||||
debug('Statistics store initialized')
|
debug('Statistics store initialized')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,18 +26,27 @@ class StatisticsStore {
|
||||||
*/
|
*/
|
||||||
initialize(count) {
|
initialize(count) {
|
||||||
this.currentCount = count
|
this.currentCount = count
|
||||||
this.historicalTotal = count
|
|
||||||
debug(`Initialized with ${count} emails`)
|
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
|
* Record an email received event
|
||||||
*/
|
*/
|
||||||
recordReceive() {
|
recordReceive() {
|
||||||
this.currentCount++
|
this.currentCount++
|
||||||
this.historicalTotal++
|
this._addDataPoint('receive')
|
||||||
this._addDataPoint('receive')
|
debug(`Email received. Current: ${this.currentCount}`)
|
||||||
debug(`Email received. Current: ${this.currentCount}, Historical: ${this.historicalTotal}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -79,12 +88,12 @@ class StatisticsStore {
|
||||||
*/
|
*/
|
||||||
getStats() {
|
getStats() {
|
||||||
this._cleanup()
|
this._cleanup()
|
||||||
|
|
||||||
const last24h = this._getLast24Hours()
|
const last24h = this._getLast24Hours()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentCount: this.currentCount,
|
currentCount: this.currentCount,
|
||||||
historicalTotal: this.historicalTotal,
|
allTimeTotal: this.largestUid,
|
||||||
last24Hours: {
|
last24Hours: {
|
||||||
receives: last24h.receives,
|
receives: last24h.receives,
|
||||||
deletes: last24h.deletes,
|
deletes: last24h.deletes,
|
||||||
|
|
@ -102,7 +111,7 @@ class StatisticsStore {
|
||||||
_addDataPoint(type) {
|
_addDataPoint(type) {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const minute = Math.floor(now / 60000) * 60000 // Round to minute
|
const minute = Math.floor(now / 60000) * 60000 // Round to minute
|
||||||
|
|
||||||
// Find or create entry for this minute
|
// Find or create entry for this minute
|
||||||
let entry = this.hourlyData.find(e => e.timestamp === minute)
|
let entry = this.hourlyData.find(e => e.timestamp === minute)
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
|
|
@ -114,10 +123,10 @@ class StatisticsStore {
|
||||||
}
|
}
|
||||||
this.hourlyData.push(entry)
|
this.hourlyData.push(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
entry[type + 's']++
|
entry[type + 's']++
|
||||||
|
|
||||||
this._cleanup()
|
this._cleanup()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -126,20 +135,20 @@ class StatisticsStore {
|
||||||
*/
|
*/
|
||||||
_cleanup() {
|
_cleanup() {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
|
|
||||||
// Only cleanup every 5 minutes to avoid constant filtering
|
// Only cleanup every 5 minutes to avoid constant filtering
|
||||||
if (now - this.lastCleanup < 5 * 60 * 1000) {
|
if (now - this.lastCleanup < 5 * 60 * 1000) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const cutoff = now - (24 * 60 * 60 * 1000)
|
const cutoff = now - (24 * 60 * 60 * 1000)
|
||||||
const beforeCount = this.hourlyData.length
|
const beforeCount = this.hourlyData.length
|
||||||
this.hourlyData = this.hourlyData.filter(entry => entry.timestamp >= cutoff)
|
this.hourlyData = this.hourlyData.filter(entry => entry.timestamp >= cutoff)
|
||||||
|
|
||||||
if (beforeCount !== this.hourlyData.length) {
|
if (beforeCount !== this.hourlyData.length) {
|
||||||
debug(`Cleaned up ${beforeCount - this.hourlyData.length} old data points`)
|
debug(`Cleaned up ${beforeCount - this.hourlyData.length} old data points`)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lastCleanup = now
|
this.lastCleanup = now
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,7 +160,7 @@ class StatisticsStore {
|
||||||
_getLast24Hours() {
|
_getLast24Hours() {
|
||||||
const cutoff = Date.now() - (24 * 60 * 60 * 1000)
|
const cutoff = Date.now() - (24 * 60 * 60 * 1000)
|
||||||
const recent = this.hourlyData.filter(e => e.timestamp >= cutoff)
|
const recent = this.hourlyData.filter(e => e.timestamp >= cutoff)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
receives: recent.reduce((sum, e) => sum + e.receives, 0),
|
receives: recent.reduce((sum, e) => sum + e.receives, 0),
|
||||||
deletes: recent.reduce((sum, e) => sum + e.deletes, 0),
|
deletes: recent.reduce((sum, e) => sum + e.deletes, 0),
|
||||||
|
|
@ -168,7 +177,7 @@ class StatisticsStore {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const cutoff = now - (24 * 60 * 60 * 1000)
|
const cutoff = now - (24 * 60 * 60 * 1000)
|
||||||
const hourly = {}
|
const hourly = {}
|
||||||
|
|
||||||
// Aggregate by hour
|
// Aggregate by hour
|
||||||
this.hourlyData
|
this.hourlyData
|
||||||
.filter(e => e.timestamp >= cutoff)
|
.filter(e => e.timestamp >= cutoff)
|
||||||
|
|
@ -181,7 +190,7 @@ class StatisticsStore {
|
||||||
hourly[hour].deletes += entry.deletes
|
hourly[hour].deletes += entry.deletes
|
||||||
hourly[hour].forwards += entry.forwards
|
hourly[hour].forwards += entry.forwards
|
||||||
})
|
})
|
||||||
|
|
||||||
// Convert to sorted array
|
// Convert to sorted array
|
||||||
return Object.values(hourly).sort((a, b) => a.timestamp - b.timestamp)
|
return Object.values(hourly).sort((a, b) => a.timestamp - b.timestamp)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,20 @@ router.get('/', async(req, res) => {
|
||||||
try {
|
try {
|
||||||
const config = req.app.get('config')
|
const config = req.app.get('config')
|
||||||
const statisticsStore = req.app.get('statisticsStore')
|
const statisticsStore = req.app.get('statisticsStore')
|
||||||
|
const imapService = req.app.get('imapService')
|
||||||
const Helper = require('../../../application/helper')
|
const Helper = require('../../../application/helper')
|
||||||
const helper = new 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 stats = statisticsStore.getStats()
|
||||||
const purgeTime = helper.purgeTimeElemetBuilder()
|
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', {
|
res.render('stats', {
|
||||||
title: `Statistics | ${config.http.branding[0]}`,
|
title: `Statistics | ${config.http.branding[0]}`,
|
||||||
|
|
@ -34,6 +41,16 @@ router.get('/', async(req, res) => {
|
||||||
router.get('/api', async(req, res) => {
|
router.get('/api', async(req, res) => {
|
||||||
try {
|
try {
|
||||||
const statisticsStore = req.app.get('statisticsStore')
|
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 stats = statisticsStore.getStats()
|
||||||
|
|
||||||
res.json(stats)
|
res.json(stats)
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@
|
||||||
|
|
||||||
<!-- Historical Total -->
|
<!-- Historical Total -->
|
||||||
<div class="stat-card">
|
<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 class="stat-label">All Time Total</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue