1cd069df5e
This reverts commit 7db5a48e35.
172 lines
5.3 KiB
Vue
172 lines
5.3 KiB
Vue
<script setup>
|
|
|
|
import { ref, onMounted, computed } from 'vue';
|
|
import { prettyDecimalSize, sleep } from '@cloudron/pankow/utils';
|
|
import { prettyRelayProviderName } from '../utils.js';
|
|
import { TextInput } from '@cloudron/pankow';
|
|
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([]);
|
|
|
|
const searchFilter = ref('');
|
|
|
|
const filteredDomains = computed(() => {
|
|
if (!searchFilter.value) return domains.value;
|
|
|
|
return domains.value.filter(d => {
|
|
const search = searchFilter.value.toLowerCase();
|
|
|
|
if (d.domain.toLowerCase().indexOf(search) !== -1) return true;
|
|
if (d.provider.toLowerCase().indexOf(search) !== -1) return true;
|
|
|
|
if (d.inboundEnabled && 'inbound'.includes(search)) return true;
|
|
if (d.outboundEnabled && 'outbound'.includes(search)) return true;
|
|
|
|
return false;
|
|
});
|
|
});
|
|
|
|
|
|
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';
|
|
domain.relayProvider = result.relay ? result.relay.provider : 'unset';
|
|
|
|
// do this even if no outbound since people forget to remove mailboxes
|
|
[error, result] = await mailModel.stats(domain.domain);
|
|
if (error) console.error(error);
|
|
|
|
domain.mailboxCount = result.mailboxCount;
|
|
|
|
// 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('emails.domains.title')">
|
|
<template #header-title-extra>
|
|
<span style="font-weight: normal; font-size: 14px">({{ domains.length === 0 ? '-' : filteredDomains.length }})</span>
|
|
</template>
|
|
|
|
<template #header-buttons>
|
|
<TextInput :placeholder="$t('main.searchPlaceholder')" style="flex-grow: 1;" v-model="searchFilter"/>
|
|
</template>
|
|
|
|
<div>
|
|
<div v-if="domains.length !== 0 && filteredDomains.length === 0" class="email-placeholder">{{ $t('domains.noMatchesPlaceholder') }}</div>
|
|
<a v-for="domain in filteredDomains" :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.inbound') }}. Relayed via {{ prettyRelayProviderName(domain.relayProvider) }}. {{ $t('emails.domains.stats', { mailboxCount: domain.mailboxCount, usage: prettyDecimalSize(domain.usage) }) }}
|
|
</div>
|
|
<div v-else>
|
|
<span v-if="domain.outboundEnabled">{{ $t('emails.domains.outbound') }}. Relayed via {{ prettyRelayProviderName(domain.relayProvider) }}</span>
|
|
<span v-else>{{ $t('emails.domains.disabled') }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
</Section>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
|
|
.email-placeholder {
|
|
text-align: center;
|
|
margin-top: 150px;
|
|
}
|
|
|
|
.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> |