feat(aether): initial commit – self-hosted Cloud, Phasen 1–7

Go-Backend (chi, pgx, golang-migrate) + React/TS-Frontend als PWA.
Enthalten: Auth inkl. OIDC, Dateien mit Versionen und Papierkorb,
Freigaben (Links + intern), WebDAV, Gruppenordner, Admin/Theming,
Volltextsuche mit OCR, eigener PDF-Betrachter und WOPI-Office
(Collabora/OnlyOffice).

Nicht im Repo: deploy/.env (Sitzungsschlüssel, DB- und Admin-Passwort)
sowie alle Daten- und Build-Verzeichnisse – siehe .gitignore.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Opus 5 committed 2026-08-24 09:25:36 +02:00
commit 31a027c93a
225 files changed
+38042

No files matched your search

+57
View File
@@ -0,0 +1,57 @@
interface UploadItem {
id: string
name: string
progress: number // 0-100
status: 'uploading' | 'done' | 'error'
error?: string
}
interface Props {
items: UploadItem[]
onDismiss: (id: string) => void
}
export type { UploadItem }
export default function UploadQueue({ items, onDismiss }: Props) {
if (items.length === 0) return null
return (
<div className="fixed bottom-4 right-4 w-72 space-y-2 z-40">
{items.map(item => (
<div
key={item.id}
className="rounded-xl p-3 text-sm shadow-lg border"
style={{ background: 'var(--color-surface)', borderColor: 'var(--color-border)' }}>
<div className="flex items-center justify-between mb-1.5">
<span className="truncate font-medium text-xs" style={{ color: 'var(--color-text)', maxWidth: 180 }}>
{item.name}
</span>
{item.status !== 'uploading' && (
<button onClick={() => onDismiss(item.id)}
className="ml-2 opacity-50 hover:opacity-100 text-xs shrink-0"
style={{ color: 'var(--color-text-muted)' }}>✕</button>
)}
</div>
{item.status === 'uploading' && (
<div className="h-1.5 rounded-full overflow-hidden" style={{ background: 'var(--color-surface-2)' }}>
<div
className="h-full rounded-full transition-all"
style={{
width: `${item.progress}%`,
background: 'linear-gradient(90deg, var(--color-primary), var(--color-accent))',
}} />
</div>
)}
{item.status === 'done' && (
<p style={{ color: 'var(--color-success)' }} className="text-xs">✓ Uploaded</p>
)}
{item.status === 'error' && (
<p style={{ color: 'var(--color-danger)' }} className="text-xs truncate">✕ {item.error}</p>
)}
</div>
))}
</div>
)
}