diff --git a/backend/package-lock.json b/backend/package-lock.json index 5d66d88..85609e8 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1,12 +1,12 @@ { "name": "ripster-backend", - "version": "0.10.2-5", + "version": "0.10.2-6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ripster-backend", - "version": "0.10.2-5", + "version": "0.10.2-6", "dependencies": { "archiver": "^7.0.1", "cors": "^2.8.5", diff --git a/backend/package.json b/backend/package.json index 25c265e..4d678de 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "ripster-backend", - "version": "0.10.2-5", + "version": "0.10.2-6", "private": true, "type": "commonjs", "scripts": { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d63a6a2..b904ea9 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "ripster-frontend", - "version": "0.10.2-5", + "version": "0.10.2-6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ripster-frontend", - "version": "0.10.2-5", + "version": "0.10.2-6", "dependencies": { "primeicons": "^7.0.0", "primereact": "^10.9.2", diff --git a/frontend/package.json b/frontend/package.json index 8ffabba..d118b4c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "ripster-frontend", - "version": "0.10.2-5", + "version": "0.10.2-6", "private": true, "type": "module", "scripts": { diff --git a/frontend/src/pages/DashboardPage.jsx b/frontend/src/pages/DashboardPage.jsx index 81e5fe4..3085dae 100644 --- a/frontend/src/pages/DashboardPage.jsx +++ b/frontend/src/pages/DashboardPage.jsx @@ -880,18 +880,38 @@ export default function DashboardPage({ const gpuMetrics = monitoringSample?.gpu || null; const storageMetrics = Array.isArray(monitoringSample?.storage) ? monitoringSample.storage : []; const storageGroups = useMemo(() => { - const groups = []; + // Phase 1: group by mountPoint + const phase1 = []; const mountMap = new Map(); for (const entry of storageMetrics) { const groupKey = entry?.mountPoint || `__no_mount_${entry?.key}`; if (!mountMap.has(groupKey)) { const group = { mountPoint: entry?.mountPoint || null, entries: [], representative: entry }; mountMap.set(groupKey, group); - groups.push(group); + phase1.push(group); } mountMap.get(groupKey).entries.push(entry); } - return groups; + + // Phase 2: merge groups with identical disk signature (same physical disk) + const merged = []; + const diskSigMap = new Map(); + for (const group of phase1) { + const totalBytes = Number(group.representative?.totalBytes); + const freeBytes = Number(group.representative?.freeBytes); + if (Number.isFinite(totalBytes) && totalBytes > 0 && Number.isFinite(freeBytes)) { + const sig = `${totalBytes}:${freeBytes}`; + if (diskSigMap.has(sig)) { + diskSigMap.get(sig).entries.push(...group.entries); + } else { + diskSigMap.set(sig, group); + merged.push(group); + } + } else { + merged.push(group); + } + } + return merged; }, [storageMetrics]); const cpuPerCoreMetrics = Array.isArray(cpuMetrics?.perCore) ? cpuMetrics.perCore : []; const gpuDevices = Array.isArray(gpuMetrics?.devices) ? gpuMetrics.devices : []; @@ -2495,19 +2515,15 @@ export default function DashboardPage({ ref={audiobookFileUploadRef} accept=".aax" maxFileSize={10737418240} + customUpload + uploadHandler={() => void handleAudiobookUpload()} disabled={audiobookUploadBusy} onSelect={(e) => setAudiobookUploadFile(e.files[0] || null)} onClear={() => setAudiobookUploadFile(null)} onRemove={() => setAudiobookUploadFile(null)} - chooseLabel="Auswählen" - chooseOptions={{ icon: 'pi pi-folder-open' }} - cancelOptions={{ icon: 'pi pi-times', className: 'p-button-outlined p-button-secondary' }} - headerTemplate={(options) => ( -