fix db sync
All checks were successful
Deploy Nyla Frontend to Production / deploy (push) Successful in 48s
All checks were successful
Deploy Nyla Frontend to Production / deploy (push) Successful in 48s
This commit is contained in:
parent
639cac2e33
commit
5a5d24bbe2
4 changed files with 53 additions and 32 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -381,15 +381,13 @@ const TreeNodeRow = React.memo(function TreeNodeRow<T>({
|
|||
</span>
|
||||
)}
|
||||
|
||||
{!hideRowActionButtons && (
|
||||
<span className={styles.nodeSize}>
|
||||
{node.sizeBytes != null ? _formatSize(node.sizeBytes) : ''}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{!hideRowActionButtons && (
|
||||
<>
|
||||
<div className={styles.nodeActionsHover}>
|
||||
<div className={styles.nodeSizeGroup}>
|
||||
<span className={styles.nodeSize}>
|
||||
{node.sizeBytes != null ? _formatSize(node.sizeBytes) : ''}
|
||||
</span>
|
||||
<div className={styles.nodeActionsHover}>
|
||||
{canCreateChild && onCreateChild && (
|
||||
<button
|
||||
className={styles.emojiBtn}
|
||||
|
|
@ -445,6 +443,7 @@ const TreeNodeRow = React.memo(function TreeNodeRow<T>({
|
|||
{'\u{1F5D1}\uFE0F'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.nodeActionsPersistent}>
|
||||
|
|
|
|||
|
|
@ -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<boolean | string>();
|
||||
const sVals = new Set<string>();
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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<string, any> = {};
|
||||
|
||||
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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue