Only refresh changed email domains when mailboxes change

This commit is contained in:
Johannes Zellner
2025-12-01 16:04:10 +01:00
parent 71affc0239
commit 3a760282f1
2 changed files with 48 additions and 39 deletions
+47 -38
View File
@@ -108,7 +108,7 @@ const filteredMailboxes = computed(() => {
});
const filteredMailboxesUsage = computed(() => {
return filteredMailboxes.value.reduce((acc, m) => acc + (m.usage && m.usage.diskSize), 0);
return filteredMailboxes.value.reduce((acc, m) => acc + (cachedMailboxUsage.value[m.fullName] && cachedMailboxUsage.value[m.fullName].diskSize), 0);
});
const mailboxDialog = useTemplateRef('mailboxDialog');
@@ -144,59 +144,68 @@ async function onSubmitRemove() {
return console.error(error);
}
await refresh();
const idx = mailboxes.value.findIndex(mbox => mbox.fullName === removeMailbox.value.fullName);
if (idx !== -1) mailboxes.value.splice(idx, 1);
removeDialog.value.close();
removeBusy.value = false;
}
const cachedMailboxUsage = ref({});
async function refreshDomainUsage(domain) {
const [error, usage] = await mailModel.usage(domain);
// retry if mail addon cannot be reached during restarts
if (error && error.status === 424) return setTimeout(refresh, 2000);
else if (error) return console.error(error);
mailboxes.value.forEach((m) => {
if (usage[m.fullName]) cachedMailboxUsage.value[m.fullName] = usage[m.fullName];
});
mailboxesUsage.value = mailboxes.value.reduce((acc, m) => acc + (cachedMailboxUsage.value[m.fullName] && cachedMailboxUsage.value[m.fullName].diskSize), 0);
}
async function refreshUsage() {
async function refreshForDomain(domain) {
const [error, usage] = await mailModel.usage(domain);
// retry if mail addon cannot be reached during restarts
if (error && error.status === 424) return setTimeout(refresh, 2000);
else if (error) return console.error(error);
mailboxes.value.forEach((m) => {
m.usage = usage[m.fullName] ?? { diskSize: 0}; // can be missing from response if no email has been received by dovecot
});
}
try {
await eachLimit(domains.value.map(d => d.domain), 10, refreshForDomain);
await eachLimit(domains.value.map(d => d.domain), 10, refreshDomainUsage);
} catch (error) {
return console.error(error);
}
}
mailboxesUsage.value = mailboxes.value.reduce((acc, m) => acc + (m.usage && m.usage.diskSize), 0);
async function refreshDomain(domain) {
const [error, result] = await mailboxesModel.list(domain);
if (error) throw error;
result.forEach((m) => {
m.fullName = m.name + '@' + m.domain;
m.owner = users.value.find(u => u.id === m.ownerId) || null;
if (!m.owner) m.owner = groups.value.find(g => g.id === m.ownerId) || null;
m.ownerDisplayName = m.owner ? (m.owner.username || m.owner.name) : '';
cachedMailboxUsage.value[m.fullName] = cachedMailboxUsage.value[m.fullName] || { diskSize: 0 };
// update in-place or add
const idx = mailboxes.value.findIndex(mbox => mbox.fullName === m.fullName);
if (idx !== -1) mailboxes.value[idx] = m;
else mailboxes.value.push(m);
});
}
async function refresh() {
let tmp = [];
async function refreshForDomain(domain) {
const [error, result] = await mailboxesModel.list(domain);
if (error) throw error;
result.forEach((m) => {
m.fullName = m.name + '@' + m.domain;
m.owner = users.value.find(u => u.id === m.ownerId) || null;
if (!m.owner) m.owner = groups.value.find(g => g.id === m.ownerId) || null;
m.ownerDisplayName = m.owner ? (m.owner.username || m.owner.name) : '';
m.usage = -1;
});
tmp = tmp.concat(result);
}
try {
await eachLimit(domains.value.map(d => d.domain), 10, refreshForDomain);
await eachLimit(domains.value.map(d => d.domain), 10, refreshDomain);
} catch (error) {
return console.error(error);
}
}
mailboxes.value = tmp;
async function onMailboxDialogSuccess(mailbox) {
await refreshDomain(mailbox.domain);
await refreshDomainUsage(mailbox.domain);
}
onMounted(async () => {
@@ -250,7 +259,7 @@ onMounted(async () => {
</div>
</Dialog>
<MailboxDialog ref="mailboxDialog" :apps="apps" :users="users" :groups="groups" :domains="domains" @success="refresh()"/>
<MailboxDialog ref="mailboxDialog" :apps="apps" :users="users" :groups="groups" :domains="domains" @success="onMailboxDialogSuccess"/>
<Section :title="$t('email.incoming.mailboxes.title')">
<template #header-title-extra>
@@ -264,11 +273,11 @@ onMounted(async () => {
<TableView :columns="columns" :model="filteredMailboxes" :busy="busy" :fixed-layout="true" :placeholder="$t(searchFilter ? 'email.incoming.mailboxes.noMatchesPlaceholder' : 'email.incoming.mailboxes.emptyPlaceholder')">
<template #aliases="mailbox">{{ mailbox.aliases.length }}</template>
<template #usage="mailbox">
<span v-if="mailbox.usage !== -1">{{ prettyDecimalSize(mailbox.usage.diskSize) }}</span>
<span v-if="cachedMailboxUsage[mailbox.fullName]">{{ prettyDecimalSize(cachedMailboxUsage[mailbox.fullName].diskSize) }}</span>
<span v-else>{{ $t('main.loadingPlaceholder') }} ...</span>
</template>
<template #storageQuota="mailbox">
<span v-if="mailbox.usage && mailbox.usage.quotaLimit">{{ prettyDecimalSize(mailbox.usage.quotaLimit) }}</span>
<span v-if="cachedMailboxUsage[mailbox.fullName] && typeof cachedMailboxUsage[mailbox.fullName].quotaLimit !== 'undefined'">{{ prettyDecimalSize(cachedMailboxUsage[mailbox.fullName].quotaLimit) }}</span>
<span v-else>-</span>
</template>
<template #actions="mailbox">