Split up Email views

This commit is contained in:
Johannes Zellner
2025-04-16 17:59:03 +02:00
parent ec20fb453b
commit 024d057e03
7 changed files with 265 additions and 211 deletions

View File

@@ -5,12 +5,12 @@ const i18n = useI18n();
const t = i18n.t;
import { ref, onMounted, useTemplateRef } from 'vue';
import { Button, Checkbox, InputDialog, Dialog, FormGroup, Switch } from 'pankow';
import { Button, Checkbox, InputDialog, Dialog, FormGroup, Switch, SingleSelect } from 'pankow';
import { prettyDecimalSize, sleep } from 'pankow/utils';
import Section from '../components/Section.vue';
import SettingsItem from '../components/SettingsItem.vue';
import CatchAllSettingsItem from '../components/CatchAllSettingsItem.vue';
import MailRelaySettingsItem from '../components/MailRelaySettingsItem.vue';
import MailDomainStatus from '../components/MailDomainStatus.vue';
import DashboardModel from '../models/DashboardModel.js';
import DomainsModel from '../models/DomainsModel.js';
import MailModel from '../models/MailModel.js';
@@ -22,15 +22,43 @@ const mailModel = MailModel.create();
const inputDialog = useTemplateRef('inputDialog');
const enableIncomingDialog = useTemplateRef('enableIncomingDialog');
const connectionDialog = useTemplateRef('connectionDialog');
const domains = ref([]);
const domain = ref('');
const domainProvider = ref('');
const mailConfig = ref({});
const mailFqdn = ref('');
const refreshBusy = ref(false);
const incomingEnabled = ref(false);
const enableIncomeBusy = ref(false);
const enableIncomingSetupDns = ref(false);
const infoMenu = ref([]);
const busyRefresh = ref(true);
const mailboxCount = ref('');
const inboundEnabled = ref(false);
const outboundEnabled = ref(false);
const usage = ref(0);
// TODO
async function onSendTestMail(domain) {
const address = await inputDialog.value.prompt({
value: profile.value.email,
title: t('emails.testMailDialog.title', { domain: domain.domain }),
message: t('emails.testMailDialog.description', { domain: domain.domain }),
confirmLabel: t('emails.testMailDialog.sendAction'),
rejectLabel: t('main.dialog.cancel'),
rejectStyle: 'secondary'
});
if (!address) return;
const [error] = await mailModel.sendTestMail(domain.domain, address);
if (error) {
window.pankow.notify({ text: error.body ? error.body.message : 'Failed to send mail', type: 'danger' });
return console.error(error);
}
window.pankow.notify({ text: 'Mail sent', type: 'success' });
}
function onShowConnectionDialog() {
connectionDialog.value.open();
@@ -57,7 +85,7 @@ async function onAskIncomingToggle(value) {
const [error] = await mailModel.setEnabled(domain.value, false);
if (error) return console.error(error);
await refreshMailConfig();
await onDomainChanged();
}
async function onEnableIncoming() {
@@ -73,7 +101,7 @@ async function onEnableIncoming() {
if (error) console.error(error);
}
await refreshMailConfig();
await onDomainChanged();
enableIncomingDialog.value.close();
enableIncomeBusy.value = false;
@@ -117,14 +145,20 @@ async function onSubmitSignature() {
signatureBusy.value = false;
}
async function onDomainChanged() {
busyRefresh.value = true;
async function refreshMailConfig() {
refreshBusy.value = true;
let [error, result] = await domainsModel.get(domain.value);
if (error) return console.error(error);
const [error, result] = await mailModel.config(domain.value);
domainProvider.value = result.provider;
[error, result] = await mailModel.config(domain.value);
if (error) return console.error(error);
mailConfig.value = result;
inboundEnabled.value = result.enabled;
outboundEnabled.value = result.relay?.provider !== 'noop';
incomingEnabled.value = result.enabled;
mailFromValidation.value = result.mailFromValidation;
signatureText.value = mailConfig.value.banner.text || '';
@@ -140,24 +174,44 @@ async function refreshMailConfig() {
action: onShowConnectionDialog
}];
refreshBusy.value = false;
// do this even if no outbound since people forget to remove mailboxes
[error, result] = await mailModel.mailboxCount(domain.value);
if (error) console.error(error);
mailboxCount.value = result;
// this may temporarily fail while the mail server is restarting
while (true) {
const [error, result] = await mailModel.usage(domain.value);
if (error && error.status === 424) {
await sleep(1000);
continue;
} else if (error) return console.error(error);
usage.value = 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) { usage.value += result[m].diskSize; });
break;
}
busyRefresh.value = false;
}
onMounted(async () => {
domain.value = window.location.hash.slice('#/email/'.length);
if (!domain.value) return;
let [error, result] = await domainsModel.get(domain.value);
if (error) return console.error(error);
domainProvider.value = result.provider;
[error, result] = await dashboardModel.config();
let [error, result] = await dashboardModel.config();
if (error) return console.error(error);
mailFqdn.value = result.mailFqdn;
await refreshMailConfig();
[error, result] = await domainsModel.list();
if (error) return console.error(error);
domains.value = result;
domain.value = domains.value[0].domain;
await onDomainChanged();
});
</script>
@@ -231,22 +285,24 @@ onMounted(async () => {
<Section :title="$t('email.config.title', { domain: domain })">
<template #header-buttons>
<SingleSelect v-if="domains.length" v-model="domain" :options="domains" option-key="domain" option-label="domain" @select="onDomainChanged"/>
<Button secondary tool icon="fa-solid fa-book" v-tooltip="$t('app.docsActionTooltip')" :menu="infoMenu" />
</template>
<!-- v-if here ensures the prop is already set when passed down to MailDomainStatus -->
<MailDomainStatus v-if="domain" :domain="domain"/>
<div v-if="busyRefresh">{{ $t('main.loadingPlaceholder') }} ...</div>
<div v-else>
<div v-if="inboundEnabled">
{{ $t('emails.domains.stats', { mailboxCount: mailboxCount, usage: prettyDecimalSize(usage) }) }}
</div>
<div v-else>
<span v-if="outboundEnabled">{{ $t('emails.domains.outbound') }}</span>
<span v-else>{{ $t('emails.domains.disabled') }}</span>
</div>
</div>
</Section>
<Section :title="$t('emails.settings.title')" :padding="false">
<SettingsItem>
<FormGroup>
<label>{{ $t('email.incoming.title') }}</label>
<div>{{ $t(mailConfig.enabled ? 'email.incoming.enabled' : 'email.incoming.disabled') }}</div>
</FormGroup>
<Switch v-model="incomingEnabled" :disabled="refreshBusy" @change="onAskIncomingToggle"/>
</SettingsItem>
<Section title="Sending" :padding="false" v-if="!busyRefresh">
<MailRelaySettingsItem v-if="domain" :domain="domain"/>
<SettingsItem>
@@ -256,6 +312,16 @@ onMounted(async () => {
</FormGroup>
<Switch v-model="mailFromValidation" @change="onToggleMailFromValidation" :disabled="mailFromValidationBusy"/>
</SettingsItem>
</Section>
<Section title="Receiving" :padding="false" v-if="!busyRefresh">
<SettingsItem>
<FormGroup>
<label>{{ $t('email.incoming.title') }}</label>
<div>{{ $t(mailConfig.enabled ? 'email.incoming.enabled' : 'email.incoming.disabled') }}</div>
</FormGroup>
<Switch v-model="incomingEnabled" :disabled="busyRefresh" @change="onAskIncomingToggle"/>
</SettingsItem>
<CatchAllSettingsItem :domain-config="mailConfig" />