feat: lots more improvements
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { ref, shallowRef, computed, watch } from 'vue'
|
||||
import { useIntegrationStore } from '@KTXC/stores/integrationStore'
|
||||
import { useServicesStore } from '@MailManager/stores/servicesStore'
|
||||
import { useProvidersStore } from '@MailManager/stores/providersStore'
|
||||
@@ -58,8 +58,8 @@ const discoverSecret = ref<string | null>(null)
|
||||
const discoverHostname = ref<string | null>(null)
|
||||
|
||||
// Step 2: Discovery Status / Provider Selection
|
||||
const selectedProvider = ref<ProviderObject | null>(null)
|
||||
const selectedService = ref<ServiceObject | null>(null)
|
||||
const selectedProvider = shallowRef<ProviderObject | null>(null)
|
||||
const selectedService = shallowRef<ServiceObject | null>(null)
|
||||
|
||||
// Step 5: Test & Save
|
||||
const testAndSaveValid = ref(false)
|
||||
@@ -162,7 +162,7 @@ function createServiceObject(
|
||||
auxiliary: data.auxiliary ?? {}
|
||||
}
|
||||
|
||||
const factoryItem = integrationStore.getItemById('mail_service_factory', providerId) as any
|
||||
const factoryItem = integrationStore.getItemById('mail_provider_factory_service', providerId) as any
|
||||
const factory = factoryItem?.factory
|
||||
return factory ? factory(model) : new ServiceObject().fromJson(model)
|
||||
}
|
||||
@@ -175,16 +175,13 @@ function setSelectedProviderAndService(providerId: string, service: ServiceObjec
|
||||
|
||||
function handleServiceUpdate(service: ServiceObject) {
|
||||
selectedService.value = service
|
||||
testAndSaveValid.value = false
|
||||
}
|
||||
|
||||
function handleServiceTested(success: boolean) {
|
||||
testAndSaveValid.value = success
|
||||
}
|
||||
|
||||
watch(selectedService, () => {
|
||||
testAndSaveValid.value = false
|
||||
}, { deep: true })
|
||||
|
||||
// Navigation methods
|
||||
function handlePreviousStep() {
|
||||
if (currentStep.value > 1) {
|
||||
@@ -370,8 +367,7 @@ async function testConnection() {
|
||||
}
|
||||
}
|
||||
|
||||
const serviceData = selectedService.value.toJson()
|
||||
if (!serviceData.location || !serviceData.identity) {
|
||||
if (!selectedService.value.location || !selectedService.value.identity) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Missing configuration'
|
||||
@@ -381,8 +377,8 @@ async function testConnection() {
|
||||
const testResult = await servicesStore.test(
|
||||
selectedProvider.value.identifier,
|
||||
null,
|
||||
serviceData.location,
|
||||
serviceData.identity
|
||||
selectedService.value.location,
|
||||
selectedService.value.identity
|
||||
)
|
||||
|
||||
return testResult
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { ref, shallowRef, computed, watch } from 'vue'
|
||||
import { useServicesStore } from '@MailManager/stores/servicesStore'
|
||||
import { useProvidersStore } from '@MailManager/stores/providersStore'
|
||||
import type { ProviderObject, ServiceObject } from '@MailManager/models'
|
||||
import ProviderAuxiliaryPanel from '@MailManager/components/steps/ProviderAuxiliaryPanel.vue'
|
||||
import ProviderProtocolPanel from '@MailManager/components/steps/ProviderProtocolPanel.vue'
|
||||
import ProviderAuthPanel from '@MailManager/components/steps/ProviderAuthPanel.vue'
|
||||
import TestAndSavePanel from '@MailManager/components/steps/TestAndSavePanel.vue'
|
||||
|
||||
type EditTab = 'general' | 'protocol' | 'auth'
|
||||
type EditTab = 'general' | 'auxiliary' | 'protocol' | 'auth'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean
|
||||
@@ -33,19 +34,27 @@ const saving = ref(false)
|
||||
const loading = ref(false)
|
||||
const loadError = ref<string | null>(null)
|
||||
|
||||
const localProvider = ref<ProviderObject | null>(null)
|
||||
const localService = ref<ServiceObject | null>(null)
|
||||
const mutated = ref(false)
|
||||
const localProvider = shallowRef<ProviderObject | null>(null)
|
||||
const localService = shallowRef<ServiceObject | null>(null)
|
||||
|
||||
// Validation states
|
||||
const testAndSaveValid = ref(false)
|
||||
|
||||
function serviceRequiresConnectionTest(service: ServiceObject | null): boolean {
|
||||
return !!(service?.location?.mutated() || service?.identity?.mutated())
|
||||
}
|
||||
|
||||
const tabItems = [
|
||||
{
|
||||
title: 'General',
|
||||
icon: 'mdi-view-dashboard-outline',
|
||||
value: 'general' as const
|
||||
},
|
||||
{
|
||||
title: 'Auxiliary Settings',
|
||||
icon: 'mdi-tune-variant',
|
||||
value: 'auxiliary' as const
|
||||
},
|
||||
{
|
||||
title: 'Protocol',
|
||||
icon: 'mdi-tune-vertical',
|
||||
@@ -59,7 +68,7 @@ const tabItems = [
|
||||
]
|
||||
|
||||
const canSave = computed(() => {
|
||||
return testAndSaveValid.value
|
||||
return !serviceRequiresConnectionTest(localService.value) || testAndSaveValid.value
|
||||
})
|
||||
|
||||
const showSaveButton = computed(() => currentTab.value === 'general')
|
||||
@@ -118,7 +127,6 @@ function resetForm() {
|
||||
localService.value = null
|
||||
localProvider.value = null
|
||||
loadError.value = null
|
||||
testAndSaveValid.value = false
|
||||
}
|
||||
|
||||
function isTabDisabled(tab: EditTab) {
|
||||
@@ -131,14 +139,24 @@ function isTabDisabled(tab: EditTab) {
|
||||
|
||||
function handleUpdate(mutatedService: ServiceObject) {
|
||||
localService.value = mutatedService
|
||||
mutated.value = true
|
||||
|
||||
if (serviceRequiresConnectionTest(mutatedService)) {
|
||||
testAndSaveValid.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function testConnection() {
|
||||
try {
|
||||
if (!localService.value) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Missing service configuration'
|
||||
}
|
||||
}
|
||||
|
||||
let testResult = null
|
||||
|
||||
if (mutated.value) {
|
||||
if (serviceRequiresConnectionTest(localService.value)) {
|
||||
testResult = await servicesStore.test(
|
||||
localService.value.provider,
|
||||
null,
|
||||
@@ -165,27 +183,19 @@ async function testConnection() {
|
||||
|
||||
async function saveAccount() {
|
||||
// No changes made, just close the dialog
|
||||
if (!mutated.value) {
|
||||
if (!localService.value.mutated() && !localService.value.location?.mutated() && !localService.value.identity?.mutated()) {
|
||||
close()
|
||||
return
|
||||
}
|
||||
|
||||
if (!localService.value?.location || !localService.value?.identity) return
|
||||
|
||||
saving.value = true
|
||||
|
||||
try {
|
||||
const accountData = {
|
||||
label: accountLabel.value || localService.value.label,
|
||||
enabled: accountEnabled.value,
|
||||
location: localService.value.location,
|
||||
identity: localService.value.identity
|
||||
}
|
||||
|
||||
await servicesStore.update(
|
||||
localService.value.provider,
|
||||
localService.value.identifier as string | number,
|
||||
accountData
|
||||
true, // delta update
|
||||
localService.value
|
||||
)
|
||||
|
||||
emit('saved')
|
||||
@@ -254,20 +264,31 @@ async function saveAccount() {
|
||||
<v-card flat class="pa-6">
|
||||
<TestAndSavePanel
|
||||
v-if="localProvider && localService"
|
||||
:provider="localProvider"
|
||||
:service="localService"
|
||||
:provider="localProvider!"
|
||||
:service="localService!"
|
||||
:on-test="testConnection"
|
||||
@update:service="handleUpdate"
|
||||
/>
|
||||
</v-card>
|
||||
</v-window-item>
|
||||
|
||||
<v-window-item value="auxiliary">
|
||||
<v-card flat class="pa-6">
|
||||
<ProviderAuxiliaryPanel
|
||||
v-if="localProvider && localService"
|
||||
:provider="localProvider!"
|
||||
:service="localService!"
|
||||
@update:service="handleUpdate"
|
||||
/>
|
||||
</v-card>
|
||||
</v-window-item>
|
||||
|
||||
<v-window-item value="protocol">
|
||||
<v-card flat class="pa-6">
|
||||
<ProviderProtocolPanel
|
||||
v-if="localProvider && localService"
|
||||
:provider="localProvider"
|
||||
:service="localService"
|
||||
:provider="localProvider!"
|
||||
:service="localService!"
|
||||
@update:service="handleUpdate"
|
||||
/>
|
||||
</v-card>
|
||||
@@ -277,8 +298,8 @@ async function saveAccount() {
|
||||
<v-card flat class="pa-6">
|
||||
<ProviderAuthPanel
|
||||
v-if="localProvider && localService"
|
||||
:provider="localProvider"
|
||||
:service="localService"
|
||||
:provider="localProvider!"
|
||||
:service="localService!"
|
||||
@update:service="handleUpdate"
|
||||
/>
|
||||
</v-card>
|
||||
|
||||
@@ -66,11 +66,11 @@ async function loadProviderPanel() {
|
||||
panelLoading.value = true
|
||||
|
||||
// retrieve panel from integration store
|
||||
const panel = integrationStore.getItems('mail_account_auth_panels').find((panel: any) => {
|
||||
const panel = integrationStore.getItems('mail_provider_panels_auth').find((panel: any) => {
|
||||
return panel.id === providerIdentifier || panel.id.endsWith(`.${providerIdentifier}`)
|
||||
})
|
||||
if (!panel?.component) {
|
||||
console.warn(`No config panel found for provider ID: ${providerIdentifier}`)
|
||||
console.warn(`No panel found for provider ID: ${providerIdentifier}`)
|
||||
panelActive.value = null
|
||||
panelLoading.value = false
|
||||
return
|
||||
@@ -99,19 +99,18 @@ function handleUpdate(service: ServiceObject) {
|
||||
<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' }}.
|
||||
Configure authentication specific settings 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...
|
||||
Loading 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 v-else-if="!panelActive" type="info" variant="tonal">
|
||||
No panel available for this provider.
|
||||
</v-alert>
|
||||
|
||||
<component
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
<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]
|
||||
}>()
|
||||
|
||||
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)
|
||||
|
||||
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 }
|
||||
)
|
||||
|
||||
async function loadProviderPanel() {
|
||||
const providerIdentifier = localProvider.value?.identifier || localService.value?.provider
|
||||
|
||||
if (!providerIdentifier) {
|
||||
panelActive.value = null
|
||||
panelLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
if (panelCache.has(providerIdentifier)) {
|
||||
panelActive.value = panelCache.get(providerIdentifier) || null
|
||||
panelLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
panelLoading.value = true
|
||||
|
||||
const panel = integrationStore.getItems('mail_provider_panels_auxiliary').find((panel: any) => {
|
||||
return panel.id === providerIdentifier || panel.id.endsWith(`.${providerIdentifier}`)
|
||||
})
|
||||
|
||||
if (!panel?.component) {
|
||||
console.warn(`No auxiliary 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 auxiliary 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-auxiliary-panel">
|
||||
<h3 class="text-h6 mb-2">Settings</h3>
|
||||
<p class="text-body-2 text-medium-emphasis mb-6">
|
||||
Configure provider specific settings 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 panel...
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<v-alert v-else-if="!panelActive" type="info" variant="tonal">
|
||||
No panel available for this provider.
|
||||
</v-alert>
|
||||
|
||||
<component
|
||||
v-else
|
||||
:is="panelActive"
|
||||
:service="localService"
|
||||
@update:service="handleUpdate"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -66,11 +66,11 @@ async function loadProviderPanel() {
|
||||
panelLoading.value = true
|
||||
|
||||
// retrieve panel from integration store
|
||||
const panel = integrationStore.getItems('mail_account_protocol_panels').find((panel: any) => {
|
||||
const panel = integrationStore.getItems('mail_provider_panels_protocol').find((panel: any) => {
|
||||
return panel.id === providerIdentifier || panel.id.endsWith(`.${providerIdentifier}`)
|
||||
})
|
||||
if (!panel?.component) {
|
||||
console.warn(`No config panel found for provider ID: ${providerIdentifier}`)
|
||||
console.warn(`No panel found for provider ID: ${providerIdentifier}`)
|
||||
panelActive.value = null
|
||||
panelLoading.value = false
|
||||
return
|
||||
@@ -97,21 +97,20 @@ function handleUpdate(service: ServiceObject) {
|
||||
|
||||
<template>
|
||||
<div class="provider-protocol-panel">
|
||||
<h3 class="text-h6 mb-2">Protocol Configuration</h3>
|
||||
<h3 class="text-h6 mb-2">Protocol</h3>
|
||||
<p class="text-body-2 text-medium-emphasis mb-6">
|
||||
Configure authentication for {{ localProvider?.label || 'this provider' }}.
|
||||
Configure protocol specific settings 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...
|
||||
Loading 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
|
||||
No panel available for this provider.
|
||||
</v-alert>
|
||||
|
||||
<component
|
||||
|
||||
@@ -14,7 +14,7 @@ const selected = ref<string | null>(null)
|
||||
|
||||
// Get provider metadata from integrations
|
||||
const providerMetadata = computed(() => {
|
||||
const metadata = integrationStore.getItems('mail_provider_metadata')
|
||||
const metadata = integrationStore.getItems('mail_provider_details')
|
||||
return metadata.reduce((acc: any, meta: any) => {
|
||||
acc[meta.id] = meta
|
||||
return acc
|
||||
|
||||
Reference in New Issue
Block a user