feat: lots more improvements
JS Unit Tests / test (pull_request) Failing after 29s
Build Test / test (pull_request) Successful in 31s
PHP Unit Tests / test (pull_request) Successful in 1m12s

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-04-25 15:41:16 -04:00
parent 86e4772d45
commit 99a68737d1
26 changed files with 902 additions and 596 deletions
+8 -12
View File
@@ -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
+47 -26
View File
@@ -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>
+6 -7
View File
@@ -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