chore: bunch of improvements
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
+15
@@ -36,6 +36,14 @@ const rules = {
|
||||
required: (v: string) => !!v || 'Required',
|
||||
email: (v: string) => /.+@.+\..+/.test(v) || 'Invalid email address'
|
||||
}
|
||||
|
||||
function handleDiscoverOnEnter() {
|
||||
if (!localAddress.value || rules.email(localAddress.value) !== true) {
|
||||
return
|
||||
}
|
||||
|
||||
emit('discover')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -55,6 +63,7 @@ const rules = {
|
||||
autocomplete="off"
|
||||
:rules="[rules.required, rules.email]"
|
||||
class="mb-4"
|
||||
@keydown.enter.prevent="handleDiscoverOnEnter"
|
||||
/>
|
||||
|
||||
<!-- Advanced Options -->
|
||||
@@ -143,6 +152,8 @@ const rules = {
|
||||
<v-btn
|
||||
variant="text"
|
||||
block
|
||||
class="manual-action-btn"
|
||||
prepend-icon="mdi-tune"
|
||||
@click="$emit('manual')"
|
||||
>
|
||||
Manual Configuration
|
||||
@@ -155,4 +166,8 @@ const rules = {
|
||||
.gap-3 {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.manual-action-btn {
|
||||
background-color: rgba(var(--v-theme-on-surface), 0.06);
|
||||
}
|
||||
</style>
|
||||
+22
-2
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { ProviderDiscoveryStatus } from '@MailManager/types/service'
|
||||
import type { ProviderDiscoveryStatus } from '@MailManager/types'
|
||||
|
||||
const props = defineProps<{
|
||||
address: string
|
||||
@@ -16,7 +16,7 @@ const emit = defineEmits<{
|
||||
|
||||
const sortedStatus = computed(() => {
|
||||
const statusArray = Object.values(props.status)
|
||||
const order = { success: 0, discovering: 1, pending: 2, failed: 3 }
|
||||
const order: Record<string, number> = { success: 0, discovering: 1, pending: 2, failed: 3 }
|
||||
return statusArray.sort((a, b) => order[a.status] - order[b.status])
|
||||
})
|
||||
|
||||
@@ -191,6 +191,22 @@ function getProviderLabel(providerId: string): string {
|
||||
</div>
|
||||
</div>
|
||||
</v-alert>
|
||||
|
||||
<div
|
||||
v-if="!isDiscovering && successCount === 0"
|
||||
class="mt-6"
|
||||
>
|
||||
<v-btn
|
||||
size="large"
|
||||
variant="text"
|
||||
block
|
||||
class="manual-action-btn"
|
||||
prepend-icon="mdi-tune"
|
||||
@click="$emit('manual')"
|
||||
>
|
||||
Manual Configuration
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
@@ -217,4 +233,8 @@ function getProviderLabel(providerId: string): string {
|
||||
.gap-3 {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.manual-action-btn {
|
||||
background-color: rgba(var(--v-theme-on-surface), 0.06);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,124 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, shallowRef, watch } from 'vue'
|
||||
import type { Component } from 'vue'
|
||||
import { useIntegrationStore } from '@KTXC/stores/integrationStore'
|
||||
import type { ServiceObject } from '@MailManager/models/service'
|
||||
import type { ProviderObject } from '@MailManager/models/provider'
|
||||
|
||||
const props = defineProps<{
|
||||
provider: ProviderObject
|
||||
service: ServiceObject
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:service': [value: ServiceObject]
|
||||
}>()
|
||||
|
||||
// Local state
|
||||
const integrationStore = useIntegrationStore()
|
||||
const panelCache = new Map<string, Component>()
|
||||
const panelLoading = ref(false)
|
||||
const panelActive = shallowRef<Component | null>(null)
|
||||
const localProvider = ref<ProviderObject>(props.provider)
|
||||
const localService = ref<ServiceObject>(props.service)
|
||||
|
||||
// Local watchers
|
||||
watch(
|
||||
() => props.provider,
|
||||
async (provider) => {
|
||||
localProvider.value = provider
|
||||
await loadProviderPanel()
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.service,
|
||||
(service) => {
|
||||
localService.value = service
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => [localProvider.value?.identifier, localService.value?.provider] as const,
|
||||
async () => {
|
||||
await loadProviderPanel()
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// Load provider panel
|
||||
async function loadProviderPanel() {
|
||||
const providerIdentifier = localProvider.value?.identifier || localService.value?.provider
|
||||
|
||||
if (!providerIdentifier) {
|
||||
panelActive.value = null
|
||||
panelLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// retrieve panel from cache if available
|
||||
if (panelCache.has(providerIdentifier)) {
|
||||
panelActive.value = panelCache.get(providerIdentifier) || null
|
||||
panelLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
panelLoading.value = true
|
||||
|
||||
// retrieve panel from integration store
|
||||
const panel = integrationStore.getItems('mail_account_auth_panels').find((panel: any) => {
|
||||
return panel.id === providerIdentifier || panel.id.endsWith(`.${providerIdentifier}`)
|
||||
})
|
||||
if (!panel?.component) {
|
||||
console.warn(`No config panel found for provider ID: ${providerIdentifier}`)
|
||||
panelActive.value = null
|
||||
panelLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const module = await panel.component()
|
||||
const component = module.default || module
|
||||
panelCache.set(providerIdentifier, component)
|
||||
panelActive.value = component
|
||||
} catch (error) {
|
||||
console.error(`Failed to load panel for ${providerIdentifier}:`, error)
|
||||
panelActive.value = null
|
||||
} finally {
|
||||
panelLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleUpdate(service: ServiceObject) {
|
||||
localService.value = service
|
||||
emit('update:service', localService.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="provider-auth-panel">
|
||||
<h3 class="text-h6 mb-2">Authentication</h3>
|
||||
<p class="text-body-2 text-medium-emphasis mb-6">
|
||||
Configure authentication for {{ localProvider?.label || 'this provider' }}.
|
||||
</p>
|
||||
|
||||
<div v-if="panelLoading" class="text-center py-8">
|
||||
<v-progress-circular indeterminate color="primary" />
|
||||
<p class="text-caption text-medium-emphasis mt-2">
|
||||
Loading authentication panel...
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<v-alert v-else-if="!panelActive" type="error" variant="tonal">
|
||||
<v-icon start>mdi-alert-circle</v-icon>
|
||||
No authentication method available for this provider.
|
||||
</v-alert>
|
||||
|
||||
<component
|
||||
v-else
|
||||
:is="panelActive"
|
||||
:service="localService"
|
||||
@update:service="handleUpdate"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,163 +0,0 @@
|
||||
<template>
|
||||
<div class="provider-auth-step">
|
||||
<h3 class="text-h6 mb-2">Authentication</h3>
|
||||
<p class="text-body-2 text-medium-emphasis mb-6">
|
||||
Configure authentication for {{ providerLabel }}
|
||||
</p>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div v-if="loadingPanel" class="text-center py-8">
|
||||
<v-progress-circular indeterminate color="primary" />
|
||||
<p class="text-caption text-medium-emphasis mt-2">
|
||||
Loading authentication panel...
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Dynamic Provider Auth Panel -->
|
||||
<component
|
||||
v-else-if="currentAuthPanel"
|
||||
:is="currentAuthPanel"
|
||||
:email-address="emailAddress"
|
||||
:discovered-location="discoveredLocation"
|
||||
:prefilled-identity="prefilledIdentity"
|
||||
:prefilled-secret="prefilledSecret"
|
||||
v-model="localIdentity"
|
||||
@update:model-value="handleIdentityUpdate"
|
||||
@valid="handleValidChange"
|
||||
@error="handleAuthError"
|
||||
/>
|
||||
|
||||
<!-- No Panel Available -->
|
||||
<v-alert v-else type="error" variant="tonal">
|
||||
<v-icon start>mdi-alert-circle</v-icon>
|
||||
No authentication method available for this provider.
|
||||
Please contact support.
|
||||
</v-alert>
|
||||
|
||||
<!-- Error Display -->
|
||||
<v-alert
|
||||
v-if="authError"
|
||||
type="error"
|
||||
variant="tonal"
|
||||
class="mt-4"
|
||||
closable
|
||||
@click:close="authError = ''"
|
||||
>
|
||||
<v-icon start>mdi-alert</v-icon>
|
||||
{{ authError }}
|
||||
</v-alert>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useIntegrationStore } from '@KTXC/stores/integrationStore'
|
||||
import type { ServiceIdentity, ServiceLocation } from '@MailManager/types/service'
|
||||
|
||||
const props = defineProps<{
|
||||
providerId: string
|
||||
providerLabel: string
|
||||
emailAddress: string
|
||||
discoveredLocation?: ServiceLocation
|
||||
prefilledIdentity?: string
|
||||
prefilledSecret?: string
|
||||
modelValue?: ServiceIdentity | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: ServiceIdentity]
|
||||
'valid': [value: boolean]
|
||||
}>()
|
||||
|
||||
const integrationStore = useIntegrationStore()
|
||||
|
||||
const loadedPanels = new Map<string, any>()
|
||||
const currentAuthPanel = ref<any>(null)
|
||||
const loadingPanel = ref(false)
|
||||
const localIdentity = ref<ServiceIdentity | undefined>(props.modelValue)
|
||||
const authError = ref('')
|
||||
|
||||
// The full integration ID (e.g., "jmap")
|
||||
const effectiveIntegrationId = computed(() => {
|
||||
return props.providerId
|
||||
})
|
||||
|
||||
// Load provider auth panel dynamically
|
||||
async function loadAuthPanel(integrationId: string) {
|
||||
if (loadedPanels.has(integrationId)) {
|
||||
currentAuthPanel.value = loadedPanels.get(integrationId)
|
||||
return
|
||||
}
|
||||
|
||||
loadingPanel.value = true
|
||||
|
||||
// Try to find panel - integration IDs are prefixed with module handle
|
||||
// so we need to search for panels that match the provider ID
|
||||
const panels = integrationStore.getItems('mail_account_auth_panels')
|
||||
const panelConfig = panels.find((panel: any) => {
|
||||
// Check if the ID ends with the provider ID (e.g., "provider_jmapc.jmap" contains "jmap")
|
||||
return panel.id === integrationId || panel.id.endsWith(`.${integrationId}`)
|
||||
})
|
||||
|
||||
if (!panelConfig?.component) {
|
||||
console.error(`No auth panel found for provider ID: ${integrationId}`)
|
||||
console.error(`Available panels:`, panels.map((p: any) => p.id))
|
||||
currentAuthPanel.value = null
|
||||
loadingPanel.value = false
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const module = await panelConfig.component()
|
||||
const component = module.default || module
|
||||
loadedPanels.set(integrationId, component)
|
||||
currentAuthPanel.value = component
|
||||
} catch (error) {
|
||||
console.error(`Failed to load auth panel for ${integrationId}:`, error)
|
||||
currentAuthPanel.value = null
|
||||
authError.value = `Failed to load authentication panel: ${error}`
|
||||
} finally {
|
||||
loadingPanel.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Load panel when provider changes
|
||||
watch(
|
||||
effectiveIntegrationId,
|
||||
(newIntegrationId, oldIntegrationId) => {
|
||||
if (newIntegrationId && newIntegrationId !== oldIntegrationId) {
|
||||
loadAuthPanel(newIntegrationId)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
function handleIdentityUpdate(identity: ServiceIdentity) {
|
||||
localIdentity.value = identity
|
||||
emit('update:modelValue', identity)
|
||||
}
|
||||
|
||||
function handleValidChange(valid: boolean) {
|
||||
emit('valid', valid)
|
||||
}
|
||||
|
||||
function handleAuthError(error: string) {
|
||||
authError.value = error
|
||||
}
|
||||
|
||||
// Watch for prop changes
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
localIdentity.value = newValue
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.provider-auth-step {
|
||||
max-width: 800px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,124 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useIntegrationStore } from '@KTXC/stores/integrationStore'
|
||||
import type { ServiceLocation } from '@MailManager/types/service'
|
||||
|
||||
const props = defineProps<{
|
||||
providerId: string
|
||||
discoveredLocation?: ServiceLocation
|
||||
modelValue?: ServiceLocation | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: ServiceLocation]
|
||||
'valid': [value: boolean]
|
||||
}>()
|
||||
|
||||
const integrationStore = useIntegrationStore()
|
||||
|
||||
const loadedPanels = new Map<string, any>()
|
||||
const currentProviderPanel = ref<any>(null)
|
||||
const loadingPanel = ref(false)
|
||||
const localLocation = ref<ServiceLocation | undefined>(props.modelValue || props.discoveredLocation)
|
||||
|
||||
// The full integration ID (e.g., "provider_jmapc.jmap")
|
||||
const effectiveIntegrationId = computed(() => {
|
||||
return props.providerId
|
||||
})
|
||||
|
||||
// Load provider panel dynamically using the integration ID
|
||||
async function loadProviderPanel(integrationId: string) {
|
||||
if (loadedPanels.has(integrationId)) {
|
||||
currentProviderPanel.value = loadedPanels.get(integrationId)
|
||||
return
|
||||
}
|
||||
|
||||
loadingPanel.value = true
|
||||
|
||||
// Try to find panel - integration IDs are prefixed with module handle
|
||||
// so we need to search for panels that match the provider ID
|
||||
const panels = integrationStore.getItems('mail_account_config_panels')
|
||||
const panelConfig = panels.find((panel: any) => {
|
||||
// Check if the ID ends with the provider ID (e.g., "provider_jmapc.jmap" contains "jmap")
|
||||
return panel.id === integrationId || panel.id.endsWith(`.${integrationId}`)
|
||||
})
|
||||
|
||||
if (!panelConfig?.component) {
|
||||
console.warn(`No config panel found for provider ID: ${integrationId}`)
|
||||
console.warn(`Available panels:`, panels.map((p: any) => p.id))
|
||||
currentProviderPanel.value = null
|
||||
loadingPanel.value = false
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const module = await panelConfig.component()
|
||||
const component = module.default || module
|
||||
loadedPanels.set(integrationId, component)
|
||||
currentProviderPanel.value = component
|
||||
} catch (error) {
|
||||
console.error(`Failed to load panel for ${integrationId}:`, error)
|
||||
currentProviderPanel.value = null
|
||||
} finally {
|
||||
loadingPanel.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(effectiveIntegrationId, (newIntegrationId, oldIntegrationId) => {
|
||||
if (newIntegrationId && newIntegrationId !== oldIntegrationId) {
|
||||
loadProviderPanel(newIntegrationId)
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
function handleLocationUpdate(location: ServiceLocation) {
|
||||
localLocation.value = location
|
||||
emit('update:modelValue', location)
|
||||
// Emit valid when location is provided
|
||||
emit('valid', !!location)
|
||||
}
|
||||
|
||||
// Watch for prop changes
|
||||
watch(() => props.modelValue, (newValue) => {
|
||||
if (newValue) {
|
||||
localLocation.value = newValue
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => props.discoveredLocation, (newValue) => {
|
||||
if (newValue && !props.modelValue) {
|
||||
localLocation.value = newValue
|
||||
emit('update:modelValue', newValue)
|
||||
emit('valid', true)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="provider-config-step">
|
||||
<h3 class="text-h6 mb-2">Protocol Configuration</h3>
|
||||
<p class="text-body-2 text-medium-emphasis mb-6">
|
||||
Configure the connection settings for your mail service
|
||||
</p>
|
||||
|
||||
<!-- Dynamic Provider Panel -->
|
||||
<component
|
||||
v-if="currentProviderPanel"
|
||||
:is="currentProviderPanel"
|
||||
v-model="localLocation"
|
||||
:discovered-location="discoveredLocation"
|
||||
@update:model-value="handleLocationUpdate"
|
||||
/>
|
||||
|
||||
<!-- Loading state for panel -->
|
||||
<div v-else-if="loadingPanel" class="text-center py-8">
|
||||
<v-progress-circular indeterminate color="primary" />
|
||||
<p class="text-caption text-medium-emphasis mt-2">Loading configuration panel...</p>
|
||||
</div>
|
||||
|
||||
<!-- No panel available -->
|
||||
<v-alert v-else type="info" variant="tonal">
|
||||
<v-icon start>mdi-information</v-icon>
|
||||
No configuration panel available for this provider
|
||||
</v-alert>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,124 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, shallowRef, watch } from 'vue'
|
||||
import type { Component } from 'vue'
|
||||
import { useIntegrationStore } from '@KTXC/stores/integrationStore'
|
||||
import type { ServiceObject } from '@MailManager/models/service'
|
||||
import type { ProviderObject } from '@MailManager/models/provider'
|
||||
|
||||
const props = defineProps<{
|
||||
provider: ProviderObject
|
||||
service: ServiceObject
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:service': [value: ServiceObject]
|
||||
}>()
|
||||
|
||||
// Local state
|
||||
const integrationStore = useIntegrationStore()
|
||||
const panelCache = new Map<string, Component>()
|
||||
const panelLoading = ref(false)
|
||||
const panelActive = shallowRef<Component | null>(null)
|
||||
const localProvider = ref<ProviderObject>(props.provider)
|
||||
const localService = ref<ServiceObject>(props.service)
|
||||
|
||||
// Local watchers
|
||||
watch(
|
||||
() => props.provider,
|
||||
async (provider) => {
|
||||
localProvider.value = provider
|
||||
await loadProviderPanel()
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.service,
|
||||
(service) => {
|
||||
localService.value = service
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => [localProvider.value?.identifier, localService.value?.provider] as const,
|
||||
async () => {
|
||||
await loadProviderPanel()
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// Load provider panel
|
||||
async function loadProviderPanel() {
|
||||
const providerIdentifier = localProvider.value?.identifier || localService.value?.provider
|
||||
|
||||
if (!providerIdentifier) {
|
||||
panelActive.value = null
|
||||
panelLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// retrieve panel from cache if available
|
||||
if (panelCache.has(providerIdentifier)) {
|
||||
panelActive.value = panelCache.get(providerIdentifier) || null
|
||||
panelLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
panelLoading.value = true
|
||||
|
||||
// retrieve panel from integration store
|
||||
const panel = integrationStore.getItems('mail_account_protocol_panels').find((panel: any) => {
|
||||
return panel.id === providerIdentifier || panel.id.endsWith(`.${providerIdentifier}`)
|
||||
})
|
||||
if (!panel?.component) {
|
||||
console.warn(`No config panel found for provider ID: ${providerIdentifier}`)
|
||||
panelActive.value = null
|
||||
panelLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const module = await panel.component()
|
||||
const component = module.default || module
|
||||
panelCache.set(providerIdentifier, component)
|
||||
panelActive.value = component
|
||||
} catch (error) {
|
||||
console.error(`Failed to load panel for ${providerIdentifier}:`, error)
|
||||
panelActive.value = null
|
||||
} finally {
|
||||
panelLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleUpdate(service: ServiceObject) {
|
||||
localService.value = service
|
||||
emit('update:service', localService.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="provider-protocol-panel">
|
||||
<h3 class="text-h6 mb-2">Protocol Configuration</h3>
|
||||
<p class="text-body-2 text-medium-emphasis mb-6">
|
||||
Configure authentication for {{ localProvider?.label || 'this provider' }}.
|
||||
</p>
|
||||
|
||||
<div v-if="panelLoading" class="text-center py-8">
|
||||
<v-progress-circular indeterminate color="primary" />
|
||||
<p class="text-caption text-medium-emphasis mt-2">
|
||||
Loading configuration panel...
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<v-alert v-else-if="!panelActive" type="info" variant="tonal">
|
||||
<v-icon start>mdi-information</v-icon>
|
||||
No configuration panel available for this provider
|
||||
</v-alert>
|
||||
|
||||
<component
|
||||
v-else
|
||||
:is="panelActive"
|
||||
:service="localService"
|
||||
@update:service="handleUpdate"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
+135
-133
@@ -1,3 +1,121 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import type { ProviderObject, ServiceObject } from '@MailManager/models'
|
||||
|
||||
const props = defineProps<{
|
||||
provider: ProviderObject
|
||||
service: ServiceObject
|
||||
onTest?: () => Promise<{ success: boolean; message: string; details?: any }>
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:service': [value: ServiceObject]
|
||||
'tested': [success: boolean]
|
||||
}>()
|
||||
|
||||
// Local state
|
||||
const localProvider = ref<ProviderObject>(props.provider)
|
||||
const localService = ref<ServiceObject>(props.service)
|
||||
const testing = ref(false)
|
||||
const testResult = ref<any>(null)
|
||||
|
||||
// Computed
|
||||
const testSuccess = computed(() => testResult.value?.success === true)
|
||||
const serviceLocation = computed(() => localService.value.location?.toJson() ?? null)
|
||||
const serviceIdentity = computed(() => localService.value.identity?.toJson() ?? null)
|
||||
|
||||
// Helper functions
|
||||
function getAuthIcon(type?: string): string {
|
||||
switch (type) {
|
||||
case 'NA': return 'mdi-lock-open-variant'
|
||||
case 'BA': return 'mdi-account-key'
|
||||
case 'TA': return 'mdi-key'
|
||||
case 'OA': return 'mdi-shield-account'
|
||||
case 'CC': return 'mdi-certificate'
|
||||
default: return 'mdi-help-circle'
|
||||
}
|
||||
}
|
||||
|
||||
function getAuthLabel(type?: string): string {
|
||||
switch (type) {
|
||||
case 'NA': return 'No Authentication'
|
||||
case 'BA': return 'Username & Password'
|
||||
case 'TA': return 'API Token'
|
||||
case 'OA': return 'OAuth 2.0'
|
||||
case 'CC': return 'Client Certificate'
|
||||
default: return 'Unknown'
|
||||
}
|
||||
}
|
||||
|
||||
function formatCapabilities(capabilities: any): string {
|
||||
if (!capabilities || typeof capabilities !== 'object') return 'N/A'
|
||||
|
||||
const caps = Object.entries(capabilities)
|
||||
.filter(([_, value]) => value === true)
|
||||
.map(([key]) => key)
|
||||
.slice(0, 5)
|
||||
|
||||
const total = caps.length
|
||||
const display = caps.slice(0, 3).join(', ')
|
||||
|
||||
if (total > 3) {
|
||||
return `${display}, +${total - 3} more`
|
||||
}
|
||||
return display
|
||||
}
|
||||
|
||||
async function handleTest() {
|
||||
if (!localService.value.location || !localService.value.identity) {
|
||||
testResult.value = {
|
||||
success: false,
|
||||
message: 'Missing configuration'
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
testing.value = true
|
||||
testResult.value = null
|
||||
|
||||
try {
|
||||
if (!props.onTest) {
|
||||
throw new Error('No connection test callback provided')
|
||||
}
|
||||
|
||||
const result = await props.onTest()
|
||||
testResult.value = result
|
||||
emit('tested', result.success)
|
||||
} catch (error: any) {
|
||||
testResult.value = {
|
||||
success: false,
|
||||
message: error.message || 'Connection test failed'
|
||||
}
|
||||
emit('tested', false)
|
||||
} finally {
|
||||
testing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Watch for changes and emit
|
||||
watch(localService, () => {
|
||||
emit('update:service', localService.value)
|
||||
}, { deep: true })
|
||||
|
||||
watch(
|
||||
() => props.provider,
|
||||
(provider) => {
|
||||
localProvider.value = provider
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.service,
|
||||
(service) => {
|
||||
localService.value = service
|
||||
testResult.value = null
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="test-and-save-step">
|
||||
<h3 class="text-h6 mb-2">Test & Save</h3>
|
||||
@@ -16,7 +134,7 @@
|
||||
<v-icon>mdi-label</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>Account Name</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ accountLabel }}</v-list-item-subtitle>
|
||||
<v-list-item-subtitle>{{ localService.label || localService.primaryAddress || 'New Account' }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
||||
<!-- Email Address -->
|
||||
@@ -25,7 +143,7 @@
|
||||
<v-icon>mdi-email</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>Email Address</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ emailAddress }}</v-list-item-subtitle>
|
||||
<v-list-item-subtitle>{{ localService.primaryAddress }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
||||
<!-- Provider -->
|
||||
@@ -34,48 +152,48 @@
|
||||
<v-icon>mdi-cloud</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>Provider</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ providerLabel }}</v-list-item-subtitle>
|
||||
<v-list-item-subtitle>{{ localProvider.label }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
||||
<!-- Location Details -->
|
||||
<template v-if="location">
|
||||
<template v-if="serviceLocation">
|
||||
<v-divider class="my-2" />
|
||||
|
||||
<v-list-item v-if="location.type === 'URI'">
|
||||
<v-list-item v-if="serviceLocation.type === 'URI'">
|
||||
<template #prepend>
|
||||
<v-icon>mdi-web</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>Service URL</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
{{ location.scheme }}://{{ location.host }}:{{ location.port }}{{ location.path || '' }}
|
||||
{{ serviceLocation.scheme }}://{{ serviceLocation.host }}:{{ serviceLocation.port }}{{ serviceLocation.path || '' }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
||||
<template v-if="location.type === 'SOCKET_SOLE'">
|
||||
<template v-if="serviceLocation.type === 'SOCKET_SOLE'">
|
||||
<v-list-item>
|
||||
<template #prepend>
|
||||
<v-icon>mdi-server</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>Server</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ location.host }}:{{ location.port }}</v-list-item-subtitle>
|
||||
<v-list-item-subtitle>{{ serviceLocation.host }}:{{ serviceLocation.port }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<template #prepend>
|
||||
<v-icon>mdi-shield-lock</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>Security</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ location.encryption.toUpperCase() }}</v-list-item-subtitle>
|
||||
<v-list-item-subtitle>{{ serviceLocation.encryption.toUpperCase() }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<template v-if="location.type === 'SOCKET_SPLIT'">
|
||||
<template v-if="serviceLocation.type === 'SOCKET_SPLIT'">
|
||||
<v-list-item>
|
||||
<template #prepend>
|
||||
<v-icon>mdi-inbox-arrow-down</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>Incoming Mail</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
{{ location.inbound.protocol.toUpperCase() }} - {{ location.inbound.host }}:{{ location.inbound.port }} ({{ location.inbound.encryption.toUpperCase() }})
|
||||
{{ serviceLocation.inboundHost }}:{{ serviceLocation.inboundPort }} ({{ serviceLocation.inboundEncryption.toUpperCase() }})
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
@@ -84,7 +202,7 @@
|
||||
</template>
|
||||
<v-list-item-title>Outgoing Mail</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
{{ location.outbound.protocol.toUpperCase() }} - {{ location.outbound.host }}:{{ location.outbound.port }} ({{ location.outbound.encryption.toUpperCase() }})
|
||||
{{ serviceLocation.outboundHost }}:{{ serviceLocation.outboundPort }} ({{ serviceLocation.outboundEncryption.toUpperCase() }})
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</template>
|
||||
@@ -94,10 +212,10 @@
|
||||
<v-divider class="my-2" />
|
||||
<v-list-item>
|
||||
<template #prepend>
|
||||
<v-icon>{{ getAuthIcon(identity?.type) }}</v-icon>
|
||||
<v-icon>{{ getAuthIcon(serviceIdentity?.type) }}</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>Authentication</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ getAuthLabel(identity?.type) }}</v-list-item-subtitle>
|
||||
<v-list-item-subtitle>{{ getAuthLabel(serviceIdentity?.type) }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card-text>
|
||||
@@ -105,7 +223,7 @@
|
||||
|
||||
<!-- Account Label Input -->
|
||||
<v-text-field
|
||||
v-model="localAccountLabel"
|
||||
v-model="localService.label"
|
||||
label="Account Name"
|
||||
variant="outlined"
|
||||
hint="A friendly name for this account (e.g., Work Email)"
|
||||
@@ -116,7 +234,7 @@
|
||||
|
||||
<!-- Enable Account Toggle -->
|
||||
<v-switch
|
||||
v-model="accountEnabled"
|
||||
v-model="localService.enabled"
|
||||
label="Enable this account"
|
||||
color="primary"
|
||||
class="mb-4"
|
||||
@@ -176,120 +294,4 @@
|
||||
Please test the connection before saving
|
||||
</v-alert>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import type { ServiceIdentity, ServiceLocation } from '@MailManager/types/service'
|
||||
|
||||
const props = defineProps<{
|
||||
providerId: string
|
||||
providerLabel: string
|
||||
emailAddress: string
|
||||
location: ServiceLocation | null
|
||||
identity: ServiceIdentity | null
|
||||
prefilledLabel?: string
|
||||
onTest: () => Promise<any>
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:label': [value: string]
|
||||
'update:enabled': [value: boolean]
|
||||
'tested': [success: boolean]
|
||||
'valid': [value: boolean]
|
||||
}>()
|
||||
|
||||
// Local state
|
||||
const localAccountLabel = ref(props.prefilledLabel || props.emailAddress || '')
|
||||
const accountEnabled = ref(true)
|
||||
const testing = ref(false)
|
||||
const testResult = ref<any>(null)
|
||||
|
||||
// Computed
|
||||
const accountLabel = computed(() => localAccountLabel.value || props.emailAddress || 'New Account')
|
||||
const testSuccess = computed(() => testResult.value?.success === true)
|
||||
|
||||
const isValid = computed(() => {
|
||||
return testSuccess.value && !!localAccountLabel.value
|
||||
})
|
||||
|
||||
// Helper functions
|
||||
function getAuthIcon(type?: string): string {
|
||||
switch (type) {
|
||||
case 'NA': return 'mdi-lock-open-variant'
|
||||
case 'BA': return 'mdi-account-key'
|
||||
case 'TA': return 'mdi-key'
|
||||
case 'OA': return 'mdi-shield-account'
|
||||
case 'CC': return 'mdi-certificate'
|
||||
default: return 'mdi-help-circle'
|
||||
}
|
||||
}
|
||||
|
||||
function getAuthLabel(type?: string): string {
|
||||
switch (type) {
|
||||
case 'NA': return 'No Authentication'
|
||||
case 'BA': return 'Username & Password'
|
||||
case 'TA': return 'API Token'
|
||||
case 'OA': return 'OAuth 2.0'
|
||||
case 'CC': return 'Client Certificate'
|
||||
default: return 'Unknown'
|
||||
}
|
||||
}
|
||||
|
||||
function formatCapabilities(capabilities: any): string {
|
||||
if (!capabilities || typeof capabilities !== 'object') return 'N/A'
|
||||
|
||||
const caps = Object.entries(capabilities)
|
||||
.filter(([_, value]) => value === true)
|
||||
.map(([key]) => key)
|
||||
.slice(0, 5)
|
||||
|
||||
const total = caps.length
|
||||
const display = caps.slice(0, 3).join(', ')
|
||||
|
||||
if (total > 3) {
|
||||
return `${display}, +${total - 3} more`
|
||||
}
|
||||
return display
|
||||
}
|
||||
|
||||
async function handleTest() {
|
||||
if (!props.location || !props.identity) {
|
||||
testResult.value = {
|
||||
success: false,
|
||||
message: 'Missing configuration'
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
testing.value = true
|
||||
testResult.value = null
|
||||
|
||||
try {
|
||||
const result = await props.onTest()
|
||||
testResult.value = result
|
||||
emit('tested', result.success)
|
||||
} catch (error: any) {
|
||||
testResult.value = {
|
||||
success: false,
|
||||
message: error.message || 'Connection test failed'
|
||||
}
|
||||
emit('tested', false)
|
||||
} finally {
|
||||
testing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Watch for changes and emit
|
||||
watch(localAccountLabel, (value) => {
|
||||
emit('update:label', value)
|
||||
})
|
||||
|
||||
watch(accountEnabled, (value) => {
|
||||
emit('update:enabled', value)
|
||||
})
|
||||
|
||||
watch(isValid, (value) => {
|
||||
emit('valid', value)
|
||||
}, { immediate: true })
|
||||
</script>
|
||||
</template>
|
||||
Reference in New Issue
Block a user