fix: Admin Edit-Button + Email-Kontenverwaltung in Einstellungen
- Fix: Admin Edit/Delete-Buttons reagieren jetzt (@click.stop) - Neu: Email-Kontenverwaltung in den Benutzer-Einstellungen - IMAP/SMTP-Server, Port, SSL, Anmeldedaten konfigurieren - Konten hinzufuegen, bearbeiten, loeschen - Verbindungstest-Button - Standard-Konto festlegen Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -42,8 +42,8 @@
|
||||
<Column field="storage_quota_mb" header="Quota (MB)" />
|
||||
<Column header="Aktionen" style="width: 120px">
|
||||
<template #body="{ data }">
|
||||
<Button icon="pi pi-pencil" text rounded size="small" @click="openEditUser(data)" />
|
||||
<Button icon="pi pi-trash" text rounded size="small" severity="danger" @click="confirmDeleteUser(data)" />
|
||||
<Button icon="pi pi-pencil" text rounded size="small" @click.stop="openEditUser(data)" />
|
||||
<Button icon="pi pi-trash" text rounded size="small" severity="danger" @click.stop="confirmDeleteUser(data)" />
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<h2>Einstellungen</h2>
|
||||
</div>
|
||||
|
||||
<!-- Profile -->
|
||||
<div class="settings-section">
|
||||
<h3>Profil</h3>
|
||||
<div class="settings-info">
|
||||
@@ -22,6 +23,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Change Password -->
|
||||
<div class="settings-section">
|
||||
<h3>Passwort aendern</h3>
|
||||
<form @submit.prevent="handleChangePassword" class="password-form">
|
||||
@@ -42,18 +44,130 @@
|
||||
<Button type="submit" label="Passwort aendern" :loading="pwLoading" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Email Accounts -->
|
||||
<div class="settings-section">
|
||||
<div class="section-header">
|
||||
<h3>E-Mail-Konten</h3>
|
||||
<Button icon="pi pi-plus" label="Konto hinzufuegen" size="small" @click="openNewAccount" />
|
||||
</div>
|
||||
|
||||
<div v-if="!emailAccounts.length" class="empty-hint">
|
||||
<i class="pi pi-envelope"></i>
|
||||
<p>Keine E-Mail-Konten konfiguriert. Fuege ein Konto hinzu, um den E-Mail-Client zu nutzen.</p>
|
||||
</div>
|
||||
|
||||
<div v-for="account in emailAccounts" :key="account.id" class="email-account-card">
|
||||
<div class="account-info">
|
||||
<div class="account-name">
|
||||
<i class="pi pi-envelope"></i>
|
||||
<strong>{{ account.display_name }}</strong>
|
||||
<Tag v-if="account.is_default" value="Standard" size="small" severity="info" />
|
||||
</div>
|
||||
<div class="account-detail">{{ account.email_address }}</div>
|
||||
<div class="account-detail">IMAP: {{ account.imap_host }}:{{ account.imap_port }} | SMTP: {{ account.smtp_host }}:{{ account.smtp_port }}</div>
|
||||
</div>
|
||||
<div class="account-actions">
|
||||
<Button icon="pi pi-pencil" text size="small" @click="openEditAccount(account)" />
|
||||
<Button icon="pi pi-check-circle" text size="small" title="Verbindung testen" @click="testAccount(account)" />
|
||||
<Button icon="pi pi-trash" text size="small" severity="danger" @click="confirmDeleteAccount(account)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Email Account Dialog -->
|
||||
<Dialog v-model:visible="showAccountDialog" :header="editingAccount ? 'E-Mail-Konto bearbeiten' : 'Neues E-Mail-Konto'" modal :style="{ width: '550px' }">
|
||||
<div class="field">
|
||||
<label>Anzeigename</label>
|
||||
<InputText v-model="accountForm.display_name" placeholder="z.B. Arbeit, Privat" fluid autofocus />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>E-Mail-Adresse</label>
|
||||
<InputText v-model="accountForm.email_address" type="email" placeholder="name@beispiel.de" fluid />
|
||||
</div>
|
||||
|
||||
<h4 class="section-title">Posteingang (IMAP)</h4>
|
||||
<div class="field-row">
|
||||
<div class="field flex-grow">
|
||||
<label>IMAP-Server</label>
|
||||
<InputText v-model="accountForm.imap_host" placeholder="imap.beispiel.de" fluid />
|
||||
</div>
|
||||
<div class="field" style="width: 100px">
|
||||
<label>Port</label>
|
||||
<InputText v-model.number="accountForm.imap_port" type="number" fluid />
|
||||
</div>
|
||||
<div class="field" style="width: 80px">
|
||||
<label>SSL</label>
|
||||
<InputSwitch v-model="accountForm.imap_ssl" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="section-title">Postausgang (SMTP)</h4>
|
||||
<div class="field-row">
|
||||
<div class="field flex-grow">
|
||||
<label>SMTP-Server</label>
|
||||
<InputText v-model="accountForm.smtp_host" placeholder="smtp.beispiel.de" fluid />
|
||||
</div>
|
||||
<div class="field" style="width: 100px">
|
||||
<label>Port</label>
|
||||
<InputText v-model.number="accountForm.smtp_port" type="number" fluid />
|
||||
</div>
|
||||
<div class="field" style="width: 80px">
|
||||
<label>SSL</label>
|
||||
<InputSwitch v-model="accountForm.smtp_ssl" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="section-title">Anmeldedaten</h4>
|
||||
<div class="field">
|
||||
<label>Benutzername</label>
|
||||
<InputText v-model="accountForm.username" placeholder="Oft die E-Mail-Adresse" fluid />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ editingAccount ? 'Passwort (leer = nicht aendern)' : 'Passwort' }}</label>
|
||||
<Password v-model="accountForm.password" :feedback="false" toggle-mask fluid />
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label><input type="checkbox" v-model="accountForm.is_default" /> Als Standard-Konto verwenden</label>
|
||||
</div>
|
||||
|
||||
<Message v-if="accountError" severity="error" :closable="false">{{ accountError }}</Message>
|
||||
|
||||
<template #footer>
|
||||
<Button label="Abbrechen" text @click="showAccountDialog = false" />
|
||||
<Button :label="editingAccount ? 'Speichern' : 'Hinzufuegen'" @click="saveAccount" :loading="accountSaving" />
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<!-- Delete Account Confirm -->
|
||||
<Dialog v-model:visible="showDeleteAccount" header="E-Mail-Konto loeschen" modal :style="{ width: '400px' }">
|
||||
<p>Moechtest du das Konto <strong>{{ deleteAccountTarget?.display_name }}</strong> ({{ deleteAccountTarget?.email_address }}) wirklich loeschen?</p>
|
||||
<template #footer>
|
||||
<Button label="Abbrechen" text @click="showDeleteAccount = false" />
|
||||
<Button label="Loeschen" severity="danger" @click="doDeleteAccount" />
|
||||
</template>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
import { useToast } from 'primevue/usetoast'
|
||||
import apiClient from '../api/client'
|
||||
import Password from 'primevue/password'
|
||||
import Button from 'primevue/button'
|
||||
import Message from 'primevue/message'
|
||||
import Tag from 'primevue/tag'
|
||||
import Dialog from 'primevue/dialog'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import InputSwitch from 'primevue/inputswitch'
|
||||
|
||||
const auth = useAuthStore()
|
||||
const toast = useToast()
|
||||
|
||||
// --- Password change ---
|
||||
const currentPassword = ref('')
|
||||
const newPassword = ref('')
|
||||
const newPassword2 = ref('')
|
||||
@@ -83,22 +197,153 @@ async function handleChangePassword() {
|
||||
pwLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// --- Email Accounts ---
|
||||
const emailAccounts = ref([])
|
||||
const showAccountDialog = ref(false)
|
||||
const editingAccount = ref(null)
|
||||
const accountForm = ref({
|
||||
display_name: '', email_address: '', imap_host: '', imap_port: 993, imap_ssl: true,
|
||||
smtp_host: '', smtp_port: 587, smtp_ssl: true, username: '', password: '', is_default: false,
|
||||
})
|
||||
const accountError = ref('')
|
||||
const accountSaving = ref(false)
|
||||
const showDeleteAccount = ref(false)
|
||||
const deleteAccountTarget = ref(null)
|
||||
|
||||
function getEncKey() { return auth.masterKeySalt || '' }
|
||||
|
||||
async function loadAccounts() {
|
||||
try {
|
||||
const res = await apiClient.get('/email/accounts')
|
||||
emailAccounts.value = res.data
|
||||
} catch { emailAccounts.value = [] }
|
||||
}
|
||||
|
||||
function openNewAccount() {
|
||||
editingAccount.value = null
|
||||
accountForm.value = {
|
||||
display_name: '', email_address: '', imap_host: '', imap_port: 993, imap_ssl: true,
|
||||
smtp_host: '', smtp_port: 587, smtp_ssl: true, username: '', password: '', is_default: false,
|
||||
}
|
||||
accountError.value = ''
|
||||
showAccountDialog.value = true
|
||||
}
|
||||
|
||||
function openEditAccount(account) {
|
||||
editingAccount.value = account
|
||||
accountForm.value = {
|
||||
display_name: account.display_name,
|
||||
email_address: account.email_address,
|
||||
imap_host: account.imap_host,
|
||||
imap_port: account.imap_port,
|
||||
imap_ssl: account.imap_ssl,
|
||||
smtp_host: account.smtp_host,
|
||||
smtp_port: account.smtp_port,
|
||||
smtp_ssl: account.smtp_ssl,
|
||||
username: account.username,
|
||||
password: '',
|
||||
is_default: account.is_default,
|
||||
}
|
||||
accountError.value = ''
|
||||
showAccountDialog.value = true
|
||||
}
|
||||
|
||||
async function saveAccount() {
|
||||
accountError.value = ''
|
||||
accountSaving.value = true
|
||||
try {
|
||||
const payload = { ...accountForm.value }
|
||||
const headers = { 'X-Encryption-Key': getEncKey() }
|
||||
|
||||
if (editingAccount.value) {
|
||||
if (!payload.password) delete payload.password
|
||||
await apiClient.put(`/email/accounts/${editingAccount.value.id}`, payload, { headers })
|
||||
toast.add({ severity: 'success', summary: 'Konto aktualisiert', life: 3000 })
|
||||
} else {
|
||||
if (!payload.password) {
|
||||
accountError.value = 'Passwort erforderlich'
|
||||
return
|
||||
}
|
||||
await apiClient.post('/email/accounts', payload, { headers })
|
||||
toast.add({ severity: 'success', summary: 'Konto hinzugefuegt', life: 3000 })
|
||||
}
|
||||
showAccountDialog.value = false
|
||||
await loadAccounts()
|
||||
await auth.fetchMe()
|
||||
} catch (err) {
|
||||
accountError.value = err.response?.data?.error || 'Fehler beim Speichern'
|
||||
} finally {
|
||||
accountSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function testAccount(account) {
|
||||
try {
|
||||
await apiClient.post(`/email/accounts/${account.id}/test`, {}, {
|
||||
headers: { 'X-Encryption-Key': getEncKey() },
|
||||
})
|
||||
toast.add({ severity: 'success', summary: 'Verbindung erfolgreich', detail: `${account.display_name}: IMAP-Verbindung OK`, life: 5000 })
|
||||
} catch (err) {
|
||||
toast.add({ severity: 'error', summary: 'Verbindung fehlgeschlagen', detail: err.response?.data?.error, life: 8000 })
|
||||
}
|
||||
}
|
||||
|
||||
function confirmDeleteAccount(account) {
|
||||
deleteAccountTarget.value = account
|
||||
showDeleteAccount.value = true
|
||||
}
|
||||
|
||||
async function doDeleteAccount() {
|
||||
if (!deleteAccountTarget.value) return
|
||||
try {
|
||||
await apiClient.delete(`/email/accounts/${deleteAccountTarget.value.id}`)
|
||||
showDeleteAccount.value = false
|
||||
toast.add({ severity: 'success', summary: 'Konto geloescht', life: 3000 })
|
||||
await loadAccounts()
|
||||
await auth.fetchMe()
|
||||
} catch (err) {
|
||||
toast.add({ severity: 'error', summary: 'Fehler', detail: err.response?.data?.error, life: 5000 })
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadAccounts)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.view-container { padding: 1.5rem; }
|
||||
.view-header h2 { margin: 0 0 1.5rem; }
|
||||
.settings-section {
|
||||
background: var(--p-surface-0);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
background: var(--p-surface-0); border-radius: 8px; padding: 1.5rem; margin-bottom: 1.5rem;
|
||||
}
|
||||
.settings-section h3 { margin: 0 0 1rem; font-size: 1.125rem; }
|
||||
.section-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem; }
|
||||
.section-header h3 { margin: 0; }
|
||||
.settings-info { display: flex; flex-direction: column; gap: 0.5rem; }
|
||||
.info-row { display: flex; align-items: center; gap: 0.5rem; }
|
||||
.info-row .label { font-weight: 500; min-width: 120px; }
|
||||
.password-form { max-width: 400px; }
|
||||
.password-form .field { margin-bottom: 1rem; }
|
||||
.password-form .field label { display: block; margin-bottom: 0.5rem; font-weight: 500; font-size: 0.875rem; }
|
||||
|
||||
.empty-hint {
|
||||
display: flex; align-items: center; gap: 0.75rem; padding: 1.5rem;
|
||||
background: var(--p-surface-50); border-radius: 6px; color: var(--p-text-muted-color);
|
||||
}
|
||||
.empty-hint i { font-size: 1.5rem; }
|
||||
.empty-hint p { margin: 0; }
|
||||
|
||||
.email-account-card {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 1rem; border: 1px solid var(--p-surface-200); border-radius: 8px; margin-bottom: 0.5rem;
|
||||
}
|
||||
.account-name { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.25rem; }
|
||||
.account-detail { font-size: 0.825rem; color: var(--p-text-muted-color); }
|
||||
.account-actions { display: flex; gap: 0; flex-shrink: 0; }
|
||||
|
||||
.section-title { margin: 1rem 0 0.5rem; font-size: 0.95rem; font-weight: 600; }
|
||||
.field { margin-bottom: 1rem; }
|
||||
.field label { display: block; margin-bottom: 0.5rem; font-weight: 500; font-size: 0.875rem; }
|
||||
.field-row { display: flex; gap: 0.75rem; align-items: flex-end; }
|
||||
.flex-grow { flex: 1; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user