generated from Nodarx/template
234 lines
6.4 KiB
Vue
234 lines
6.4 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref, watch } from 'vue'
|
|
import { LocationSocketSole } from '@KTXM/MailManager/models/location'
|
|
import { ServiceObject } from '@KTXM/MailManager/models/service'
|
|
import type {
|
|
ServiceLocation,
|
|
ServiceLocationSocketSole,
|
|
} from '@KTXM/MailManager/types/service'
|
|
import type {
|
|
ProviderProtocolPanelProps,
|
|
ProviderProtocolPanelEmits,
|
|
} from '@KTXM/MailManager/types/integration'
|
|
|
|
type ImapEncryption = 'none' | 'ssl' | 'tls' | 'starttls'
|
|
|
|
const props = defineProps<ProviderProtocolPanelProps>()
|
|
const emit = defineEmits<ProviderProtocolPanelEmits>()
|
|
|
|
const host = ref('')
|
|
const encryption = ref<ImapEncryption>('ssl')
|
|
const port = ref('993')
|
|
const verifyPeer = ref(true)
|
|
const verifyHost = 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 isValid = computed(() => !!host.value && rules.port(port.value) === true)
|
|
|
|
const currentLocation = computed((): ServiceLocationSocketSole | null => {
|
|
if (!isValid.value) {
|
|
return null
|
|
}
|
|
|
|
return {
|
|
type: 'SOCKET_SOLE',
|
|
host: host.value,
|
|
port: Number(port.value),
|
|
encryption: encryption.value,
|
|
verifyPeer: verifyPeer.value,
|
|
verifyHost: verifyHost.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 = createServiceObject(props.service)
|
|
nextService.location = location ? LocationSocketSole.fromJson(location) : null
|
|
emit('update:service', nextService)
|
|
},
|
|
{ immediate: true, deep: true }
|
|
)
|
|
|
|
watch(encryption, (next, previous) => {
|
|
const previousDefault = defaultPortFor(previous)
|
|
if (!port.value || Number(port.value) === previousDefault) {
|
|
port.value = String(defaultPortFor(next))
|
|
}
|
|
})
|
|
|
|
function syncFromLocation(location: ServiceLocation | null) {
|
|
const socketLocation = getSocketLocation(location)
|
|
|
|
host.value = socketLocation?.host ?? ''
|
|
encryption.value = socketLocation?.encryption ?? 'ssl'
|
|
port.value = String(socketLocation?.port ?? defaultPortFor(encryption.value))
|
|
verifyPeer.value = socketLocation?.verifyPeer ?? true
|
|
verifyHost.value = socketLocation?.verifyHost ?? true
|
|
}
|
|
|
|
function createServiceObject(service?: ServiceObject): ServiceObject {
|
|
const nextService = new ServiceObject()
|
|
|
|
if (service) {
|
|
nextService.fromJson(service.toJson())
|
|
}
|
|
|
|
return nextService
|
|
}
|
|
|
|
function getSocketLocation(location?: ServiceLocation | null): ServiceLocationSocketSole | null {
|
|
return location?.type === 'SOCKET_SOLE' ? location : null
|
|
}
|
|
|
|
function sameLocation(a: ServiceLocation | null, b: ServiceLocation | null): boolean {
|
|
if (a === null || b === null) {
|
|
return a === b
|
|
}
|
|
|
|
if (a.type !== 'SOCKET_SOLE' || b.type !== 'SOCKET_SOLE') {
|
|
return false
|
|
}
|
|
|
|
return a.host === b.host
|
|
&& a.port === b.port
|
|
&& a.encryption === b.encryption
|
|
&& (a.verifyPeer ?? true) === (b.verifyPeer ?? true)
|
|
&& (a.verifyHost ?? true) === (b.verifyHost ?? true)
|
|
}
|
|
|
|
function defaultPortFor(nextEncryption: ImapEncryption): number {
|
|
return nextEncryption === 'ssl' || nextEncryption === 'tls' ? 993 : 143
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="imap-protocol-panel">
|
|
<h3 class="text-h6 mb-4">IMAP Connection Settings</h3>
|
|
<p class="text-body-2 mb-6">Configure the server address, transport security, and certificate verification for your IMAP mailbox.</p>
|
|
|
|
<v-text-field
|
|
v-model="host"
|
|
label="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="encryption"
|
|
:items="encryptionOptions"
|
|
label="Security"
|
|
variant="outlined"
|
|
prepend-inner-icon="mdi-shield-lock"
|
|
class="mb-4"
|
|
/>
|
|
|
|
<v-text-field
|
|
v-model="port"
|
|
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="mt-4">
|
|
<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="verifyPeer"
|
|
label="Verify TLS certificate"
|
|
color="primary"
|
|
hint="Disable only for trusted internal or test environments"
|
|
persistent-hint
|
|
class="mb-4"
|
|
/>
|
|
|
|
<v-switch
|
|
v-model="verifyHost"
|
|
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-alert type="info" variant="tonal" density="compact" class="mt-4">
|
|
<template #prepend>
|
|
<v-icon>mdi-information</v-icon>
|
|
</template>
|
|
<div class="text-caption">
|
|
STARTTLS is accepted for compatibility, but the current IMAP client transport does not perform STARTTLS negotiation. Prefer TLS on port 993 when available.
|
|
</div>
|
|
</v-alert>
|
|
</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> |