feat: Mini-Cloud Plattform - komplette Implementierung Phase 0-8

Selbstgehostete Web-Cloud mit Dateiverwaltung, Kalender, Kontakte,
Email-Webclient, Office-Viewer und Passwort-Manager.

Backend (Flask/Python):
- JWT-Auth mit Access/Refresh Tokens, Benutzerverwaltung
- Dateien: Upload/Download, Ordner, Berechtigungen, Share-Links
- Kalender: CRUD, Teilen, iCal-Export, CalDAV well-known URLs
- Kontakte: Adressbuecher, vCard-Export, Teilen
- Email: IMAP/SMTP-Proxy, Multi-Account
- Office-Viewer: DOCX/XLSX/PPTX/PDF Vorschau
- Passwort-Manager: AES-256-GCM clientseitig, KeePass-Import
- Sync-API fuer Desktop/Mobile-Clients
- SQLite mit WAL-Modus

Frontend (Vue 3 + PrimeVue):
- Datei-Explorer mit Breadcrumbs und Share-Dialogen
- Monatskalender mit Event-Verwaltung
- Kontaktliste mit Adressbuch-Sidebar
- Email-Client mit 3-Spalten-Layout
- Passwort-Manager mit TOTP und Passwort-Generator
- Admin-Panel, Settings, oeffentliche Share-Seite

Docker: Multi-Stage Build, Bind Mounts (keine Volumes)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Hacker
2026-04-11 14:53:28 +02:00
parent d4f7e90d0c
commit 62f550c373
56 changed files with 8047 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
<template>
<div class="view-container">
<div class="view-header">
<h2>Einstellungen</h2>
</div>
<div class="settings-section">
<h3>Profil</h3>
<div class="settings-info">
<div class="info-row">
<span class="label">Benutzername:</span>
<span>{{ auth.user?.username }}</span>
</div>
<div class="info-row">
<span class="label">E-Mail:</span>
<span>{{ auth.user?.email || 'Nicht angegeben' }}</span>
</div>
<div class="info-row">
<span class="label">Rolle:</span>
<Tag :value="auth.user?.role" :severity="auth.user?.role === 'admin' ? 'danger' : 'info'" />
</div>
</div>
</div>
<div class="settings-section">
<h3>Passwort aendern</h3>
<form @submit.prevent="handleChangePassword" class="password-form">
<div class="field">
<label>Aktuelles Passwort</label>
<Password v-model="currentPassword" :feedback="false" toggle-mask fluid />
</div>
<div class="field">
<label>Neues Passwort</label>
<Password v-model="newPassword" toggle-mask fluid />
</div>
<div class="field">
<label>Neues Passwort wiederholen</label>
<Password v-model="newPassword2" :feedback="false" toggle-mask fluid />
</div>
<Message v-if="pwError" severity="error" :closable="false">{{ pwError }}</Message>
<Message v-if="pwSuccess" severity="success" :closable="false">{{ pwSuccess }}</Message>
<Button type="submit" label="Passwort aendern" :loading="pwLoading" />
</form>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useAuthStore } from '../stores/auth'
import Password from 'primevue/password'
import Button from 'primevue/button'
import Message from 'primevue/message'
import Tag from 'primevue/tag'
const auth = useAuthStore()
const currentPassword = ref('')
const newPassword = ref('')
const newPassword2 = ref('')
const pwError = ref('')
const pwSuccess = ref('')
const pwLoading = ref(false)
async function handleChangePassword() {
pwError.value = ''
pwSuccess.value = ''
if (newPassword.value !== newPassword2.value) {
pwError.value = 'Neue Passwoerter stimmen nicht ueberein'
return
}
pwLoading.value = true
try {
await auth.changePassword(currentPassword.value, newPassword.value)
pwSuccess.value = 'Passwort erfolgreich geaendert'
currentPassword.value = ''
newPassword.value = ''
newPassword2.value = ''
} catch (err) {
pwError.value = err.response?.data?.error || 'Fehler beim Aendern des Passworts'
} finally {
pwLoading.value = false
}
}
</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;
}
.settings-section h3 { margin: 0 0 1rem; font-size: 1.125rem; }
.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; }
</style>