176 lines
8.9 KiB
TypeScript
176 lines
8.9 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { Code, BookOpen, FileJson, HardDrive } from 'lucide-react';
|
|
import { SettingsMenuOrderPage } from './SettingsMenuOrder';
|
|
import { SettingsThemePage } from './SettingsTheme';
|
|
import { SettingsPluginsPage } from './SettingsPlugins';
|
|
|
|
export function SettingsSystemPage() {
|
|
const { t } = useTranslation();
|
|
const [activeTab, setActiveTab] = useState('menu');
|
|
|
|
const tabs = [
|
|
{ key: 'menu', label: t('settings.menuOrder', 'Menü') },
|
|
{ key: 'theme', label: t('settings.theme', 'Theme') },
|
|
{ key: 'plugins', label: t('settings.plugins', 'Plugins') },
|
|
{ key: 'storage', label: t('settings.storage', 'Storage') },
|
|
{ key: 'entwickler', label: t('settings.developer', 'Entwickler') },
|
|
];
|
|
|
|
return (
|
|
<div className="space-y-6" data-testid="settings-system-page">
|
|
<h1 className="text-2xl font-bold text-secondary-900">System</h1>
|
|
|
|
<div className="border-b border-secondary-200">
|
|
<nav className="flex gap-4" role="tablist" aria-label="System Tabs">
|
|
{tabs.map((tab) => (
|
|
<button
|
|
key={tab.key}
|
|
onClick={() => setActiveTab(tab.key)}
|
|
role="tab"
|
|
aria-selected={activeTab === tab.key}
|
|
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
|
|
activeTab === tab.key
|
|
? 'border-primary-500 text-primary-700'
|
|
: 'border-transparent text-secondary-600 hover:text-secondary-900 hover:border-secondary-300'
|
|
}`}
|
|
>
|
|
{tab.label}
|
|
</button>
|
|
))}
|
|
</nav>
|
|
</div>
|
|
|
|
<div role="tabpanel">
|
|
{activeTab === 'menu' && <SettingsMenuOrderPage />}
|
|
{activeTab === 'theme' && <SettingsThemePage />}
|
|
{activeTab === 'plugins' && <SettingsPluginsPage />}
|
|
{activeTab === 'storage' && (
|
|
<div className="space-y-6" data-testid="settings-storage-section">
|
|
<div className="bg-white rounded-lg border border-secondary-200 p-6">
|
|
<div className="flex items-center gap-3 mb-4">
|
|
<div className="w-10 h-10 rounded-lg bg-primary-50 text-primary-600 flex items-center justify-center">
|
|
<HardDrive className="w-5 h-5" />
|
|
</div>
|
|
<div>
|
|
<h3 className="text-sm font-semibold text-secondary-900">{t('settings.storage.local', 'Lokaler Storage')}</h3>
|
|
<p className="text-xs text-secondary-500">/data/storage</p>
|
|
</div>
|
|
</div>
|
|
<p className="text-sm text-secondary-600 mb-4">
|
|
{t('settings.storage.localDesc', 'Dateien werden lokal im Docker-Volume gespeichert. Bei Bedarf auf S3 oder externe Storage-Provider umstellbar.')}
|
|
</p>
|
|
<div className="grid grid-cols-2 gap-4 text-sm">
|
|
<div>
|
|
<span className="text-secondary-500">Backend:</span>
|
|
<span className="ml-2 font-medium text-secondary-900">Local</span>
|
|
</div>
|
|
<div>
|
|
<span className="text-secondary-500">Pfad:</span>
|
|
<span className="ml-2 font-medium text-secondary-900">/data/storage</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-white rounded-lg border border-secondary-200 p-6">
|
|
<div className="flex items-center gap-3 mb-4">
|
|
<div className="w-10 h-10 rounded-lg bg-warning-50 text-warning-600 flex items-center justify-center">
|
|
<HardDrive className="w-5 h-5" />
|
|
</div>
|
|
<div>
|
|
<h3 className="text-sm font-semibold text-secondary-900">{t('settings.storage.s3', 'S3 Storage')}</h3>
|
|
<p className="text-xs text-secondary-500">{t('settings.storage.s3Desc', 'S3-kompatibler Storage (AWS S3, MinIO, etc.)')}</p>
|
|
</div>
|
|
</div>
|
|
<div className="space-y-3 text-sm">
|
|
<div className="flex items-center justify-between p-3 bg-secondary-50 rounded-md">
|
|
<span className="text-secondary-600">Status</span>
|
|
<span className="font-medium text-secondary-400">{t('settings.storage.notConfigured', 'Nicht konfiguriert')}</span>
|
|
</div>
|
|
<p className="text-xs text-secondary-500">
|
|
{t('settings.storage.s3Hint', 'S3 wird via Environment-Variablen konfiguriert: STORAGE_BACKEND=s3, S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY, S3_SECRET_KEY')}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-white rounded-lg border border-secondary-200 p-6">
|
|
<div className="flex items-center gap-3 mb-4">
|
|
<div className="w-10 h-10 rounded-lg bg-success-50 text-success-600 flex items-center justify-center">
|
|
<HardDrive className="w-5 h-5" />
|
|
</div>
|
|
<div>
|
|
<h3 className="text-sm font-semibold text-secondary-900">{t('settings.storage.external', 'Externe Storage-Provider')}</h3>
|
|
<p className="text-xs text-secondary-500">{t('settings.storage.externalDesc', 'WebDAV, Nextcloud, Google Drive, Dropbox')}</p>
|
|
</div>
|
|
</div>
|
|
<div className="space-y-3">
|
|
<div className="flex items-center justify-between p-3 bg-secondary-50 rounded-md">
|
|
<span className="text-sm text-secondary-600">{t('settings.storage.webdav', 'WebDAV')}</span>
|
|
<span className="text-xs font-medium text-secondary-400">{t('settings.storage.comingSoon', 'In Entwicklung')}</span>
|
|
</div>
|
|
<p className="text-xs text-secondary-500">
|
|
{t('settings.storage.externalHint', 'Externe Storage-Provider werden als Plugins implementiert. In Zukunft hier konfigurierbar.')}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{activeTab === 'entwickler' && (
|
|
<div className="space-y-4" data-testid="settings-developer-section">
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
<a
|
|
href="/docs"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="flex items-start gap-3 p-4 rounded-lg border border-secondary-200 bg-white hover:border-primary-300 hover:shadow-sm transition-all min-h-touch"
|
|
>
|
|
<div className="flex-shrink-0 w-10 h-10 rounded-lg bg-primary-50 text-primary-600 flex items-center justify-center">
|
|
<Code className="w-5 h-5" aria-hidden="true" strokeWidth={2} />
|
|
</div>
|
|
<div className="min-w-0">
|
|
<p className="text-sm font-semibold text-secondary-900">
|
|
{t('settings.apiDocsSwagger', 'API Dokumentation (Swagger)')}
|
|
</p>
|
|
<p className="text-xs text-secondary-500 mt-0.5">/docs</p>
|
|
</div>
|
|
</a>
|
|
<a
|
|
href="/redoc"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="flex items-start gap-3 p-4 rounded-lg border border-secondary-200 bg-white hover:border-primary-300 hover:shadow-sm transition-all min-h-touch"
|
|
>
|
|
<div className="flex-shrink-0 w-10 h-10 rounded-lg bg-primary-50 text-primary-600 flex items-center justify-center">
|
|
<BookOpen className="w-5 h-5" aria-hidden="true" strokeWidth={2} />
|
|
</div>
|
|
<div className="min-w-0">
|
|
<p className="text-sm font-semibold text-secondary-900">
|
|
{t('settings.apiDocsRedoc', 'API Dokumentation (ReDoc)')}
|
|
</p>
|
|
<p className="text-xs text-secondary-500 mt-0.5">/redoc</p>
|
|
</div>
|
|
</a>
|
|
<a
|
|
href="/openapi.json"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="flex items-start gap-3 p-4 rounded-lg border border-secondary-200 bg-white hover:border-primary-300 hover:shadow-sm transition-all min-h-touch"
|
|
>
|
|
<div className="flex-shrink-0 w-10 h-10 rounded-lg bg-primary-50 text-primary-600 flex items-center justify-center">
|
|
<FileJson className="w-5 h-5" aria-hidden="true" strokeWidth={2} />
|
|
</div>
|
|
<div className="min-w-0">
|
|
<p className="text-sm font-semibold text-secondary-900">
|
|
{t('settings.openApiSchema', 'OpenAPI Schema')}
|
|
</p>
|
|
<p className="text-xs text-secondary-500 mt-0.5">/openapi.json</p>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|