241 lines
6.4 KiB
Vue
241 lines
6.4 KiB
Vue
<script setup lang="ts">
|
|
import { computed } from 'vue'
|
|
import type { ProviderDiscoveryStatus } from '@MailManager/types'
|
|
|
|
const props = defineProps<{
|
|
address: string
|
|
status: Record<string, ProviderDiscoveryStatus>
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
'select': [providerId: string]
|
|
'advanced': [providerId: string]
|
|
'manual': []
|
|
'back': []
|
|
}>()
|
|
|
|
const sortedStatus = computed(() => {
|
|
const statusArray = Object.values(props.status)
|
|
const order: Record<string, number> = { success: 0, discovering: 1, pending: 2, failed: 3 }
|
|
return statusArray.sort((a, b) => order[a.status] - order[b.status])
|
|
})
|
|
|
|
const progressPercent = computed(() => {
|
|
const total = Object.keys(props.status).length
|
|
if (total === 0) return 0
|
|
|
|
const completed = Object.values(props.status).filter(
|
|
s => s.status === 'success' || s.status === 'failed'
|
|
).length
|
|
|
|
return (completed / total) * 100
|
|
})
|
|
|
|
const isDiscovering = computed(() => {
|
|
return Object.values(props.status).some(
|
|
s => s.status === 'discovering' || s.status === 'pending'
|
|
)
|
|
})
|
|
|
|
const successCount = computed(() => {
|
|
return Object.values(props.status).filter(s => s.status === 'success').length
|
|
})
|
|
|
|
function getStatusColor(status: string): string {
|
|
const colors: Record<string, string> = {
|
|
success: 'success',
|
|
failed: 'error',
|
|
discovering: 'primary',
|
|
pending: 'grey'
|
|
}
|
|
return colors[status] || 'grey'
|
|
}
|
|
|
|
function getProviderLabel(providerId: string): string {
|
|
const labels: Record<string, string> = {
|
|
jmap: 'JMAP',
|
|
smtp: 'SMTP/IMAP',
|
|
imap: 'IMAP',
|
|
exchange: 'Microsoft Exchange'
|
|
}
|
|
return labels[providerId] || providerId.toUpperCase()
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="discovery-status-step">
|
|
<h3 class="text-h6 mb-2">Discovering Mail Servers</h3>
|
|
<p class="text-body-2 text-medium-emphasis mb-6">
|
|
Testing {{ address }} across multiple providers...
|
|
</p>
|
|
|
|
<!-- Overall Progress -->
|
|
<v-progress-linear
|
|
:model-value="progressPercent"
|
|
color="primary"
|
|
height="8"
|
|
rounded
|
|
class="mb-6"
|
|
/>
|
|
|
|
<!-- Provider Status List -->
|
|
<v-list class="provider-status-list">
|
|
<v-list-item
|
|
v-for="status in sortedStatus"
|
|
:key="status.provider"
|
|
class="provider-status-item mb-2"
|
|
:class="`status-${status.status}`"
|
|
>
|
|
<template #prepend>
|
|
<v-avatar :color="getStatusColor(status.status)" size="40">
|
|
<v-icon v-if="status.status === 'success'" color="white">
|
|
mdi-check
|
|
</v-icon>
|
|
<v-icon v-else-if="status.status === 'failed'" color="white">
|
|
mdi-close
|
|
</v-icon>
|
|
<v-progress-circular
|
|
v-else-if="status.status === 'discovering'"
|
|
indeterminate
|
|
size="24"
|
|
width="3"
|
|
color="white"
|
|
/>
|
|
<v-icon v-else color="white">
|
|
mdi-clock-outline
|
|
</v-icon>
|
|
</v-avatar>
|
|
</template>
|
|
|
|
<v-list-item-title class="d-flex align-center gap-2">
|
|
<span class="font-weight-medium">{{ getProviderLabel(status.provider) }}</span>
|
|
<v-chip
|
|
v-if="status.status === 'success'"
|
|
size="x-small"
|
|
color="success"
|
|
variant="flat"
|
|
>
|
|
Found
|
|
</v-chip>
|
|
<v-chip
|
|
v-else-if="status.status === 'failed'"
|
|
size="x-small"
|
|
color="error"
|
|
variant="flat"
|
|
>
|
|
{{ status.error || 'Failed' }}
|
|
</v-chip>
|
|
<v-chip
|
|
v-else-if="status.status === 'discovering'"
|
|
size="x-small"
|
|
color="primary"
|
|
variant="flat"
|
|
>
|
|
Testing...
|
|
</v-chip>
|
|
</v-list-item-title>
|
|
|
|
<v-list-item-subtitle v-if="status.status === 'success' && status.metadata">
|
|
<div class="text-caption">
|
|
<v-icon size="14" class="mr-1">mdi-server</v-icon>
|
|
{{ status.metadata.host }}{{ status.metadata.port ? ':' + status.metadata.port : '' }}
|
|
<span v-if="status.metadata.protocol" class="ml-2">
|
|
<v-icon size="14" class="mr-1">mdi-shield-lock</v-icon>
|
|
{{ status.metadata.protocol.toUpperCase() }}
|
|
</span>
|
|
</div>
|
|
</v-list-item-subtitle>
|
|
|
|
<template #append>
|
|
<div v-if="status.status === 'success'" class="d-flex gap-2">
|
|
<v-btn
|
|
size="small"
|
|
variant="tonal"
|
|
color="primary"
|
|
prepend-icon="mdi-check"
|
|
@click="$emit('select', status.provider)"
|
|
>
|
|
Select
|
|
</v-btn>
|
|
<v-tooltip text="Advanced configuration">
|
|
<template #activator="{ props }">
|
|
<v-btn
|
|
v-bind="props"
|
|
size="small"
|
|
variant="outlined"
|
|
icon="mdi-tune"
|
|
@click="$emit('advanced', status.provider)"
|
|
/>
|
|
</template>
|
|
</v-tooltip>
|
|
</div>
|
|
</template>
|
|
</v-list-item>
|
|
</v-list>
|
|
|
|
<!-- No Results Message -->
|
|
<v-alert
|
|
v-if="!isDiscovering && successCount === 0"
|
|
type="warning"
|
|
variant="tonal"
|
|
class="mt-6"
|
|
>
|
|
<template #prepend>
|
|
<v-icon>mdi-alert</v-icon>
|
|
</template>
|
|
<div>
|
|
<div class="font-weight-medium mb-1">No configurations found</div>
|
|
<div class="text-caption">
|
|
We couldn't automatically discover server settings for {{ address }}.
|
|
You can try manual configuration instead.
|
|
</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>
|
|
|
|
<style scoped>
|
|
.provider-status-item {
|
|
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
|
border-radius: 8px;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.provider-status-item.status-success {
|
|
background-color: rgba(var(--v-theme-success), 0.05);
|
|
}
|
|
|
|
.provider-status-item.status-discovering {
|
|
background-color: rgba(var(--v-theme-primary), 0.05);
|
|
}
|
|
|
|
.gap-2 {
|
|
gap: 8px;
|
|
}
|
|
|
|
.gap-3 {
|
|
gap: 12px;
|
|
}
|
|
|
|
.manual-action-btn {
|
|
background-color: rgba(var(--v-theme-on-surface), 0.06);
|
|
}
|
|
</style>
|