feat(nextcloud-files): Modul Dateien mit Nextcloud-Adresse, Verbindungsprüfung und gesicherter Verbindungsschicht
- Migration 20261008180000: NextcloudFilesConfig (Organisation) und NextcloudFilesAccount (Mandant UND Benutzer per Zeilenschutz, nur verschlüsseltes App-Passwort) - Transportschicht ncRequest: feste Pfadanfänge, Segmentcodierung, keine Weiterleitungen, keine Cookies, Zeitgrenzen; Aufrufsperre pausiert den Ursprung nach jedem 429 und sperrt Zugangsschlüssel nach dem ersten 401 - Einstellungen: Adresse speichern/prüfen, Adresswechsel läuft alle Konten ab (mit Bestätigung) - Controller mit Verwalten nur auf Einstellungen, Seed, Modul, Registrierung in Web (Ladefunktion, Symbol folder, Navigation, Layout) und Reiter Einstellungen - RLS-Inventar fortgeschrieben, Testaufbau (tessera-nc-test) und E2E-Skripte Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
'use client';
|
||||
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { TabBar } from '@/components/accounting/tab-bar';
|
||||
import { SettingsSection } from '@/components/control-center/settings-section';
|
||||
import { PageHeader } from '@/components/layout/page-header';
|
||||
import { getNextcloudFilesStatus, type NextcloudFilesStatus } from '@/lib/nextcloud-files-api';
|
||||
import { useCanManageModule } from '@/lib/use-module-capability';
|
||||
import { SettingsTab } from './components/SettingsTab';
|
||||
|
||||
type TabId = 'files' | 'settings';
|
||||
|
||||
/**
|
||||
* Dateien (quick-261008-mzu): die Nextcloud-Dateien jedes Benutzers in Tessera.
|
||||
* Die Adresse der Nextcloud stellen Administratoren und Benutzer mit der
|
||||
* Freigabestufe Verwalten im Reiter "Einstellungen" ein; alle anderen sehen nur
|
||||
* den Reiter "Dateien" ohne Reiterleiste — bindend ist allein die API. Der
|
||||
* Abschnitt `nextcloud-files-main` ist die Stelle, an der das Verbinden des
|
||||
* eigenen Kontos und der Dateibrowser eingehaengt werden.
|
||||
*/
|
||||
export default function NextcloudFilesPage() {
|
||||
const t = useTranslations('nextcloudFiles');
|
||||
const canManage = useCanManageModule('nextcloud-files') === true;
|
||||
|
||||
const [status, setStatus] = useState<NextcloudFilesStatus | null>(null);
|
||||
const [statusError, setStatusError] = useState(false);
|
||||
const [tab, setTab] = useState<TabId>('files');
|
||||
|
||||
const reloadStatus = useCallback(async () => {
|
||||
try {
|
||||
setStatus(await getNextcloudFilesStatus());
|
||||
setStatusError(false);
|
||||
} catch {
|
||||
setStatusError(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
void reloadStatus();
|
||||
}, [reloadStatus]);
|
||||
|
||||
const tabs: { id: TabId; label: string }[] = [{ id: 'files', label: t('tabs.files') }];
|
||||
if (canManage) tabs.push({ id: 'settings', label: t('tabs.settings') });
|
||||
const activeTab: TabId = tabs.some((x) => x.id === tab) ? tab : 'files';
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-6xl space-y-6 p-3 sm:p-6">
|
||||
<PageHeader moduleSlug="nextcloud-files" title={t('title')} description={t('description')} />
|
||||
{statusError && (
|
||||
<p role="alert" className="text-sm text-destructive">
|
||||
{t('errors.loadStatus')}
|
||||
</p>
|
||||
)}
|
||||
{canManage && <TabBar tabs={tabs} active={activeTab} onChange={setTab} />}
|
||||
{activeTab === 'files' && status && !status.configured && (
|
||||
<SettingsSection title={t('notConfigured.title')}>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{canManage ? t('notConfigured.managerHint') : t('notConfigured.userHint')}
|
||||
</p>
|
||||
{canManage && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setTab('settings')}
|
||||
className="mt-3 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:opacity-90"
|
||||
>
|
||||
{t('notConfigured.goToSettings')}
|
||||
</button>
|
||||
)}
|
||||
</SettingsSection>
|
||||
)}
|
||||
{activeTab === 'files' && status?.configured && (
|
||||
<section data-testid="nextcloud-files-main" className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('main.server', { host: status.host ?? '' })}
|
||||
</p>
|
||||
</section>
|
||||
)}
|
||||
{activeTab === 'settings' && canManage && (
|
||||
<SettingsTab onChanged={() => void reloadStatus()} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user