mail: make status a tristate

status can be 'passed', 'failed' or 'skipped'. The motivation for this
change is that when using a relay, we can provide a message indicating
why the check was skipped.
This commit is contained in:
Girish Ramakrishnan
2025-06-27 18:51:03 +02:00
parent ec17e58eed
commit 9428cf0d06
4 changed files with 226 additions and 274 deletions
+59 -56
View File
@@ -8,25 +8,18 @@ const props = defineProps([ 'domain' ]);
const mailModel = MailModel.create();
const expectedDnsRecordsTypes = [
{ name: 'MX', value: 'mx' },
{ name: 'DKIM', value: 'dkim' },
{ name: 'SPF', value: 'spf' },
{ name: 'DMARC', value: 'dmarc' },
{ name: 'PTR4', value: 'ptr4' },
{ name: 'PTR6', value: 'ptr6' },
];
const dnsRecordLabels = {
'mx': 'MX',
'dkim': 'DKIM',
'spf': 'SPF',
'dmarc': 'DMARC',
'ptr4': 'PTR4',
'ptr6': 'PTR6',
};
const expectedDnsRecords = ref({});
const busy = ref(false);
const mailConfig = ref({});
const mailStatus = ref({});
const relayStatusCollapsed = ref(true);
const cloudronSmtpCollapsed = ref(true);
function onToggleCollapsed(record) {
record.collapsed = !record.collapsed;
}
const domainStatus = ref({});
async function refresh() {
busy.value = true;
@@ -34,19 +27,12 @@ async function refresh() {
let [error, result] = await mailModel.status(props.domain);
if (error) return console.error(error);
mailStatus.value = result;
for (const type of expectedDnsRecordsTypes) {
expectedDnsRecords.value[type.value] = result.dns[type.value] || {};
expectedDnsRecords.value[type.value].collapsed = expectedDnsRecords.value[type.value].status;
}
domainStatus.value = result;
[error, result] = await mailModel.config(props.domain);
if (error) return console.error(error);
mailConfig.value = result;
relayStatusCollapsed.value = true;
cloudronSmtpCollapsed.value = true;
busy.value = false;
}
@@ -58,42 +44,48 @@ onMounted(async () => {
</script>
<template>
<div>
<div v-if="domainStatus.mx">
<h4>{{ $t('email.dnsStatus.title') }} &nbsp; <Button @click="refresh()" small tool icon="fa fa-sync-alt" :disabled="busy" :loading="busy"/></h4>
<div v-html="$t('email.dnsStatus.description', { emailDnsDocsLink:'https://docs.cloudron.io/email/#dns-records'})"></div>
<br/>
<div v-for="record in expectedDnsRecordsTypes" :key="record.value">
<div v-if="expectedDnsRecords[record.value]?.expected">
<div class="text-muted">
<i v-if="!busy" class="fa-solid" :class="{ 'fa-check-circle text-success': expectedDnsRecords[record.value].status, 'fa-exclamation-triangle text-danger': !expectedDnsRecords[record.value].status }"></i>
<i v-else class="fa-solid fa-circle-notch fa-spin"></i>
&nbsp;
<span class="actionable" @click="onToggleCollapsed(expectedDnsRecords[record.value])">{{ record.name }} record</span>
</div>
<div class="record-details" v-show="!expectedDnsRecords[record.value]?.collapsed">
<div v-if="record.name === 'MX' && domain.provider === 'namecheap'">{{ $t('email.dnsStatus.namecheapInfo') }} <sup><a href="https://docs.cloudron.io/domains/#namecheap-dns" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></div>
<div v-if="record.name === 'PTR4' || record.name === 'PTR6'">{{ $t('email.dnsStatus.ptrInfo') }} <sup><a href="https://docs.cloudron.io/email/#ptr-record" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></div>
<div v-for="(label, type) in dnsRecordLabels" :key="type">
<div class="text-muted">
<i v-if="!busy" class="fa-solid" :class="{
'fa-check-circle text-success': domainStatus[type].status === 'passed',
'fa-exclamation-triangle text-danger': domainStatus[type].status === 'failed',
'fa-minus text-success': domainStatus[type].status === 'skipped',
}"></i>
<i v-else class="fa-solid fa-circle-notch fa-spin"></i>
&nbsp;
<span>{{ label }} record</span>
</div>
<div class="record-details">
<div v-if="type === 'mx' && domain.provider === 'namecheap'">{{ $t('email.dnsStatus.namecheapInfo') }} <sup><a href="https://docs.cloudron.io/domains/#namecheap-dns" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></div>
<div v-if="type === 'ptr4' || type === 'ptr6'">{{ $t('email.dnsStatus.ptrInfo') }} <sup><a href="https://docs.cloudron.io/email/#ptr-record" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></div>
<div v-if="domainStatus[type].status === 'skipped'">{{ domainStatus[type].message }}</div>
<div v-else>
<table>
<tbody>
<tr v-if="expectedDnsRecords[record.value].name">
<tr>
<td>{{ $t('email.dnsStatus.hostname') }}:</td>
<td>{{ expectedDnsRecords[record.value].name }}</td>
<td>{{ domainStatus[type].name }}</td>
</tr>
<tr v-if="!expectedDnsRecords[record.value].name">
<tr>
<td>{{ $t('email.dnsStatus.domain') }}:</td>
<td>{{ expectedDnsRecords[record.value].domain }}</td>
<td>{{ domainStatus[type].domain }}</td>
</tr>
<tr>
<td style="width: 10px;">{{ $t('email.dnsStatus.type') }}:</td>
<td>{{ expectedDnsRecords[record.value].type }}</td>
<td>{{ domainStatus[type].type }}</td>
</tr>
<tr>
<td>{{ $t('email.dnsStatus.expected') }}:</td>
<td>{{ expectedDnsRecords[record.value].expected }}</td>
<td>{{ domainStatus[type].expected }}</td>
</tr>
<tr>
<td>{{ $t('email.dnsStatus.current') }}:</td>
<td>{{ expectedDnsRecords[record.value].value ? expectedDnsRecords[record.value].value : ('['+('email.dnsStatus.recordNotSet' | tr)+']') }}</td>
<td>{{ domainStatus[type].value ? domainStatus[type].value : ('['+$t('email.dnsStatus.recordNotSet')+']') }}</td>
</tr>
</tbody>
</table>
@@ -104,31 +96,42 @@ onMounted(async () => {
<h4>{{ $t('email.smtpStatus.title') }} <sup><a href="https://docs.cloudron.io/email/#smtp-status" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></h4>
<div>
<div v-if="domainStatus.relay">
<div class="text-muted">
<i v-if="!busy" class="fa" :class="{ 'fa-check-circle text-success': mailStatus.relay?.status, 'fa-exclamation-triangle text-danger': !mailStatus.relay?.status }"></i>
<i v-if="!busy" class="fa" :class="{
'fa-check-circle text-success': domainStatus.relay.status === 'passed',
'fa-exclamation-triangle text-danger': domainStatus.relay.status === 'failed',
'fa-minus text-success': domainStatus.relay.status === 'skipped',
}"></i>
<i v-else class="fa-solid fa-circle-notch fa-spin"></i>
&nbsp;
<span class="actionable" @click="relayStatusCollapsed = !relayStatusCollapsed">{{ $t(mailConfig.relay?.provider === 'cloudron-smtp' ? 'email.smtpStatus.outboudDirect' : 'email.smtpStatus.outboudRelay') }}</span>
<span>{{ $t('email.smtpStatus.outboundSmtp') }}</span>
</div>
<div class="record-details" v-show="!relayStatusCollapsed">
<b>{{ mailStatus.relay?.value }}</b>
<div class="record-details">
{{ domainStatus.relay.message }}
</div>
</div>
<div v-if="mailConfig.relay?.provider === 'cloudron-smtp'">
<div v-if="domainStatus.rbl4">
<div class="text-muted">
<i v-if="!busy" class="fa" :class="{ 'fa-check-circle text-success': mailStatus.rbl?.status, 'fa-exclamation-triangle text-danger': !mailStatus.rbl?.status }"></i>
<i v-if="!busy" class="fa" :class="{
'fa-check-circle text-success': domainStatus.rbl4.status === 'passed',
'fa-exclamation-triangle text-danger': domainStatus.rbl4.status === 'failed',
'fa-minus text-success': domainStatus.rbl4.status === 'skipped',
}"></i>
<i v-else class="fa-solid fa-circle-notch fa-spin"></i>
&nbsp;
<span class="actionable" @click="cloudronSmtpCollapsed = !cloudronSmtpCollapsed">{{ $t('email.smtpStatus.blacklistCheck') }}</span>
<span>{{ $t('email.smtpStatus.blacklistCheck') }}</span>
</div>
<div class="record-details" v-show="!cloudronSmtpCollapsed">
<div v-if="mailStatus.rbl?.servers.length" v-html="$t('email.smtpStatus.blacklisted', { ip: mailStatus.rbl.ip })"></div>
<div v-else v-html="$t('email.smtpStatus.notBlacklisted', { ip: mailStatus.rbl?.ip })"></div>
<div class="record-details">
<div v-if="domainStatus.rbl4.status !== 'failed'">{{ domainStatus.rbl4.message }}</div>
<div v-else>
<div v-if="domainStatus.rbl4.servers.length" v-html="$t('email.smtpStatus.blacklisted', { ip: domainStatus.rbl4.ip })"></div>
<div v-else v-html="$t('email.smtpStatus.notBlacklisted', { ip: domainStatus.rbl4.ip })"></div>
<div v-for="server in mailStatus.rbl.servers" :key="server.name">
<a :href="server.site" target="_blank">{{ server.name }}</a>
<div v-for="server in domainStatus.rbl4.servers" :key="server.name">
<a :href="server.site" target="_blank">{{ server.name }}</a>
</div>
</div>
</div>
</div>