[Feat]: Show total and historical email count in UI

Enhances user interface by displaying both the current number of emails and the largest UID seen, offering better visibility into historical mailbox activity. Updates backend logic and view templates to support this change, and improves maintainability by centralizing count formatting.
This commit is contained in:
ClaraCrazy 2025-12-26 05:53:06 +01:00
parent 1cf35f76f0
commit 83a4fac4ab
No known key found for this signature in database
GPG key ID: EBBC896ACB497011
8 changed files with 43 additions and 3 deletions

View file

@ -71,7 +71,7 @@ User=user
Group=user
WorkingDirectory=/opt/48hr-email
ExecStart=npm run prod
ExecStart=npm run start
Restart=on-failure
TimeoutStartSec=0

3
app.js
View file

@ -56,6 +56,9 @@ imapService.on(ImapService.EVENT_ERROR, error => {
app.set('mailProcessingService', mailProcessingService)
app.locals.imapService = imapService
app.locals.mailProcessingService = mailProcessingService
debug('Starting IMAP connection and message loading')
imapService.connectAndLoadMessages().catch(error => {
debug('Failed to connect to IMAP:', error.message)

View file

@ -150,6 +150,17 @@ class Helper {
return result
}
}
async getLargestUid(imapService) {
return await imapService.getLargestUid();
}
countElementBuilder(count = 0, largestUid = 0) {
const handling = `<label title="Historically managed ${largestUid} email${largestUid === 1 ? '' : 's'}">
<h4 style="display: inline;"><u><i>${count}</i></u> mail${count === 1 ? '' : 's'}</h4>
</label>`
return handling
}
}
module.exports = Helper

View file

@ -421,8 +421,18 @@ class ImapService extends EventEmitter {
]
return this.connection.search(searchCriteria, fetchOptions)
}
/*
* Get the largest UID from all messages in the mailbox.
*/
async getLargestUid() {
const uids = await this._getAllUids();
return uids.length > 0 ? Math.max(...uids) : null;
}
}
// Consumers should use these constants:
ImapService.EVENT_NEW_MAIL = 'mail'
ImapService.EVENT_DELETED_MAIL = 'mailDeleted'

View file

@ -16,6 +16,8 @@ router.get('/:address/:errorCode', async(req, res, next) => {
}
debug(`Error page requested: ${req.params.errorCode} for ${req.params.address}`)
const count = await mailProcessingService.getCount()
const largestUid = await req.app.locals.imapService.getLargestUid()
const totalcount = helper.countElementBuilder(count, largestUid)
const errorCode = parseInt(req.params.errorCode) || 404
const message = req.query.message || (req.session && req.session.errorMessage) || 'An error occurred'
@ -26,6 +28,7 @@ router.get('/:address/:errorCode', async(req, res, next) => {
purgeTime: purgeTime,
address: req.params.address,
count: count,
totalcount: totalcount,
message: message,
status: errorCode,
branding: config.http.branding

View file

@ -9,6 +9,7 @@ const helper = new(Helper)
const purgeTime = helper.purgeTimeElemetBuilder()
const sanitizeAddress = param('address').customSanitizer(
(value, { req }) => {
return req.params.address
@ -25,12 +26,15 @@ router.get('^/:address([^@/]+@[^@/]+)', sanitizeAddress, async(req, res, next) =
}
debug(`Inbox request for ${req.params.address}`)
const count = await mailProcessingService.getCount()
const largestUid = await req.app.locals.imapService.getLargestUid()
const totalcount = helper.countElementBuilder(count, largestUid)
debug(`Rendering inbox with ${count} total mails`)
res.render('inbox', {
title: `${config.http.branding[0]} | ` + req.params.address,
purgeTime: purgeTime,
address: req.params.address,
count: count,
totalcount: totalcount,
mailSummaries: mailProcessingService.getMailSummaries(req.params.address),
branding: config.http.branding,
})
@ -49,6 +53,8 @@ router.get(
const mailProcessingService = req.app.get('mailProcessingService')
debug(`Viewing email ${req.params.uid} for ${req.params.address}`)
const count = await mailProcessingService.getCount()
const largestUid = await req.app.locals.imapService.getLargestUid()
const totalcount = helper.countElementBuilder(count, largestUid)
const mail = await mailProcessingService.getOneFullMail(
req.params.address,
req.params.uid
@ -67,6 +73,7 @@ router.get(
purgeTime: purgeTime,
address: req.params.address,
count: count,
totalcount: totalcount,
mail,
uid: req.params.uid,
branding: config.http.branding,
@ -194,6 +201,8 @@ router.get(
debug(`Fetching raw email ${req.params.uid} for ${req.params.address}`)
const uid = parseInt(req.params.uid, 10)
const count = await mailProcessingService.getCount()
const largestUid = await req.app.locals.imapService.getLargestUid()
const totalcount = helper.countElementBuilder(count, largestUid)
// Validate UID is a valid integer
if (isNaN(uid) || uid <= 0) {
@ -214,7 +223,8 @@ router.get(
debug(`Rendering raw email view for UID ${req.params.uid}`)
res.render('raw', {
title: req.params.uid + " | raw | " + req.params.address,
mail
mail,
totalcount: totalcount
})
} else {
debug(`Raw email ${uid} not found for ${req.params.address}`)

View file

@ -18,6 +18,8 @@ router.get('/', async(req, res, next) => {
}
debug('Login page requested')
const count = await mailProcessingService.getCount()
const largestUid = await req.app.locals.imapService.getLargestUid()
const totalcount = helper.countElementBuilder(count, largestUid)
debug(`Rendering login page with ${count} total mails`)
res.render('login', {
title: `${config.http.branding[0]} | Your temporary Inbox`,
@ -25,6 +27,7 @@ router.get('/', async(req, res, next) => {
purgeTime: purgeTime,
domains: helper.getDomains(),
count: count,
totalcount: totalcount,
branding: config.http.branding,
example: config.email.examples.account,
})

View file

@ -30,7 +30,7 @@
{% block footer %}
<section class="container footer">
<hr>
<h4>{{ branding[0] }} offered by <a href="{{ branding[2] }}" style="text-decoration:underline" target="_blank">{{ branding[1] }}</a> | All Emails will be deleted after {{ purgeTime | raw }} | Currently handling <u><i>{{ count }}</i></u> Emails</h4>
<h4>{{ branding[0] }} offered by <a href="{{ branding[2] }}" style="text-decoration:underline" target="_blank">{{ branding[1] }}</a> | All Emails will be deleted after {{ purgeTime | raw }} | Currently handling {{ totalcount | raw }}</h4>
<h4 class="container footer-two"> This project is <a href="https://github.com/crazyco-xyz/48hr.email" style="text-decoration:underline" target="_blank">open-source ♥</a></h4>
</section>
{% endblock %}