Show more info in the email domains view

This commit is contained in:
Johannes Zellner
2025-09-24 11:59:06 +02:00
parent cc17373166
commit dd183cbca0
2 changed files with 118 additions and 50 deletions
+110 -4
View File
@@ -1,30 +1,136 @@
<script setup>
import { ref, onMounted } from 'vue';
import { prettyDecimalSize, sleep } from '@cloudron/pankow/utils';
import Section from '../components/Section.vue';
import StateLED from '../components/StateLED.vue';
import DomainsModel from '../models/DomainsModel.js';
import MailModel from '../models/MailModel.js';
const domainsModel = DomainsModel.create();
const mailModel = MailModel.create();
const domains = ref([]);
async function refreshStatus() {
for (const domain of domains.value) {
const [error, result] = await mailModel.status(domain.domain);
if (error) {
console.error(error);
} else {
domain.status = Object.keys(result).every((k) => {
return result[k].status === 'passed' || result[k].status === 'skipped';
});
domain.loadingStatus = false;
}
}
}
async function refreshUsage() {
for (const domain of domains.value) {
let [error, result] = await mailModel.config(domain.domain);
if (error) return console.error(error);
domain.inboundEnabled = result.enabled;
domain.outboundEnabled = result.relay?.provider !== 'noop';
// do this even if no outbound since people forget to remove mailboxes
[error, result] = await mailModel.mailboxCount(domain.domain);
if (error) console.error(error);
domain.mailboxCount = result;
// this may temporarily fail while the mail server is restarting
while (true) {
const [error, result] = await mailModel.usage(domain.domain);
if (error && error.status === 424) {
await sleep(1000);
continue;
} else if (error) return console.error(error);
domain.usage = 0;
// we used to use quotaValue here but it's quite different wrt diskSize. so choose diskSize consistently
// also, quotaValue can be missing for deleted mailboxes that are on disk but removed from dovecot/ldap itself
Object.keys(result).forEach(function (m) { domain.usage += result[m].diskSize; });
break;
}
domain.loadingUsage = false;
}
}
onMounted(async () => {
const [error, result] = await domainsModel.list();
if (error) return console.error(error);
result.forEach(d => {
d.loadingStatus = true;
d.status = {
state: ''
};
d.loadingUsage = true;
d.mailboxCount = 0;
d.usage = 0;
d.inboundEnabled = true;
d.outboundEnabled = true;
});
domains.value = result;
refreshStatus();
refreshUsage();
});
</script>
<template>
<div class="content">
<Section :title="$t('email.config.title')">
<Section :title="$t('emails.domains.title')">
<div>
<div v-for="domain in domains" :key="domain.domain">
<a :href="`#/email-domain/${domain.domain}`">{{ domain.domain }}</a>
</div>
<a v-for="domain in domains" :key="domain.domain" :href="`#/email-domain/${domain.domain}`" class="email-domain">
<div style="display: flex; align-items: center;">
<StateLED :busy="domain.loadingStatus" :state="domain.status ? 'success' : 'danger'"/>
</div>
<div class="email-domain-details">
<div><b style="font-size: 16px">{{ domain.domain }}</b></div>
<div v-if="domain.loadingUsage">
{{ $t('main.loadingPlaceholder') }} ...
</div>
<div v-else>
<div v-if="domain.inboundEnabled">
{{ $t('emails.domains.stats', { mailboxCount: domain.mailboxCount, usage: prettyDecimalSize(domain.usage) }) }}
</div>
<div v-else>
<span v-if="domain.outboundEnabled">{{ $t('emails.domains.outbound') }}</span>
<span v-else>{{ $t('emails.domains.disabled') }}</span>
</div>
</div>
</div>
</a>
</div>
</Section>
</div>
</template>
<style scoped>
.email-domain {
display: flex;
border-radius: var(--pankow-border-radius);
padding: 10px;
gap: 10px;
margin-bottom: 10px;
color: var(--pankow-text-color);
}
.email-domain:hover {
background-color: var(--pankow-color-background-hover);
}
.email-domain-details {
flex-grow: 1;
}
</style>