Storage limit: 80% warning and limit in the web interface
CI / unit (8.2) (push) Has been cancelled
CI / unit (8.3) (push) Has been cancelled
CI / unit (8.4) (push) Has been cancelled
CI / integration (mariadb, 32) (push) Has been cancelled
CI / integration (mariadb, 33) (push) Has been cancelled
CI / integration (mariadb, 34) (push) Has been cancelled
CI / integration (mariadb, 35) (push) Has been cancelled
CI / integration (pgsql, 32) (push) Has been cancelled
CI / integration (pgsql, 33) (push) Has been cancelled
CI / integration (pgsql, 34) (push) Has been cancelled
CI / integration (pgsql, 35) (push) Has been cancelled
CI / integration (sqlite, 32) (push) Has been cancelled
CI / integration (sqlite, 33) (push) Has been cancelled
CI / integration (sqlite, 34) (push) Has been cancelled
CI / integration (sqlite, 35) (push) Has been cancelled

- after each successful backup the stored size is recorded; at 80% of a
  location's limit administrators get a notification (once a day)
- locations list shows stored size and limit; the add form has a
  'maximum size' field that explains that empty means unlimited and
  billed by the provider; PUT /api/targets/{id}/limit (password
  confirmation)

Tested on NC34 with the OVH test bucket: a backup above the limit stops,
a successful one at 81% sends the warning.
This commit is contained in:
2026-10-02 14:50:16 +02:00
parent 2610c1ad2f
commit 85e835bfc2
8 changed files with 70 additions and 7 deletions
+30
View File
@@ -54,6 +54,36 @@ class AlertService {
['kind' => $run->getKind(), 'target' => $targetName, 'error' => mb_strimwidth((string)$run->getError(), 0, 300, '…')]);
}
/**
* Remember how much is stored on a location (for the settings page) and warn administrators
* once a day when it is at 80% of its limit or more.
*/
public function storedBytes(Target $target, int $bytes): void {
$this->appConfig->setValueString(Application::APP_ID, 'stored_' . $target->getId(), (string)$bytes);
$limit = $target->getMaxBytes();
if ($limit === null || $limit <= 0 || $bytes < 0.8 * $limit) {
return;
}
$key = 'quota_notified_' . $target->getId();
if ($this->appConfig->getValueString(Application::APP_ID, $key, '') === date('Y-m-d')) {
return;
}
$this->appConfig->setValueString(Application::APP_ID, $key, date('Y-m-d'));
$this->notifyAdmins('quota_warning', 'quota-' . $target->getId() . '-' . date('Y-m-d'), [
'target' => $target->getName(), 'percent' => (string)round($bytes / $limit * 100),
'used' => self::gib($bytes), 'limit' => self::gib($limit)]);
}
private static function gib(int $bytes): string {
$g = $bytes / 1073741824;
return sprintf($g < 10 ? '%.2f GiB' : '%.1f GiB', $g);
}
public function lastStoredBytes(Target $target): ?int {
$v = $this->appConfig->getValueString(Application::APP_ID, 'stored_' . $target->getId(), '');
return $v === '' ? null : (int)$v;
}
/** At most one notification per location per day. */
public function checkStale(Target $target): void {
$last = $this->runs->lastFinished($target->getId(), BackupService::KIND_FULL);