diff --git a/src/components/FormGenerator/FormGeneratorTree/FormGeneratorTree.module.css b/src/components/FormGenerator/FormGeneratorTree/FormGeneratorTree.module.css index bb2cdb1..088de25 100644 --- a/src/components/FormGenerator/FormGeneratorTree/FormGeneratorTree.module.css +++ b/src/components/FormGenerator/FormGeneratorTree/FormGeneratorTree.module.css @@ -396,7 +396,8 @@ min-width: 0; } -/* Hover action icons -- overlay on top of file size to save width */ +/* Hover action icons -- overlay on top of file size to save width. + Positioned inside .nodeSizeGroup so they never cover .nodeActionsPersistent. */ .nodeActionsHover { position: absolute; right: 0; @@ -408,7 +409,6 @@ gap: 2px; opacity: 0; transition: opacity 0.15s ease; - z-index: 1; } .nodeRow:hover .nodeActionsHover { diff --git a/src/components/FormGenerator/FormGeneratorTree/FormGeneratorTree.tsx b/src/components/FormGenerator/FormGeneratorTree/FormGeneratorTree.tsx index 1b9aca7..cee9833 100644 --- a/src/components/FormGenerator/FormGeneratorTree/FormGeneratorTree.tsx +++ b/src/components/FormGenerator/FormGeneratorTree/FormGeneratorTree.tsx @@ -381,15 +381,13 @@ const TreeNodeRow = React.memo(function TreeNodeRow({ )} - {!hideRowActionButtons && ( - - {node.sizeBytes != null ? _formatSize(node.sizeBytes) : ''} - - )} - {!hideRowActionButtons && ( <> -
+
+ + {node.sizeBytes != null ? _formatSize(node.sizeBytes) : ''} + +
{canCreateChild && onCreateChild && ( )} +
diff --git a/src/components/FormGenerator/FormGeneratorTree/providers/FolderFileProvider.tsx b/src/components/FormGenerator/FormGeneratorTree/providers/FolderFileProvider.tsx index f983a84..cb3bb2d 100644 --- a/src/components/FormGenerator/FormGeneratorTree/providers/FolderFileProvider.tsx +++ b/src/components/FormGenerator/FormGeneratorTree/providers/FolderFileProvider.tsx @@ -7,7 +7,7 @@ interface FolderData { id: string; name: string; parentId?: string | null; - scope?: ScopeValue; + scope?: ScopeValue | 'mixed'; neutralize?: boolean | 'mixed'; contextOrphan?: boolean; } @@ -83,6 +83,26 @@ function _makeSyntheticRoot(ownership: Ownership): TreeNode { }; } +function _applyRootAggregate(root: TreeNode, allFolders: FolderData[]): void { + const topFolders = allFolders.filter((f) => (f.parentId ?? null) === null); + if (topFolders.length === 0) return; + const nVals = new Set(); + const sVals = new Set(); + for (const f of topFolders) { + if (f.neutralize === 'mixed') { nVals.add(true); nVals.add(false); } + else nVals.add(Boolean(f.neutralize)); + const sv = f.scope ?? 'personal'; + if (sv === 'mixed') { sVals.add('__a'); sVals.add('__b'); } + else sVals.add(sv); + } + root.neutralize = nVals.size > 1 + ? 'mixed' + : ((nVals.values().next().value as boolean) ?? false); + root.scope = (sVals.size > 1 + ? 'mixed' + : (sVals.values().next().value ?? 'personal')) as ScopeValue; +} + export function createFolderFileProvider(options: { includeFiles?: boolean } = {}): TreeNodeProvider { const includeFiles = options.includeFiles !== false; const ownerParam = (ownership: Ownership) => (ownership === 'own' ? 'me' : 'shared'); @@ -136,12 +156,16 @@ export function createFolderFileProvider(options: { includeFiles?: boolean } = { rootKey: 'files', async loadChildren(parentId, ownership) { - // Synthetic root: when the tree asks for top-level (parentId=null), - // we return ONE container ("/") instead of the real items. The real - // top-level items are then loaded as children of that container the - // next time the tree resolves it (auto-expanded via defaultExpanded). if (parentId === null) { - return [_makeSyntheticRoot(ownership)]; + const root = _makeSyntheticRoot(ownership); + try { + const res = await api.get('/api/files/folders/tree', { + params: { owner: ownerParam(ownership) }, + }); + const allFolders: FolderData[] = res.data ?? []; + _applyRootAggregate(root, allFolders); + } catch { /* keep defaults */ } + return [root]; } const synthRootId = _SYNTH_ROOT_ID(ownership); diff --git a/src/pages/admin/AdminDatabaseHealthPage.tsx b/src/pages/admin/AdminDatabaseHealthPage.tsx index ef98bb2..f34ca63 100644 --- a/src/pages/admin/AdminDatabaseHealthPage.tsx +++ b/src/pages/admin/AdminDatabaseHealthPage.tsx @@ -841,31 +841,22 @@ const MigrationTab: React.FC = () => { _addExportLog(t('Export gestartet: {count} Datenbanken', { count: totalDbs })); - let token = ''; - try { - const startRes = await api.post('/api/admin/database-health/migration/export-start'); - token = startRes.data.token; - } catch (err: any) { - _addExportLog(t('Fehler beim Starten des Exports: {error}', { error: String(err) }), 'error'); - toast.showError(t('Export fehlgeschlagen')); - setExporting(false); - return; - } - let totalTables = 0; let totalRecords = 0; let errors = 0; let exportedCount = 0; + const collectedDatabases: Record = {}; for (let i = 0; i < dbList.length; i++) { const dbName = dbList[i]; _addExportLog(t('Exportiere {index}/{total}: {db}...', { index: i + 1, total: totalDbs, db: dbName })); try { const res = await api.get('/api/admin/database-health/migration/export-single', { - params: { token, database: dbName }, + params: { database: dbName }, }); totalTables += res.data.tableCount || 0; totalRecords += res.data.totalRecords || 0; + collectedDatabases[dbName] = res.data.payload; exportedCount++; _addExportLog( t('{db}: {tables} Tabellen, {records} Datensaetze', { @@ -897,12 +888,19 @@ const MigrationTab: React.FC = () => { const scope = isFullExport ? 'full' : 'partial'; const filename = `db_backup_${instanceLabel}_${scope}_${ts}.json`; - const res = await api.get('/api/admin/database-health/migration/export-download', { - params: { token, filename }, - responseType: 'blob', - }); + const exportData = { + meta: { + exportedAt: new Date().toISOString(), + version: '1.0', + databaseCount: Object.keys(collectedDatabases).length, + totalTables, + totalRecords, + }, + databases: collectedDatabases, + }; - const blob = new Blob([res.data], { type: 'application/json' }); + const content = JSON.stringify(exportData, null, 0); + const blob = new Blob([content], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url;