generated from Nodarx/template
c8f0f8328b
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
356 lines
11 KiB
Vue
356 lines
11 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref, watch } from 'vue'
|
|
import { LocationSocketSplit } from '@KTXM/MailManager/models/location'
|
|
import { ServiceObject } from '@KTXM/MailManager/models/service'
|
|
import type {
|
|
ServiceLocation,
|
|
ServiceLocationSocketSplit,
|
|
} from '@KTXM/MailManager/types/service'
|
|
import type {
|
|
ProviderProtocolPanelProps,
|
|
ProviderProtocolPanelEmits,
|
|
} from '@KTXM/MailManager/types/integration'
|
|
|
|
type Encryption = 'none' | 'ssl' | 'tls' | 'starttls'
|
|
|
|
const props = defineProps<ProviderProtocolPanelProps>()
|
|
const emit = defineEmits<ProviderProtocolPanelEmits>()
|
|
|
|
// ── Inbound (IMAP) ──────────────────────────────────────────────────────────
|
|
const inboundHost = ref('')
|
|
const inboundEncryption = ref<Encryption>('ssl')
|
|
const inboundPort = ref('993')
|
|
const inboundVerifyPeer = ref(true)
|
|
const inboundVerifyHost = ref(true)
|
|
|
|
// ── Outbound (SMTP) ─────────────────────────────────────────────────────────
|
|
const outboundHost = ref('')
|
|
const outboundEncryption = ref<Encryption>('starttls')
|
|
const outboundPort = ref('587')
|
|
const outboundVerifyPeer = ref(true)
|
|
const outboundVerifyHost = ref(true)
|
|
|
|
const encryptionOptions = [
|
|
{ title: 'Implicit TLS (SSL)', value: 'ssl' },
|
|
{ title: 'TLS', value: 'tls' },
|
|
{ title: 'STARTTLS', value: 'starttls' },
|
|
{ title: 'None', value: 'none' },
|
|
]
|
|
|
|
const rules = {
|
|
required: (value: unknown) => !!value || 'This field is required',
|
|
port: (value: string) => {
|
|
const numericValue = Number(value)
|
|
return Number.isInteger(numericValue) && numericValue >= 1 && numericValue <= 65535
|
|
? true
|
|
: 'Port must be between 1 and 65535'
|
|
},
|
|
}
|
|
|
|
const inboundValid = computed(() => !!inboundHost.value && rules.port(inboundPort.value) === true)
|
|
const outboundValid = computed(() => !!outboundHost.value && rules.port(outboundPort.value) === true)
|
|
const isValid = computed(() => inboundValid.value && outboundValid.value)
|
|
|
|
const currentLocation = computed((): ServiceLocationSocketSplit | null => {
|
|
if (!isValid.value) {
|
|
return null
|
|
}
|
|
|
|
return {
|
|
type: 'SOCKET_SPLIT',
|
|
inboundHost: inboundHost.value,
|
|
inboundPort: Number(inboundPort.value),
|
|
inboundEncryption: inboundEncryption.value,
|
|
inboundVerifyPeer: inboundVerifyPeer.value,
|
|
inboundVerifyHost: inboundVerifyHost.value,
|
|
outboundHost: outboundHost.value,
|
|
outboundPort: Number(outboundPort.value),
|
|
outboundEncryption: outboundEncryption.value,
|
|
outboundVerifyPeer: outboundVerifyPeer.value,
|
|
outboundVerifyHost: outboundVerifyHost.value,
|
|
}
|
|
})
|
|
|
|
watch(
|
|
() => [props.service, props.discoveredLocation] as const,
|
|
([service, discoveredLocation]) => {
|
|
syncFromLocation(service?.location?.toJson() ?? discoveredLocation ?? null)
|
|
},
|
|
{ immediate: true }
|
|
)
|
|
|
|
watch(
|
|
currentLocation,
|
|
location => {
|
|
const existingLocation = props.service?.location?.toJson() ?? null
|
|
if (sameLocation(existingLocation, location)) {
|
|
return
|
|
}
|
|
|
|
const nextService = props.service ?? new ServiceObject()
|
|
|
|
if (location === null) {
|
|
nextService.location = null
|
|
emit('update:service', nextService)
|
|
return
|
|
}
|
|
|
|
if (nextService.location instanceof LocationSocketSplit) {
|
|
const target = nextService.location
|
|
target.inboundHost = location.inboundHost
|
|
target.inboundPort = location.inboundPort
|
|
target.inboundEncryption = location.inboundEncryption
|
|
target.inboundVerifyPeer = location.inboundVerifyPeer ?? true
|
|
target.inboundVerifyHost = location.inboundVerifyHost ?? true
|
|
target.outboundHost = location.outboundHost
|
|
target.outboundPort = location.outboundPort
|
|
target.outboundEncryption = location.outboundEncryption
|
|
target.outboundVerifyPeer = location.outboundVerifyPeer ?? true
|
|
target.outboundVerifyHost = location.outboundVerifyHost ?? true
|
|
} else {
|
|
nextService.location = LocationSocketSplit.fromJson(location)
|
|
}
|
|
|
|
emit('update:service', nextService)
|
|
},
|
|
{ immediate: true, deep: true }
|
|
)
|
|
|
|
watch(inboundEncryption, (next, previous) => {
|
|
const previousDefault = defaultInboundPort(previous)
|
|
if (!inboundPort.value || Number(inboundPort.value) === previousDefault) {
|
|
inboundPort.value = String(defaultInboundPort(next))
|
|
}
|
|
})
|
|
|
|
watch(outboundEncryption, (next, previous) => {
|
|
const previousDefault = defaultOutboundPort(previous)
|
|
if (!outboundPort.value || Number(outboundPort.value) === previousDefault) {
|
|
outboundPort.value = String(defaultOutboundPort(next))
|
|
}
|
|
})
|
|
|
|
function syncFromLocation(location: ServiceLocation | null) {
|
|
const split = location?.type === 'SOCKET_SPLIT' ? location : null
|
|
|
|
inboundHost.value = split?.inboundHost ?? ''
|
|
inboundEncryption.value = split?.inboundEncryption ?? 'ssl'
|
|
inboundPort.value = String(split?.inboundPort ?? defaultInboundPort(inboundEncryption.value))
|
|
inboundVerifyPeer.value = split?.inboundVerifyPeer ?? true
|
|
inboundVerifyHost.value = split?.inboundVerifyHost ?? true
|
|
|
|
outboundHost.value = split?.outboundHost ?? ''
|
|
outboundEncryption.value = split?.outboundEncryption ?? 'starttls'
|
|
outboundPort.value = String(split?.outboundPort ?? defaultOutboundPort(outboundEncryption.value))
|
|
outboundVerifyPeer.value = split?.outboundVerifyPeer ?? true
|
|
outboundVerifyHost.value = split?.outboundVerifyHost ?? true
|
|
}
|
|
|
|
function sameLocation(a: ServiceLocation | null, b: ServiceLocation | null): boolean {
|
|
if (a === null || b === null) {
|
|
return a === b
|
|
}
|
|
|
|
if (a.type !== 'SOCKET_SPLIT' || b.type !== 'SOCKET_SPLIT') {
|
|
return false
|
|
}
|
|
|
|
return a.inboundHost === b.inboundHost
|
|
&& a.inboundPort === b.inboundPort
|
|
&& a.inboundEncryption === b.inboundEncryption
|
|
&& (a.inboundVerifyPeer ?? true) === (b.inboundVerifyPeer ?? true)
|
|
&& (a.inboundVerifyHost ?? true) === (b.inboundVerifyHost ?? true)
|
|
&& a.outboundHost === b.outboundHost
|
|
&& a.outboundPort === b.outboundPort
|
|
&& a.outboundEncryption === b.outboundEncryption
|
|
&& (a.outboundVerifyPeer ?? true) === (b.outboundVerifyPeer ?? true)
|
|
&& (a.outboundVerifyHost ?? true) === (b.outboundVerifyHost ?? true)
|
|
}
|
|
|
|
function defaultInboundPort(encryption: Encryption): number {
|
|
return encryption === 'ssl' || encryption === 'tls' ? 993 : 143
|
|
}
|
|
|
|
function defaultOutboundPort(encryption: Encryption): number {
|
|
if (encryption === 'ssl') {
|
|
return 465
|
|
}
|
|
if (encryption === 'none') {
|
|
return 25
|
|
}
|
|
return 587
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="imap-protocol-panel">
|
|
<h3 class="text-h6 mb-4">Connection Settings</h3>
|
|
<p class="text-body-2 mb-6">Configure the incoming (IMAP) and outgoing (SMTP) servers, transport security, and certificate verification for this mailbox.</p>
|
|
|
|
<!-- ── Incoming (IMAP) ─────────────────────────────────────────────────── -->
|
|
<div class="d-flex align-center mb-4">
|
|
<v-icon start color="primary">mdi-email-arrow-left</v-icon>
|
|
<span class="text-subtitle-1 font-weight-medium">Incoming mail (IMAP)</span>
|
|
</div>
|
|
|
|
<v-text-field
|
|
v-model="inboundHost"
|
|
label="IMAP Server Host"
|
|
hint="For example: imap.example.com"
|
|
persistent-hint
|
|
variant="outlined"
|
|
prepend-inner-icon="mdi-server"
|
|
class="mb-4"
|
|
autocomplete="off"
|
|
autocorrect="off"
|
|
autocapitalize="none"
|
|
:rules="[rules.required]"
|
|
/>
|
|
|
|
<v-select
|
|
v-model="inboundEncryption"
|
|
:items="encryptionOptions"
|
|
label="Security"
|
|
variant="outlined"
|
|
prepend-inner-icon="mdi-shield-lock"
|
|
class="mb-4"
|
|
/>
|
|
|
|
<v-text-field
|
|
v-model="inboundPort"
|
|
label="Port"
|
|
hint="Defaults to 993 for TLS/SSL and 143 for plain or STARTTLS"
|
|
persistent-hint
|
|
variant="outlined"
|
|
prepend-inner-icon="mdi-numeric"
|
|
class="mb-4"
|
|
type="number"
|
|
min="1"
|
|
max="65535"
|
|
:rules="[rules.required, rules.port]"
|
|
/>
|
|
|
|
<v-expansion-panels class="mb-6">
|
|
<v-expansion-panel>
|
|
<v-expansion-panel-title>
|
|
<v-icon start>mdi-cog</v-icon>
|
|
Security Options
|
|
</v-expansion-panel-title>
|
|
<v-expansion-panel-text>
|
|
<v-switch
|
|
v-model="inboundVerifyPeer"
|
|
label="Verify TLS certificate"
|
|
color="primary"
|
|
hint="Disable only for trusted internal or test environments"
|
|
persistent-hint
|
|
class="mb-4"
|
|
/>
|
|
|
|
<v-switch
|
|
v-model="inboundVerifyHost"
|
|
label="Verify certificate hostname"
|
|
color="primary"
|
|
hint="Checks that the certificate matches the IMAP host"
|
|
persistent-hint
|
|
class="mb-4"
|
|
/>
|
|
</v-expansion-panel-text>
|
|
</v-expansion-panel>
|
|
</v-expansion-panels>
|
|
|
|
<v-divider class="mb-6" />
|
|
|
|
<!-- ── Outgoing (SMTP) ─────────────────────────────────────────────────── -->
|
|
<div class="d-flex align-center mb-4">
|
|
<v-icon start color="primary">mdi-email-arrow-right</v-icon>
|
|
<span class="text-subtitle-1 font-weight-medium">Outgoing mail (SMTP)</span>
|
|
</div>
|
|
|
|
<v-text-field
|
|
v-model="outboundHost"
|
|
label="SMTP Server Host"
|
|
hint="For example: smtp.example.com"
|
|
persistent-hint
|
|
variant="outlined"
|
|
prepend-inner-icon="mdi-server-network"
|
|
class="mb-4"
|
|
autocomplete="off"
|
|
autocorrect="off"
|
|
autocapitalize="none"
|
|
:rules="[rules.required]"
|
|
/>
|
|
|
|
<v-select
|
|
v-model="outboundEncryption"
|
|
:items="encryptionOptions"
|
|
label="Security"
|
|
variant="outlined"
|
|
prepend-inner-icon="mdi-shield-lock"
|
|
class="mb-4"
|
|
/>
|
|
|
|
<v-text-field
|
|
v-model="outboundPort"
|
|
label="Port"
|
|
hint="Defaults to 465 for implicit TLS, 587 for STARTTLS, and 25 for plain"
|
|
persistent-hint
|
|
variant="outlined"
|
|
prepend-inner-icon="mdi-numeric"
|
|
class="mb-4"
|
|
type="number"
|
|
min="1"
|
|
max="65535"
|
|
:rules="[rules.required, rules.port]"
|
|
/>
|
|
|
|
<v-expansion-panels>
|
|
<v-expansion-panel>
|
|
<v-expansion-panel-title>
|
|
<v-icon start>mdi-cog</v-icon>
|
|
Security Options
|
|
</v-expansion-panel-title>
|
|
<v-expansion-panel-text>
|
|
<v-switch
|
|
v-model="outboundVerifyPeer"
|
|
label="Verify TLS certificate"
|
|
color="primary"
|
|
hint="Disable only for trusted internal or test environments"
|
|
persistent-hint
|
|
class="mb-4"
|
|
/>
|
|
|
|
<v-switch
|
|
v-model="outboundVerifyHost"
|
|
label="Verify certificate hostname"
|
|
color="primary"
|
|
hint="Checks that the certificate matches the SMTP host"
|
|
persistent-hint
|
|
class="mb-4"
|
|
/>
|
|
</v-expansion-panel-text>
|
|
</v-expansion-panel>
|
|
</v-expansion-panels>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.imap-protocol-panel {
|
|
max-width: 800px;
|
|
}
|
|
|
|
.text-h6 {
|
|
font-size: 1.25rem;
|
|
font-weight: 500;
|
|
line-height: 2rem;
|
|
letter-spacing: 0.0125em;
|
|
}
|
|
|
|
.text-body-2 {
|
|
font-size: 0.875rem;
|
|
font-weight: 400;
|
|
line-height: 1.25rem;
|
|
letter-spacing: 0.0178571429em;
|
|
color: rgba(var(--v-theme-on-surface), 0.7);
|
|
}
|
|
</style>
|