+
+
+ {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;