375 lines
9.9 KiB
Vue
375 lines
9.9 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed, onMounted } from 'vue'
|
|
import { useServicesStore } from '@/stores/servicesStore'
|
|
import AddAccountDialog from '@/components/AddAccountDialog.vue'
|
|
import type { ServiceObject } from '@/models'
|
|
|
|
const servicesStore = useServicesStore()
|
|
|
|
const showAddDialog = ref(false)
|
|
const showEditDialog = ref(false)
|
|
const showDeleteConfirm = ref(false)
|
|
const showTestResult = ref(false)
|
|
const selectedAccount = ref<any>({})
|
|
const loading = ref(false)
|
|
const saving = ref(false)
|
|
const deleting = ref(false)
|
|
const testingId = ref<string | null>(null)
|
|
const testResult = ref<any>(null)
|
|
|
|
const groupedServices = computed(() => servicesStore.servicesByProvider)
|
|
|
|
const hasAccounts = computed(() => servicesStore.has)
|
|
|
|
onMounted(async () => {
|
|
loading.value = true
|
|
try {
|
|
await servicesStore.list()
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
})
|
|
|
|
function getProviderIcon(providerId: string): string {
|
|
const icons: Record<string, string> = {
|
|
'smtp': 'mdi-email-multiple',
|
|
'jmap': 'mdi-api',
|
|
'exchange': 'mdi-microsoft',
|
|
}
|
|
return icons[providerId] || 'mdi-email'
|
|
}
|
|
|
|
function getProviderLabel(providerId: string): string {
|
|
const labels: Record<string, string> = {
|
|
'smtp': 'SMTP/IMAP',
|
|
'jmap': 'JMAP',
|
|
'exchange': 'Microsoft Exchange',
|
|
}
|
|
return labels[providerId] || providerId.toUpperCase()
|
|
}
|
|
|
|
function editAccount(account: any) {
|
|
selectedAccount.value = { ...account }
|
|
showEditDialog.value = true
|
|
}
|
|
|
|
async function saveEdit() {
|
|
saving.value = true
|
|
try {
|
|
await servicesStore.update(
|
|
selectedAccount.value.provider,
|
|
selectedAccount.value.identifier,
|
|
selectedAccount.value
|
|
)
|
|
showEditDialog.value = false
|
|
} catch (error) {
|
|
console.error('Failed to update account:', error)
|
|
} finally {
|
|
saving.value = false
|
|
}
|
|
}
|
|
|
|
function confirmDelete(account: any) {
|
|
selectedAccount.value = account
|
|
showDeleteConfirm.value = true
|
|
}
|
|
|
|
async function deleteAccount() {
|
|
deleting.value = true
|
|
try {
|
|
await servicesStore.delete(
|
|
selectedAccount.value.provider,
|
|
selectedAccount.value.identifier
|
|
)
|
|
showDeleteConfirm.value = false
|
|
selectedAccount.value = {}
|
|
} catch (error) {
|
|
console.error('Failed to delete account:', error)
|
|
} finally {
|
|
deleting.value = false
|
|
}
|
|
}
|
|
|
|
async function testAccount(service: ServiceObject) {
|
|
try {
|
|
const result = await servicesStore.test(
|
|
service.provider,
|
|
service.identifier,
|
|
service.location,
|
|
service.identity
|
|
)
|
|
testResult.value = result
|
|
showTestResult.value = true
|
|
} catch (error: any) {
|
|
testResult.value = {
|
|
success: false,
|
|
message: error.message || 'Connection test failed'
|
|
}
|
|
showTestResult.value = true
|
|
} finally {
|
|
testingId.value = null
|
|
}
|
|
}
|
|
|
|
async function handleAccountSaved() {
|
|
await servicesStore.list()
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<v-container fluid>
|
|
<!-- Page Header -->
|
|
<div class="d-flex align-center justify-space-between mb-6">
|
|
<div>
|
|
<h1 class="text-h4 mb-1">Mail Accounts</h1>
|
|
<p class="text-body-2 text-medium-emphasis">
|
|
Manage your email accounts across all providers
|
|
</p>
|
|
</div>
|
|
<v-btn
|
|
color="primary"
|
|
prepend-icon="mdi-plus"
|
|
@click="showAddDialog = true"
|
|
>
|
|
Add Account
|
|
</v-btn>
|
|
</div>
|
|
|
|
<!-- Loading State -->
|
|
<v-row v-if="loading" class="mt-8">
|
|
<v-col
|
|
v-for="i in 3"
|
|
:key="i"
|
|
cols="12"
|
|
md="6"
|
|
lg="4"
|
|
>
|
|
<v-skeleton-loader type="card" />
|
|
</v-col>
|
|
</v-row>
|
|
|
|
<!-- Empty State -->
|
|
<v-card
|
|
v-else-if="!hasAccounts"
|
|
class="text-center pa-12"
|
|
variant="flat"
|
|
>
|
|
<v-icon
|
|
size="80"
|
|
color="grey-lighten-1"
|
|
class="mb-4"
|
|
>
|
|
mdi-email-off-outline
|
|
</v-icon>
|
|
<h2 class="text-h5 mb-2">No Mail Accounts</h2>
|
|
<p class="text-body-1 text-medium-emphasis mb-6">
|
|
Add your first mail account to start sending and receiving emails
|
|
</p>
|
|
<v-btn
|
|
color="primary"
|
|
size="large"
|
|
prepend-icon="mdi-plus"
|
|
@click="showAddDialog = true"
|
|
>
|
|
Add Your First Account
|
|
</v-btn>
|
|
</v-card>
|
|
|
|
<!-- Accounts by Provider -->
|
|
<template v-else>
|
|
<div
|
|
v-for="(services, providerId) in groupedServices"
|
|
:key="providerId"
|
|
class="mb-8"
|
|
>
|
|
<!-- Provider Header -->
|
|
<div class="d-flex align-center mb-4">
|
|
<v-icon
|
|
:icon="getProviderIcon(providerId)"
|
|
class="mr-2"
|
|
/>
|
|
<h2 class="text-h6">
|
|
{{ getProviderLabel(providerId) }}
|
|
</h2>
|
|
<v-chip
|
|
size="small"
|
|
class="ml-2"
|
|
variant="text"
|
|
>
|
|
{{ services.length }} account{{ services.length !== 1 ? 's' : '' }}
|
|
</v-chip>
|
|
</div>
|
|
|
|
<!-- Service Cards -->
|
|
<v-row>
|
|
<v-col
|
|
v-for="service in services"
|
|
:key="`${service.provider}:${service.identifier}`"
|
|
cols="12"
|
|
md="6"
|
|
lg="4"
|
|
>
|
|
<v-card
|
|
:class="{ 'border-error': !service.enabled }"
|
|
variant="outlined"
|
|
hover
|
|
>
|
|
<v-card-text>
|
|
<div class="d-flex align-center justify-space-between mb-2">
|
|
<div class="d-flex align-center">
|
|
<v-avatar
|
|
size="40"
|
|
:color="service.enabled ? 'primary' : 'grey'"
|
|
class="mr-3"
|
|
>
|
|
<v-icon color="white">
|
|
{{ service.enabled ? 'mdi-email' : 'mdi-email-off' }}
|
|
</v-icon>
|
|
</v-avatar>
|
|
<div>
|
|
<h3 class="text-h6">{{ service.label }}</h3>
|
|
<p class="text-caption text-medium-emphasis">
|
|
{{ service.primaryAddress || (service.identity?.type === 'BA' ? service.identity.identity : 'No email configured') }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<v-chip
|
|
:color="service.enabled ? 'success' : 'error'"
|
|
size="small"
|
|
variant="flat"
|
|
>
|
|
{{ service.enabled ? 'Enabled' : 'Disabled' }}
|
|
</v-chip>
|
|
</div>
|
|
|
|
<!-- Service Stats/Info -->
|
|
<v-divider class="my-3" />
|
|
<div class="text-caption text-medium-emphasis">
|
|
<div class="d-flex align-center mb-1">
|
|
<v-icon size="small" class="mr-1">mdi-identifier</v-icon>
|
|
ID: {{ service.identifier }}
|
|
</div>
|
|
</div>
|
|
</v-card-text>
|
|
|
|
<v-card-actions>
|
|
<v-btn
|
|
variant="text"
|
|
size="small"
|
|
prepend-icon="mdi-pencil"
|
|
@click="editAccount(service)"
|
|
>
|
|
Edit
|
|
</v-btn>
|
|
<v-btn
|
|
variant="text"
|
|
size="small"
|
|
prepend-icon="mdi-connection"
|
|
:loading="testingId === service.identifier"
|
|
@click="testAccount(service)"
|
|
>
|
|
Test
|
|
</v-btn>
|
|
<v-spacer />
|
|
<v-btn
|
|
variant="text"
|
|
size="small"
|
|
color="error"
|
|
icon="mdi-delete"
|
|
@click="confirmDelete(service)"
|
|
/>
|
|
</v-card-actions>
|
|
</v-card>
|
|
</v-col>
|
|
</v-row>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Add Account Dialog -->
|
|
<AddAccountDialog
|
|
v-model="showAddDialog"
|
|
@saved="handleAccountSaved"
|
|
/>
|
|
|
|
<!-- Edit Account Dialog -->
|
|
<v-dialog
|
|
v-model="showEditDialog"
|
|
max-width="600"
|
|
>
|
|
<v-card>
|
|
<v-card-title>Edit Account</v-card-title>
|
|
<v-card-text>
|
|
<v-text-field
|
|
v-model="selectedAccount.label"
|
|
label="Account Name"
|
|
variant="outlined"
|
|
/>
|
|
<v-switch
|
|
v-model="selectedAccount.enabled"
|
|
label="Enable this account"
|
|
color="primary"
|
|
/>
|
|
</v-card-text>
|
|
<v-card-actions>
|
|
<v-spacer />
|
|
<v-btn
|
|
variant="text"
|
|
@click="showEditDialog = false"
|
|
>
|
|
Cancel
|
|
</v-btn>
|
|
<v-btn
|
|
color="primary"
|
|
:loading="saving"
|
|
@click="saveEdit"
|
|
>
|
|
Save
|
|
</v-btn>
|
|
</v-card-actions>
|
|
</v-card>
|
|
</v-dialog>
|
|
|
|
<!-- Delete Confirmation Dialog -->
|
|
<v-dialog
|
|
v-model="showDeleteConfirm"
|
|
max-width="400"
|
|
>
|
|
<v-card>
|
|
<v-card-title class="text-h6">Delete Account?</v-card-title>
|
|
<v-card-text>
|
|
Are you sure you want to delete <strong>{{ selectedAccount?.label }}</strong>?
|
|
This action cannot be undone.
|
|
</v-card-text>
|
|
<v-card-actions>
|
|
<v-spacer />
|
|
<v-btn
|
|
variant="text"
|
|
@click="showDeleteConfirm = false"
|
|
>
|
|
Cancel
|
|
</v-btn>
|
|
<v-btn
|
|
color="error"
|
|
variant="flat"
|
|
:loading="deleting"
|
|
@click="deleteAccount"
|
|
>
|
|
Delete
|
|
</v-btn>
|
|
</v-card-actions>
|
|
</v-card>
|
|
</v-dialog>
|
|
|
|
<!-- Test Result Snackbar -->
|
|
<v-snackbar
|
|
v-model="showTestResult"
|
|
:color="testResult?.success ? 'success' : 'error'"
|
|
:timeout="5000"
|
|
>
|
|
<v-icon start>
|
|
{{ testResult?.success ? 'mdi-check-circle' : 'mdi-alert-circle' }}
|
|
</v-icon>
|
|
{{ testResult?.message || 'Connection test completed' }}
|
|
</v-snackbar>
|
|
</v-container>
|
|
</template> |