'use client' import { useTranslation } from '../../i18n/useTranslation'; import React from 'react' import Header from '../../components/nav/Header' import Footer from '../../components/Footer' import PageTransitionEffect from '../../components/animation/pageTransitionEffect' import { CommandLineIcon, PlayIcon, TrashIcon, ExclamationTriangleIcon, ArrowUpTrayIcon, WrenchScrewdriverIcon, FolderOpenIcon, ArrowPathIcon, ArrowDownTrayIcon } from '@heroicons/react/24/outline' import useAuthStore from '../../store/authStore' import { useRouter } from 'next/navigation' import { importSqlDump, SqlExecutionData, SqlExecutionMeta } from './hooks/executeSql' import { createFolderStructure, listFolderStructureIssues, listLooseFiles, listGhostDirectories, moveLooseFilesToContract, downloadExoscaleArchive, FixResult, FolderStructureIssueUser, GhostDirectory, LooseFileUser } from './hooks/exoscaleMaintenance' export default function DevManagementPage() { const { t } = useTranslation(); const router = useRouter() const user = useAuthStore(s => s.user) const isAdmin = !!user && ( (user as any)?.role === 'admin' || (user as any)?.userType === 'admin' || (user as any)?.isAdmin === true || ((user as any)?.roles?.includes?.('admin')) || (user as any)?.role === 'super_admin' || (user as any)?.userType === 'super_admin' || (user as any)?.isSuperAdmin === true || ((user as any)?.roles?.includes?.('super_admin')) ) const [authChecked, setAuthChecked] = React.useState(false) React.useEffect(() => { if (user === null) { router.replace('/login') return } if (user && !isAdmin) { router.replace('/admin') return } setAuthChecked(true) }, [user, isAdmin, router]) const [selectedFile, setSelectedFile] = React.useState(null) const [loading, setLoading] = React.useState(false) const [error, setError] = React.useState('') const [result, setResult] = React.useState(null) const [meta, setMeta] = React.useState(null) const fileInputRef = React.useRef(null) const [activeTab, setActiveTab] = React.useState<'sql' | 'structure' | 'loose' | 'ghost'>('sql') const [structureUsers, setStructureUsers] = React.useState([]) const [structureMeta, setStructureMeta] = React.useState<{ scannedUsers?: number; invalidCount?: number } | null>(null) const [looseUsers, setLooseUsers] = React.useState([]) const [looseMeta, setLooseMeta] = React.useState<{ scannedUsers?: number; looseCount?: number } | null>(null) const [ghostDirs, setGhostDirs] = React.useState([]) const [ghostMeta, setGhostMeta] = React.useState<{ ghostCount?: number } | null>(null) const [exoscaleLoading, setExoscaleLoading] = React.useState(false) const [exoscaleError, setExoscaleError] = React.useState('') const [fixingUserId, setFixingUserId] = React.useState(null) const [fixingAll, setFixingAll] = React.useState(false) const [fixResults, setFixResults] = React.useState>({}) const [structureActionMeta, setStructureActionMeta] = React.useState<{ processedUsers?: number; createdTotal?: number; errorCount?: number } | null>(null) const [structureActionResults, setStructureActionResults] = React.useState([]) const [looseActionMeta, setLooseActionMeta] = React.useState<{ processedUsers?: number; movedTotal?: number; errorCount?: number } | null>(null) const [looseActionResults, setLooseActionResults] = React.useState([]) const [structureStatus, setStructureStatus] = React.useState('') const [looseStatus, setLooseStatus] = React.useState('') const [ghostStatus, setGhostStatus] = React.useState('') const [archiveLoading, setArchiveLoading] = React.useState(false) const [archiveStatus, setArchiveStatus] = React.useState('') const formatNow = () => new Date().toLocaleString() const runImport = async () => { setError('') if (!selectedFile) { setError('Please select a SQL dump file.') return } setLoading(true) try { const res = await importSqlDump(selectedFile) if (!res.ok) { setError(res.message || 'Failed to import SQL dump.') return } setResult(res.data || null) setMeta(res.meta || null) } finally { setLoading(false) } } const clearResults = () => { setResult(null) setMeta(null) setError('') } const loadStructureIssues = async () => { setExoscaleError('') setExoscaleLoading(true) setStructureStatus('Refreshing list...') try { const res = await listFolderStructureIssues() if (!res.ok) { setExoscaleError(res.message || 'Failed to load folder structure issues.') return } setStructureUsers(res.data || []) setStructureMeta(res.meta || null) setStructureStatus(`Last refresh: ${formatNow()}`) } finally { setExoscaleLoading(false) } } const loadLooseFiles = async () => { setExoscaleError('') setExoscaleLoading(true) setLooseStatus('Refreshing list...') try { const res = await listLooseFiles() if (!res.ok) { setExoscaleError(res.message || 'Failed to load loose files.') return } setLooseUsers(res.data || []) setLooseMeta(res.meta || null) setLooseStatus(`Last refresh: ${formatNow()}`) } finally { setExoscaleLoading(false) } } const loadGhostDirectories = async () => { setExoscaleError('') setExoscaleLoading(true) setGhostStatus('Refreshing list...') try { const res = await listGhostDirectories() if (!res.ok) { setExoscaleError(res.message || 'Failed to load ghost directories.') return } setGhostDirs(res.data || []) setGhostMeta(res.meta || null) setGhostStatus(`Last refresh: ${formatNow()}`) } finally { setExoscaleLoading(false) } } const runArchiveDownload = async () => { setExoscaleError('') setArchiveLoading(true) setArchiveStatus('Preparing ZIP export...') try { const res = await downloadExoscaleArchive() if (!res.ok) { setExoscaleError(res.message || 'Failed to download Exoscale archive.') setArchiveStatus('') return } setArchiveStatus(`Download started: ${res.fileName || formatNow()}`) } finally { setArchiveLoading(false) } } const runCreateStructure = async (userId?: number) => { setExoscaleError('') if (userId) { setFixingUserId(userId) setStructureStatus(`Creating folders for #${userId}...`) } else { setFixingAll(true) setStructureStatus('Creating folders for each user...') setStructureActionResults([]) setStructureActionMeta({ processedUsers: 0, createdTotal: 0, errorCount: 0 }) } try { const targets = userId ? [{ userId }] : structureUsers.map(u => ({ userId: u.userId })) for (const target of targets) { const res = await createFolderStructure(target.userId) if (!res.ok) { setExoscaleError(res.message || 'Failed to create folder structure.') break } const batch = res.data || [] setStructureActionResults(prev => [...prev, ...batch]) setStructureActionMeta(prev => ({ processedUsers: (prev?.processedUsers || 0) + (res.meta?.processedUsers || 0), createdTotal: (prev?.createdTotal || 0) + (res.meta?.createdTotal || 0), errorCount: (prev?.errorCount || 0) + (res.meta?.errorCount || 0) })) setFixResults(prev => { const next = { ...prev } for (const item of batch) { next[item.userId] = item } return next }) setStructureStatus(`Last create: ${formatNow()} (processed #${target.userId})`) } await loadStructureIssues() } finally { setFixingUserId(null) setFixingAll(false) } } const runMoveLooseFiles = async (userId?: number) => { setExoscaleError('') if (userId) { setFixingUserId(userId) setLooseStatus(`Moving loose files for #${userId}...`) } else { setFixingAll(true) setLooseStatus('Moving loose files for each user...') setLooseActionResults([]) setLooseActionMeta({ processedUsers: 0, movedTotal: 0, errorCount: 0 }) } try { const targets = userId ? [{ userId }] : looseUsers.map(u => ({ userId: u.userId })) for (const target of targets) { const res = await moveLooseFilesToContract(target.userId) if (!res.ok) { setExoscaleError(res.message || 'Failed to move loose files.') break } const batch = res.data || [] setLooseActionResults(prev => [...prev, ...batch]) setLooseActionMeta(prev => ({ processedUsers: (prev?.processedUsers || 0) + (res.meta?.processedUsers || 0), movedTotal: (prev?.movedTotal || 0) + (res.meta?.movedTotal || 0), errorCount: (prev?.errorCount || 0) + (res.meta?.errorCount || 0) })) setFixResults(prev => { const next = { ...prev } for (const item of batch) { next[item.userId] = item } return next }) setLooseStatus(`Last move: ${formatNow()} (processed #${target.userId})`) } await loadLooseFiles() } finally { setFixingUserId(null) setFixingAll(false) } } React.useEffect(() => { if (activeTab === 'structure') loadStructureIssues() if (activeTab === 'loose') loadLooseFiles() if (activeTab === 'ghost') loadGhostDirectories() }, [activeTab]) const onImportFile = (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (!file) return setSelectedFile(file) e.target.value = '' } if (!authChecked) return null return (
{/* tighter padding on mobile */}
{/* stack on mobile */}

{t('autofix.k664072a1')}

{t('autofix.k6e4a6069')}

{t('autofix.k6c6e5c0f')}
{t('autofix.k8a35cc53')}

Exoscale Backup Export

Ladet alle Dateien aus dem Exoscale Storage und liefert sie als ZIP-Download aus.

{archiveStatus && (
{archiveStatus}
)}
{activeTab === 'sql' && ( <>

{t('autofix.k981b1f1a')}

{/* actions: stack on mobile, full width */}
{t('autofix.kb6eacc9d')}
{t('autofix.k3ac8ca10')}
{selectedFile && (
Selected: {selectedFile.name}
)}
{error && (
{error}
)}

{t('autofix.kde2b4fa0')}

{t('autofix.k7938d4fd')} {Array.isArray(result?.result) ? (result?.result as any[]).length : result?.result ? 1 : 0}
Multi-statement {result?.isMulti ? 'Yes' : 'No'}
Duration {meta?.durationMs ? `${meta.durationMs} ms` : '-'}
{t('autofix.k0f0395ca')}

{t('autofix.kb4675362')}

{!result && (
{t('autofix.k23c9f0ff')}
)} {result?.result && (
                      {JSON.stringify(result.result, null, 2)}
                    
)}
)} {activeTab === 'structure' && (

{t('autofix.kd51f320c')}

{t('autofix.kb1341138')}

{structureStatus && (
{structureStatus}
)}
Scanned: {structureMeta?.scannedUsers ?? '-'} Missing: {structureMeta?.invalidCount ?? structureUsers.length}
{exoscaleError && (
{exoscaleError}
)} {exoscaleLoading ? (
{t('autofix.k8358f1d1')}
) : structureUsers.length === 0 ? (
{t('autofix.k9e609523')}
) : (
{structureUsers.map(user => { const fix = fixResults[user.userId] const displayName = user.name || user.email const missing = [!user.hasContractFolder ? 'contract' : null, !user.hasGdprFolder ? 'gdpr' : null].filter(Boolean).join(', ') return (
{displayName}
#{user.userId} • {user.email} • {user.userType} • {user.contractCategory}
{t('autofix.kd058bb7b')}{missing || 'none'}
{fix && (
Created {fix.created || 0}{fix.errors && fix.errors.length > 0 ? `, errors ${fix.errors.length}` : ''}.
)}
) })}
)} {(structureActionMeta || structureActionResults.length > 0) && (
{t('autofix.k941fd092')}
Processed: {structureActionMeta?.processedUsers ?? '-'} Created: {structureActionMeta?.createdTotal ?? '-'} Errors: {structureActionMeta?.errorCount ?? '-'}
{structureActionResults.length > 0 && (
{structureActionResults.map(item => (
#{item.userId} {item.contractCategory && ( {item.contractCategory} )} Created: {item.created ?? 0} {item.errors && item.errors.length > 0 && ( Errors: {item.errors.length} )}
))}
)}
)}
)} {activeTab === 'loose' && (

{t('autofix.k04b5cbca')}

{t('autofix.kbff01823')}

{looseStatus && (
{looseStatus}
)}
Scanned: {looseMeta?.scannedUsers ?? '-'} Loose: {looseMeta?.looseCount ?? looseUsers.length}
{exoscaleError && (
{exoscaleError}
)} {exoscaleLoading ? (
{t('autofix.k8193b7a2')}
) : looseUsers.length === 0 ? (
{t('autofix.k1db0c7cd')}
) : (
{looseUsers.map(user => { const fix = fixResults[user.userId] const displayName = user.name || user.email return (
{displayName}
#{user.userId} • {user.email} • {user.userType} • {user.contractCategory}
{t('autofix.kf340aa10')}{user.looseObjects}
{user.sampleKeys && user.sampleKeys.length > 0 && (
Sample: {user.sampleKeys.join(', ')}
)} {fix && (
Moved {fix.moved}, skipped {fix.skipped}{fix.errors && fix.errors.length > 0 ? `, errors ${fix.errors.length}` : ''}.
)}
) })}
)} {(looseActionMeta || looseActionResults.length > 0) && (
{t('autofix.kcf61fc9e')}
Processed: {looseActionMeta?.processedUsers ?? '-'} Moved: {looseActionMeta?.movedTotal ?? '-'} Errors: {looseActionMeta?.errorCount ?? '-'}
{looseActionResults.length > 0 && (
{looseActionResults.map(item => (
#{item.userId} {item.contractCategory && ( {item.contractCategory} )} Moved: {item.moved} Skipped: {item.skipped} {item.errors && item.errors.length > 0 && ( Errors: {item.errors.length} )}
))}
)}
)}
)} {activeTab === 'ghost' && (

{t('autofix.k6838438d')}

{t('autofix.k77444d5b')}

{ghostStatus && (
{ghostStatus}
)}
Ghost directories: {ghostMeta?.ghostCount ?? ghostDirs.length}
{exoscaleError && (
{exoscaleError}
)} {exoscaleLoading ? (
{t('autofix.k883ea8c5')}
) : ghostDirs.length === 0 ? (
{t('autofix.k12a7170a')}
) : (
{ghostDirs.map(dir => (
#{dir.userId}
{dir.contractCategory} • {dir.basePrefix}
))}
)}
)}
) }