Show daily repo size changes as graph
This commit is contained in:
parent
5b9c7dfa31
commit
93da0603da
|
@ -1,7 +1,7 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from ..utility.time import seconds_to_string
|
from ..utility.time import seconds_to_string
|
||||||
from ..utility.data import bytes_to_string
|
from ..utility.data import bytes_to_string, convert_bytes
|
||||||
from . import Label
|
from . import Label
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,12 +46,15 @@ class Repo(models.Model):
|
||||||
def size(self):
|
def size(self):
|
||||||
if self.archive_set.all().exists():
|
if self.archive_set.all().exists():
|
||||||
cache = self.latest_archive().cache
|
cache = self.latest_archive().cache
|
||||||
return f"{bytes_to_string(cache.unique_csize)}"
|
return cache.unique_csize
|
||||||
else:
|
else:
|
||||||
return "No archives stored"
|
return 0
|
||||||
|
|
||||||
def recent_errors(self):
|
def size_string(self):
|
||||||
days = 7
|
size = self.size()
|
||||||
|
return bytes_to_string(size)
|
||||||
|
|
||||||
|
def recent_errors(self, days: int = 7):
|
||||||
days_ago = (datetime.utcnow() - timedelta(days=days))
|
days_ago = (datetime.utcnow() - timedelta(days=days))
|
||||||
errors = self.label.errors.all().filter(time__gt=days_ago)
|
errors = self.label.errors.all().filter(time__gt=days_ago)
|
||||||
return errors
|
return errors
|
||||||
|
@ -71,21 +74,57 @@ class Repo(models.Model):
|
||||||
days.append(len(cday_archives) > 0)
|
days.append(len(cday_archives) > 0)
|
||||||
return days
|
return days
|
||||||
|
|
||||||
def get_archive_hours_dict(self):
|
def daily_dict(self, units, n_hours: int = 24):
|
||||||
if self.archive_set.all().exists():
|
archives = self.daily_archives(n_hours)
|
||||||
return {"id": self.id,
|
return {
|
||||||
|
"id": self.id,
|
||||||
"label": self.label.label,
|
"label": self.label.label,
|
||||||
"hours": self.get_archive_hours()}
|
"daily_size": list(reversed(self.series_csize(archives, units)))
|
||||||
else:
|
}
|
||||||
return {"id": self.id,
|
|
||||||
"label": self.label.label,
|
|
||||||
"hours": []}
|
|
||||||
|
|
||||||
def get_archive_hours(self):
|
@staticmethod
|
||||||
hours = []
|
def series_times(archives):
|
||||||
for hour in range(24):
|
return [archive.start if archive is not None else None for archive in archives]
|
||||||
chour = datetime.utcnow() - timedelta(hours=hour)
|
|
||||||
cday_archives = self.archive_set.all().filter(start__date=chour.date()).filter(start__hour=chour.hour)
|
@staticmethod
|
||||||
hours.append(len(cday_archives) > 0)
|
def series_csize(archives, units=None):
|
||||||
hours = ''.join(['H' if hour is True else '-' for hour in hours])
|
return [convert_bytes(archive.cache.unique_csize, units)[0]
|
||||||
return hours
|
if archive is not None else None for archive in archives]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def series_csize_pretty(archives):
|
||||||
|
return [archive.cache.unique_csize if archive is not None else None for archive in archives]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def series_success_string(archives):
|
||||||
|
return ''.join(['H' if archive is not None else '-' for archive in archives])
|
||||||
|
|
||||||
|
def hourly_archive_string(self):
|
||||||
|
return ''.join(['H' if archive is not None else '-' for archive in self.hourly_archives(24)])
|
||||||
|
|
||||||
|
def daily_archives(self, n_days: int = 24):
|
||||||
|
archives = []
|
||||||
|
for day in range(n_days):
|
||||||
|
current_date = (datetime.utcnow() - timedelta(days=day)).date()
|
||||||
|
archive_current_date = self.archive_set.all()\
|
||||||
|
.filter(start__date=current_date)\
|
||||||
|
.order_by('-start')
|
||||||
|
if len(archive_current_date) > 0:
|
||||||
|
archives.append(archive_current_date[0])
|
||||||
|
else:
|
||||||
|
archives.append(None)
|
||||||
|
return archives
|
||||||
|
|
||||||
|
def hourly_archives(self, n_hours: int = 24):
|
||||||
|
archives = []
|
||||||
|
for hour in range(n_hours):
|
||||||
|
current_hour = datetime.utcnow() - timedelta(hours=hour)
|
||||||
|
archives_hour = self.archive_set.all()\
|
||||||
|
.filter(start__date=current_hour.date())\
|
||||||
|
.filter(start__hour=current_hour.hour)\
|
||||||
|
.order_by('-start')
|
||||||
|
if len(archives_hour) > 0:
|
||||||
|
archives.append(archives_hour[0])
|
||||||
|
else:
|
||||||
|
archives.append(None)
|
||||||
|
return archives
|
||||||
|
|
|
@ -1,7 +1,46 @@
|
||||||
window.addEventListener("DOMContentLoaded", function () {
|
window.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const repoDict = JSON.parse(document.getElementById('hour_list').textContent);
|
||||||
const hour_json = JSON.parse(document.getElementById('hour_list').textContent);
|
set_daily_graph(repoDict)
|
||||||
hour_json.forEach(function (repo) {
|
|
||||||
console.log(repo.hours);
|
|
||||||
})
|
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
|
function set_daily_graph(repoDict) {
|
||||||
|
const labels = repoDict.date_labels;
|
||||||
|
const y_units = repoDict.units
|
||||||
|
|
||||||
|
var datasets = []
|
||||||
|
repoDict.repos.forEach(function (repo) {
|
||||||
|
datasets.push({label: repo.label,
|
||||||
|
data: repo.daily_size,
|
||||||
|
fill: false,
|
||||||
|
tension: 0.1,
|
||||||
|
borderColor: 'rgb(75, 192, 192)'});
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
labels: labels,
|
||||||
|
datasets: datasets
|
||||||
|
};
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
type: 'line',
|
||||||
|
data,
|
||||||
|
options: {
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
ticks: {
|
||||||
|
display: true,
|
||||||
|
grid: false,
|
||||||
|
callback: function(value, index, values) {
|
||||||
|
return value + " " + y_units;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var myChart = new Chart(
|
||||||
|
document.getElementById('backup_csize_hourly'),
|
||||||
|
config
|
||||||
|
);
|
||||||
|
}
|
|
@ -31,11 +31,11 @@
|
||||||
</dl>
|
</dl>
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
<dt class="col-sm-3">Hourly backups:</dt>
|
<dt class="col-sm-3">Hourly backups:</dt>
|
||||||
<dd class="col-sm-9">{{ repo.get_archive_hours }}</dd>
|
<dd class="col-sm-9">{{ repo.hourly_archive_string }}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
<dt class="col-sm-3">Size:</dt>
|
<dt class="col-sm-3">Size:</dt>
|
||||||
<dd class="col-sm-9">{{ repo.size }}</dd>
|
<dd class="col-sm-9">{{ repo.size_string }}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
{% if repo.recent_errors|length > 0 %}
|
{% if repo.recent_errors|length > 0 %}
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
|
@ -53,6 +53,9 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>No repos found.</p>
|
<p>No repos found.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<div class="col-md-4 rounded float-xl-left">
|
||||||
|
<canvas id="backup_csize_hourly" width="200" height="100"></canvas>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -1,11 +1,24 @@
|
||||||
from math import floor, log
|
from math import floor, log
|
||||||
|
|
||||||
|
bytes_in_unit = 1024 # Kibibyte
|
||||||
|
units = ("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "HiB")
|
||||||
|
# bytes_in_unit = 1000 # Kilobyte
|
||||||
|
# units = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "HB")
|
||||||
|
|
||||||
def bytes_to_string(bytes: int):
|
|
||||||
suffixes = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "HB")
|
def convert_bytes(c_bytes: int, unit: str = None) -> (float, str):
|
||||||
if bytes == 0:
|
if c_bytes == 0:
|
||||||
return f"0{suffixes[0]}"
|
return 0, units[0] if unit is None else unit
|
||||||
|
|
||||||
|
if unit is None:
|
||||||
|
index = int(floor(log(c_bytes, bytes_in_unit)))
|
||||||
else:
|
else:
|
||||||
index = int(floor(log(bytes, 1024)))
|
index = units.index(unit)
|
||||||
s = round(bytes / pow(1024, index), 2)
|
|
||||||
return f"{s}{suffixes[index]}"
|
result = round(c_bytes / pow(bytes_in_unit, index), 2)
|
||||||
|
return result, units[index]
|
||||||
|
|
||||||
|
|
||||||
|
def bytes_to_string(c_bytes: int, unit: str = None) -> str:
|
||||||
|
n_bytes, unit = convert_bytes(c_bytes, unit)
|
||||||
|
return f"{n_bytes} {unit}"
|
||||||
|
|
|
@ -5,19 +5,36 @@ from .models import Repo, Label, Archive, Cache, Error
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from .forms import RepoForm, ArchiveForm, ErrorForm
|
from .forms import RepoForm, ArchiveForm, ErrorForm
|
||||||
from django.contrib.auth.decorators import permission_required
|
from django.contrib.auth.decorators import permission_required
|
||||||
|
from .utility import data
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
|
||||||
def index(request):
|
def index(request):
|
||||||
repo_list = Repo.objects.all()
|
repo_list = Repo.objects.all()
|
||||||
|
|
||||||
hour_list = [repo.get_archive_hours_dict() for repo in repo_list]
|
repo_dict = repo_daily_dict(repo_list, 24)
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'repo_list': repo_list,
|
'repo_list': repo_list,
|
||||||
'hour_list': hour_list
|
'hour_list': repo_dict
|
||||||
}
|
}
|
||||||
return render(request, 'borg/index.html', context)
|
return render(request, 'borg/index.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
def repo_daily_dict(repo_list, n_days=14):
|
||||||
|
date_labels = list(reversed([(datetime.utcnow() - timedelta(days=day)).strftime("%d %b") for day in range(n_days)]))
|
||||||
|
max_repo_size = max(repo.latest_archive().cache.unique_csize for repo in repo_list)
|
||||||
|
_, max_unit = data.convert_bytes(max_repo_size)
|
||||||
|
|
||||||
|
repo_dicts = [repo.daily_dict(max_unit, n_days) for repo in repo_list]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"date_labels": date_labels,
|
||||||
|
"repos": repo_dicts,
|
||||||
|
"units": max_unit
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@permission_required("borg.add_repo")
|
@permission_required("borg.add_repo")
|
||||||
def get_repo(request):
|
def get_repo(request):
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
|
|
Loading…
Reference in New Issue
Block a user