Files
mail_manager/src/components/steps/TestAndSavePanel.vue
T
Sebastian bea7c1d0fb
Build Test / test (pull_request) Successful in 37s
JS Unit Tests / test (pull_request) Failing after 37s
PHP Unit Tests / test (pull_request) Successful in 59s
refactor: service address
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
2026-06-16 13:31:23 -04:00

297 lines
9.2 KiB
Vue

<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>
<p class="text-body-2 text-medium-emphasis mb-6">
Test your connection and save the account configuration
</p>
<!-- Configuration Summary -->
<v-card variant="outlined" class="mb-4">
<v-card-subtitle>Configuration Summary</v-card-subtitle>
<v-card-text>
<v-list density="compact" class="bg-transparent">
<!-- Account Label -->
<v-list-item>
<template #prepend>
<v-icon>mdi-label</v-icon>
</template>
<v-list-item-title>Account Name</v-list-item-title>
<v-list-item-subtitle>{{ localService.label || localService.primaryAddress?.address || 'New Account' }}</v-list-item-subtitle>
</v-list-item>
<!-- Email Address -->
<v-list-item>
<template #prepend>
<v-icon>mdi-email</v-icon>
</template>
<v-list-item-title>Email Address</v-list-item-title>
<v-list-item-subtitle>{{ localService.primaryAddress?.address }}</v-list-item-subtitle>
</v-list-item>
<!-- Provider -->
<v-list-item>
<template #prepend>
<v-icon>mdi-cloud</v-icon>
</template>
<v-list-item-title>Provider</v-list-item-title>
<v-list-item-subtitle>{{ localProvider.label }}</v-list-item-subtitle>
</v-list-item>
<!-- Location Details -->
<template v-if="serviceLocation">
<v-divider class="my-2" />
<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>
{{ serviceLocation.scheme }}://{{ serviceLocation.host }}:{{ serviceLocation.port }}{{ serviceLocation.path || '' }}
</v-list-item-subtitle>
</v-list-item>
<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>{{ 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>{{ serviceLocation.encryption.toUpperCase() }}</v-list-item-subtitle>
</v-list-item>
</template>
<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>
{{ serviceLocation.inboundHost }}:{{ serviceLocation.inboundPort }} ({{ serviceLocation.inboundEncryption.toUpperCase() }})
</v-list-item-subtitle>
</v-list-item>
<v-list-item>
<template #prepend>
<v-icon>mdi-send</v-icon>
</template>
<v-list-item-title>Outgoing Mail</v-list-item-title>
<v-list-item-subtitle>
{{ serviceLocation.outboundHost }}:{{ serviceLocation.outboundPort }} ({{ serviceLocation.outboundEncryption.toUpperCase() }})
</v-list-item-subtitle>
</v-list-item>
</template>
</template>
<!-- Authentication Method -->
<v-divider class="my-2" />
<v-list-item>
<template #prepend>
<v-icon>{{ getAuthIcon(serviceIdentity?.type) }}</v-icon>
</template>
<v-list-item-title>Authentication</v-list-item-title>
<v-list-item-subtitle>{{ getAuthLabel(serviceIdentity?.type) }}</v-list-item-subtitle>
</v-list-item>
</v-list>
</v-card-text>
</v-card>
<!-- Account Label Input -->
<v-text-field
v-model="localService.label"
label="Account Name"
variant="outlined"
hint="A friendly name for this account (e.g., Work Email)"
persistent-hint
prepend-inner-icon="mdi-label"
class="mb-4"
/>
<!-- Enable Account Toggle -->
<v-switch
v-model="localService.enabled"
label="Enable this account"
color="primary"
class="mb-4"
/>
<!-- Test Connection -->
<div class="mb-4">
<v-btn
color="primary"
variant="outlined"
size="large"
block
:loading="testing"
:disabled="testSuccess"
prepend-icon="mdi-connection"
@click="handleTest"
>
{{ testSuccess ? 'Connection Tested Successfully' : 'Test Connection' }}
</v-btn>
<!-- Test Result -->
<v-alert
v-if="testResult"
:type="testResult.success ? 'success' : 'error'"
variant="tonal"
class="mt-4"
>
<div class="d-flex align-center">
<v-icon
:icon="testResult.success ? 'mdi-check-circle' : 'mdi-alert-circle'"
class="mr-2"
/>
<div class="flex-grow-1">
<div class="font-weight-bold">{{ testResult.message }}</div>
<div v-if="testResult.details?.latency" class="text-caption">
Response time: {{ testResult.details.latency }}ms
</div>
<div v-if="testResult.details?.protocols" class="text-caption">
Protocols: {{ Object.keys(testResult.details.protocols).join(', ') }}
</div>
<div v-if="testResult.details?.capabilities" class="text-caption mt-1">
Capabilities: {{ formatCapabilities(testResult.details.capabilities) }}
</div>
</div>
</div>
</v-alert>
</div>
<!-- Save Warning -->
<v-alert
v-if="!testSuccess"
type="warning"
variant="tonal"
density="compact"
>
<v-icon start>mdi-alert</v-icon>
Please test the connection before saving
</v-alert>
</div>
</template>