Runs list: show the total size of each backup next to what it uploaded

This commit is contained in:
2026-10-13 11:02:18 +02:00
parent bec070b1b5
commit 92e693cfe6
3 changed files with 23 additions and 2 deletions
+7
View File
@@ -7,6 +7,13 @@ the app store accepts a `-beta` suffix, but Nextcloud servers then only offer th
beta/daily update channel, which would hide the app from most admins. The stability stage is beta/daily update channel, which would hide the app from most admins. The stability stage is
instead carried by the app description, the git tag, the release, and this file. instead carried by the app description, the git tag, the release, and this file.
## [Unreleased]
### Added
- The list of backup runs (web interface and `occ backup:status`) shows the total size of each
backup next to what it uploaded: an incremental backup of 1 TB that uploads 800 MB now shows
both. `occ backup:status` shows sizes in GiB where that reads better than MiB.
## [0.9.5-beta.6] - 2026-10-12 ## [0.9.5-beta.6] - 2026-10-12
Bugfix release for SFTP locations and large repositories. Tested on a production instance with a Bugfix release for SFTP locations and large repositories. Tested on a production instance with a
+14 -2
View File
@@ -48,10 +48,11 @@ final class Status extends Command {
$s = json_decode($r->getStats() ?? '{}', true)['files'] ?? []; $s = json_decode($r->getStats() ?? '{}', true)['files'] ?? [];
$rows[] = [$r->getId(), $names[$r->getTargetId()] ?? '#' . $r->getTargetId(), $r->getKind(), $r->getStatus() . ($r->getStatus() === 'running' ? ' (' . $r->getPhase() . self::percent($r) . ')' : ''), $rows[] = [$r->getId(), $names[$r->getTargetId()] ?? '#' . $r->getTargetId(), $r->getKind(), $r->getStatus() . ($r->getStatus() === 'running' ? ' (' . $r->getPhase() . self::percent($r) . ')' : ''),
date('Y-m-d H:i', $r->getStartedAt()), $r->getFinishedAt() ? gmdate('H:i:s', $r->getFinishedAt() - $r->getStartedAt()) : '', date('Y-m-d H:i', $r->getStartedAt()), $r->getFinishedAt() ? gmdate('H:i:s', $r->getFinishedAt() - $r->getStartedAt()) : '',
$s['files'] ?? '', isset($s['uploaded']) ? sprintf('%.1f MiB', $s['uploaded'] / 1048576) : '', mb_strimwidth((string)$r->getError(), 0, 50, '…')]; $s['files'] ?? '', isset($s['bytes']) ? self::size((int)$s['bytes']) : '',
isset($s['uploaded']) ? self::size((int)$s['uploaded']) : '', mb_strimwidth((string)$r->getError(), 0, 50, '…')];
} }
if ($rows !== []) { if ($rows !== []) {
(new Table($output))->setHeaders(['run', 'location', 'kind', 'status', 'started', 'took', 'files', 'uploaded', 'error'])->setRows($rows)->render(); (new Table($output))->setHeaders(['run', 'location', 'kind', 'status', 'started', 'took', 'files', 'size', 'uploaded', 'error'])->setRows($rows)->render();
} }
foreach ($this->verify->progress() as $targetId => $state) { foreach ($this->verify->progress() as $targetId => $state) {
$output->writeln(sprintf('Verify of %s on %s: %s', mb_strimwidth((string)($state['what'] ?? ''), 0, 40, '…'), $output->writeln(sprintf('Verify of %s on %s: %s', mb_strimwidth((string)($state['what'] ?? ''), 0, 40, '…'),
@@ -61,6 +62,17 @@ final class Status extends Command {
} }
/** " 68%", or " ~68%" when estimated from the file cache; empty without an estimate */ /** " 68%", or " ~68%" when estimated from the file cache; empty without an estimate */
private static function size(int $bytes): string {
$b = (float)$bytes;
foreach (['B', 'KiB', 'MiB', 'GiB'] as $u) {
if ($b < 1024) {
return $u === 'B' ? "$bytes B" : sprintf('%.1f %s', $b, $u);
}
$b /= 1024.0;
}
return sprintf('%.1f TiB', $b);
}
private static function percent(\OCA\NgBackup\Db\Run $r): string { private static function percent(\OCA\NgBackup\Db\Run $r): string {
$p = \OCA\NgBackup\Job\BackupRun::progressOf($r); $p = \OCA\NgBackup\Job\BackupRun::progressOf($r);
return $p === null ? '' : sprintf(' %s%d%%', $p['approximate'] ? '~' : '', (int)floor($p['fraction'] * 100.0)); return $p === null ? '' : sprintf(' %s%d%%', $p['approximate'] ? '~' : '', (int)floor($p['fraction'] * 100.0));
+2
View File
@@ -40,6 +40,7 @@
<th>{{ t('ng_backup', 'Restore point') }}</th> <th>{{ t('ng_backup', 'Restore point') }}</th>
<th>{{ t('ng_backup', 'Status') }}</th> <th>{{ t('ng_backup', 'Status') }}</th>
<th>{{ t('ng_backup', 'Files') }}</th> <th>{{ t('ng_backup', 'Files') }}</th>
<th :title="t('ng_backup', 'Total size of the files in this backup, unchanged files included')">{{ t('ng_backup', 'Size') }}</th>
<th>{{ t('ng_backup', 'Uploaded') }}</th> <th>{{ t('ng_backup', 'Uploaded') }}</th>
</tr> </tr>
</thead> </thead>
@@ -51,6 +52,7 @@
<td>{{ run.snapshot ? new Date(run.snapshot.createdAt * 1000).toLocaleString() + (run.snapshot.label ? ' – ' + run.snapshot.label : '') : '' }}</td> <td>{{ run.snapshot ? new Date(run.snapshot.createdAt * 1000).toLocaleString() + (run.snapshot.label ? ' – ' + run.snapshot.label : '') : '' }}</td>
<td :class="'ngb-' + run.status">{{ run.status }}{{ run.status === 'running' ? ' (' + run.phase + percent(run) + ')' : '' }}{{ run.error ? ': ' + run.error : '' }}</td> <td :class="'ngb-' + run.status">{{ run.status }}{{ run.status === 'running' ? ' (' + run.phase + percent(run) + ')' : '' }}{{ run.error ? ': ' + run.error : '' }}</td>
<td>{{ run.stats?.files?.files ?? run.stats?.restored ?? '' }}</td> <td>{{ run.stats?.files?.files ?? run.stats?.restored ?? '' }}</td>
<td>{{ run.stats?.files ? formatSize(run.stats.files.bytes) : '' }}</td>
<td>{{ run.stats?.files ? formatSize(run.stats.files.uploaded) : '' }}</td> <td>{{ run.stats?.files ? formatSize(run.stats.files.uploaded) : '' }}</td>
</tr> </tr>
</tbody> </tbody>