2025-03-12 12:49:06 +01:00
|
|
|
<script setup>
|
|
|
|
|
|
|
|
|
|
import { ref, onMounted } from 'vue';
|
2025-09-24 13:13:05 +02:00
|
|
|
import { Button } from '@cloudron/pankow';
|
2025-09-24 12:46:15 +02:00
|
|
|
import Section from './Section.vue';
|
2025-03-12 12:49:06 +01:00
|
|
|
import MailModel from '../models/MailModel.js';
|
|
|
|
|
|
|
|
|
|
const props = defineProps([ 'domain' ]);
|
|
|
|
|
|
|
|
|
|
const mailModel = MailModel.create();
|
|
|
|
|
|
2025-09-24 13:13:05 +02:00
|
|
|
const dnsRecordLabels = ref({
|
|
|
|
|
'mx': {
|
|
|
|
|
label: 'MX',
|
|
|
|
|
isOpen: false,
|
|
|
|
|
},
|
|
|
|
|
'dkim': {
|
|
|
|
|
label: 'DKIM',
|
|
|
|
|
isOpen: false,
|
|
|
|
|
},
|
|
|
|
|
'spf': {
|
|
|
|
|
label: 'SPF',
|
|
|
|
|
isOpen: false,
|
|
|
|
|
},
|
|
|
|
|
'dmarc': {
|
|
|
|
|
label: 'DMARC',
|
|
|
|
|
isOpen: false,
|
|
|
|
|
},
|
|
|
|
|
'ptr4': {
|
|
|
|
|
label: 'PTR4',
|
|
|
|
|
isOpen: false,
|
|
|
|
|
},
|
|
|
|
|
'ptr6': {
|
|
|
|
|
label: 'PTR6',
|
|
|
|
|
isOpen: false,
|
|
|
|
|
},
|
|
|
|
|
});
|
2025-06-27 18:51:03 +02:00
|
|
|
|
2025-09-24 13:13:05 +02:00
|
|
|
const rblTypes = ref({
|
|
|
|
|
'rbl4': {
|
|
|
|
|
label: 'rbl4',
|
|
|
|
|
isOpen: false,
|
|
|
|
|
},
|
|
|
|
|
'rbl6': {
|
|
|
|
|
label: 'rbl6',
|
|
|
|
|
isOpen: false,
|
|
|
|
|
},
|
|
|
|
|
});
|
2025-06-28 13:19:03 +02:00
|
|
|
|
2025-03-12 12:49:06 +01:00
|
|
|
const busy = ref(false);
|
|
|
|
|
const mailConfig = ref({});
|
2025-06-27 18:51:03 +02:00
|
|
|
const domainStatus = ref({});
|
2025-03-12 12:49:06 +01:00
|
|
|
|
|
|
|
|
async function refresh() {
|
|
|
|
|
busy.value = true;
|
|
|
|
|
|
|
|
|
|
let [error, result] = await mailModel.status(props.domain);
|
|
|
|
|
if (error) return console.error(error);
|
|
|
|
|
|
2025-09-24 13:13:05 +02:00
|
|
|
if (result.relay) result.relay.isOpen = false;
|
|
|
|
|
|
2025-06-27 18:51:03 +02:00
|
|
|
domainStatus.value = result;
|
2025-03-12 12:49:06 +01:00
|
|
|
|
|
|
|
|
[error, result] = await mailModel.config(props.domain);
|
|
|
|
|
if (error) return console.error(error);
|
|
|
|
|
|
|
|
|
|
mailConfig.value = result;
|
|
|
|
|
|
|
|
|
|
busy.value = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
await refresh();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
2025-09-24 12:46:15 +02:00
|
|
|
<Section title="Status">
|
|
|
|
|
<template #header-buttons>
|
|
|
|
|
<Button @click="refresh()" secondary tool icon="fa fa-sync-alt" :disabled="busy" :loading="busy"/>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<div v-html="$t('email.dnsStatus.description', { emailDnsDocsLink:'https://docs.cloudron.io/email/#dns-records'})"></div>
|
|
|
|
|
<br/>
|
|
|
|
|
|
2025-08-11 19:07:02 +02:00
|
|
|
<div v-if="domainStatus.mx">
|
2025-09-24 13:13:05 +02:00
|
|
|
<div v-for="(item, key) in dnsRecordLabels" :key="key" class="record-item" @click="item.isOpen = !item.isOpen">
|
2025-08-11 19:07:02 +02:00
|
|
|
<div>
|
|
|
|
|
<i v-if="!busy" class="fa-solid" :class="{
|
2025-09-24 13:13:05 +02:00
|
|
|
'fa-check-circle text-success': domainStatus[key].status === 'passed',
|
|
|
|
|
'fa-exclamation-triangle text-danger': domainStatus[key].status === 'failed',
|
|
|
|
|
'fa-circle-minus text-warning': domainStatus[key].status === 'skipped',
|
2025-08-11 19:07:02 +02:00
|
|
|
}"></i>
|
|
|
|
|
<i v-else class="fa-solid fa-circle-notch fa-spin"></i>
|
|
|
|
|
|
2025-09-24 13:13:05 +02:00
|
|
|
<b>{{ item.label }} record</b>
|
2025-08-11 19:07:02 +02:00
|
|
|
</div>
|
2025-06-27 18:51:03 +02:00
|
|
|
|
2025-09-24 13:13:05 +02:00
|
|
|
<div class="record-details" v-if="item.isOpen" @click.stop>
|
|
|
|
|
<div v-if="key === '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="key === 'ptr4' || key === '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[key].status === 'skipped'">{{ domainStatus[key].message }}</div>
|
2025-08-11 19:07:02 +02:00
|
|
|
<div v-else>
|
|
|
|
|
<table class="domain-status">
|
|
|
|
|
<tbody>
|
|
|
|
|
<tr>
|
|
|
|
|
<td>{{ $t('email.dnsStatus.hostname') }}:</td>
|
2025-09-24 13:13:05 +02:00
|
|
|
<td>{{ domainStatus[key].name }}</td>
|
2025-08-11 19:07:02 +02:00
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td>{{ $t('email.dnsStatus.domain') }}:</td>
|
2025-09-24 13:13:05 +02:00
|
|
|
<td>{{ domainStatus[key].domain }}</td>
|
2025-08-11 19:07:02 +02:00
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td>{{ $t('email.dnsStatus.type') }}:</td>
|
2025-09-24 13:13:05 +02:00
|
|
|
<td>{{ domainStatus[key].type }}</td>
|
2025-08-11 19:07:02 +02:00
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td>{{ $t('email.dnsStatus.expected') }}:</td>
|
2025-09-24 13:13:05 +02:00
|
|
|
<td>{{ domainStatus[key].expected }}</td>
|
2025-08-11 19:07:02 +02:00
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td>{{ $t('email.dnsStatus.current') }}:</td>
|
2025-09-24 13:13:05 +02:00
|
|
|
<td>{{ domainStatus[key].value ? domainStatus[key].value : ('['+$t('email.dnsStatus.recordNotSet')+']') }} {{ domainStatus[key].message }}</td>
|
2025-08-11 19:07:02 +02:00
|
|
|
</tr>
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
2025-03-12 12:49:06 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-09-24 12:46:15 +02:00
|
|
|
</div>
|
|
|
|
|
|
2025-09-24 13:13:05 +02:00
|
|
|
<div v-if="domainStatus.relay" class="record-item" @click="domainStatus.relay.isOpen = !domainStatus.relay.isOpen">
|
2025-09-24 12:46:15 +02:00
|
|
|
<div>
|
|
|
|
|
<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-circle-minus text-warning': domainStatus.relay.status === 'skipped',
|
|
|
|
|
}"></i>
|
|
|
|
|
<i v-else class="fa-solid fa-circle-notch fa-spin"></i>
|
|
|
|
|
|
|
|
|
|
<b>{{ $t('email.smtpStatus.outboundSmtp') }}</b>
|
|
|
|
|
</div>
|
2025-09-24 13:13:05 +02:00
|
|
|
<div class="record-details" v-if="domainStatus.relay.isOpen">
|
2025-09-24 12:46:15 +02:00
|
|
|
{{ domainStatus.relay.message }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-03-12 12:49:06 +01:00
|
|
|
|
2025-09-24 13:13:05 +02:00
|
|
|
<div v-for="(item, key) in rblTypes" :key="key" class="record-item" @click="item.isOpen = !item.isOpen">
|
|
|
|
|
<div v-if="domainStatus[key]">
|
2025-08-11 19:07:02 +02:00
|
|
|
<div>
|
2025-06-28 13:19:03 +02:00
|
|
|
<i v-if="!busy" class="fa" :class="{
|
2025-09-24 13:13:05 +02:00
|
|
|
'fa-check-circle text-success': domainStatus[key].status === 'passed',
|
|
|
|
|
'fa-exclamation-triangle text-danger': domainStatus[key].status === 'failed',
|
|
|
|
|
'fa-circle-minus text-success': domainStatus[key].status === 'skipped',
|
2025-06-28 13:19:03 +02:00
|
|
|
}"></i>
|
|
|
|
|
<i v-else class="fa-solid fa-circle-notch fa-spin"></i>
|
|
|
|
|
|
2025-09-24 13:13:05 +02:00
|
|
|
<b>{{ key === 'rbl4' ? 'IPv4' : 'IPv6' }} {{ $t('email.smtpStatus.rblCheck') }}</b>
|
2025-06-28 13:19:03 +02:00
|
|
|
</div>
|
2025-09-24 13:13:05 +02:00
|
|
|
<div class="record-details" v-if="item.isOpen">
|
|
|
|
|
<div v-if="domainStatus[key].status !== 'failed'">IP: {{ domainStatus[key].ip }}</div>
|
2025-09-24 12:46:15 +02:00
|
|
|
<div v-else>
|
2025-09-24 13:13:05 +02:00
|
|
|
{{ domainStatus[key] }}
|
|
|
|
|
<div v-if="domainStatus[key].servers.length" v-html="$t('email.smtpStatus.blacklisted', { ip: domainStatus[key].ip })"></div>
|
|
|
|
|
<div v-else v-html="$t('email.smtpStatus.notBlacklisted', { ip: domainStatus[key].ip })"></div>
|
2025-06-28 13:19:03 +02:00
|
|
|
|
2025-09-24 13:13:05 +02:00
|
|
|
<div v-for="server in domainStatus[key].servers" :key="server.name">
|
2025-09-24 12:46:15 +02:00
|
|
|
<a :href="server.removal" target="_blank">{{ server.name }}</a>
|
2025-06-28 13:19:03 +02:00
|
|
|
</div>
|
2025-06-27 18:51:03 +02:00
|
|
|
</div>
|
2025-03-12 12:49:06 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-09-24 12:46:15 +02:00
|
|
|
</Section>
|
2025-03-12 12:49:06 +01:00
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
2025-09-24 13:13:05 +02:00
|
|
|
.record-item {
|
|
|
|
|
border-radius: var(--pankow-border-radius);
|
|
|
|
|
padding: 10px;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
color: var(--pankow-text-color);
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.record-item:hover {
|
|
|
|
|
background-color: var(--pankow-color-background-hover);
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-12 12:49:06 +01:00
|
|
|
.record-details {
|
2025-08-11 19:07:02 +02:00
|
|
|
padding: 10px 30px;
|
2025-03-12 12:49:06 +01:00
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.record-details > table {
|
|
|
|
|
table-layout: fixed;
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.record-details > table > tr > td:nth-child(1) {
|
|
|
|
|
width: 150px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.record-details > table > tr > td:nth-child(2) {
|
|
|
|
|
display: block;
|
|
|
|
|
overflow: scroll;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
text-overflow: auto;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-11 19:07:02 +02:00
|
|
|
.domain-status > tbody > tr > td:first-of-type {
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
padding-right: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-12 12:49:06 +01:00
|
|
|
</style>
|