basic dashboard
This commit is contained in:
parent
c006204bea
commit
ba4423ac57
|
@ -27,6 +27,8 @@ $footer-text-colour: $colour-near-white;
|
||||||
$footer-link-colour: $colour-near-black;
|
$footer-link-colour: $colour-near-black;
|
||||||
$footer-link-hover-colour: $colour-orange;
|
$footer-link-hover-colour: $colour-orange;
|
||||||
|
|
||||||
|
$dash-desc-border: $colour-near-black;
|
||||||
|
|
||||||
$form-label-text-colour: $colour-near-black;
|
$form-label-text-colour: $colour-near-black;
|
||||||
$form-input-border-colour: $colour-light-blue;
|
$form-input-border-colour: $colour-light-blue;
|
||||||
$form-input-border-active-colour: $colour-orange;
|
$form-input-border-active-colour: $colour-orange;
|
||||||
|
|
|
@ -6,6 +6,10 @@ strong {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-para-margin-top {
|
||||||
|
margin-block-start: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.no-margin-bottom {
|
.no-margin-bottom {
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,6 +143,29 @@ main {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dashcard {
|
||||||
|
text-align: center;
|
||||||
|
h3 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
text-align: left;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
padding: 0 0 0.5rem 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.desc {
|
||||||
|
border-bottom: 1px $dash-desc-border solid;
|
||||||
|
padding: 0 0 0.5rem 0;
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
}
|
||||||
|
.collection-item {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.pagination {
|
.pagination {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-top: 1rem;
|
padding-top: 1rem;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}{% load humanize %}
|
||||||
|
|
||||||
{% block headtitle %}Dashboard{% endblock %}
|
{% block headtitle %}Dashboard{% endblock %}
|
||||||
|
|
||||||
|
@ -8,11 +8,93 @@
|
||||||
<h1 class="truncate">Dashboard</h1>
|
<h1 class="truncate">Dashboard</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% if num_sources == 0 %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s12">
|
<div class="col s12">
|
||||||
<div class="card">
|
<p class="no-para-margin-top">
|
||||||
|
You don't have any media sources added. To get started, head over to the
|
||||||
|
<a href="{% url 'sync:sources' %}">sources</a> page and add some.
|
||||||
|
</p>
|
||||||
|
<a href="{% url 'sync:sources' %}" class="btn">Add a source <i class="fas fa-fw fa-play"></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="row no-margin-bottom">
|
||||||
|
<div class="col s12 m6 xl3">
|
||||||
|
<div class="card dashcard">
|
||||||
|
<a href="{% url 'sync:sources' %}">
|
||||||
|
<div class="card-content">
|
||||||
|
<h3>{{ num_sources }}</h3>
|
||||||
|
<div class="desc truncate">source{{ num_sources|pluralize }}</div>
|
||||||
|
<div class="truncate"><strong>{{ num_video_sources }}</strong> video, <strong>{{ num_audio_sources }}</strong> audio</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col s12 m6 xl3">
|
||||||
|
<div class="card dashcard">
|
||||||
|
<a href="{% url 'sync:media' %}">
|
||||||
|
<div class="card-content">
|
||||||
|
<h3>{{ num_media }}</h3>
|
||||||
|
<div class="desc truncate">media</div>
|
||||||
|
<div class="truncate"><strong>{{ num_downloaded_media }}</strong> downloaded</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col s12 m6 xl3">
|
||||||
|
<div class="card dashcard">
|
||||||
|
<a href="{% url 'sync:tasks' %}">
|
||||||
|
<div class="card-content">
|
||||||
|
<h3>{{ num_tasks }}</h3>
|
||||||
|
<div class="desc truncate">scheduled task{{ num_tasks|pluralize }}</div>
|
||||||
|
<div class="truncate"><strong>{{ num_completed_tasks }}</strong> completed</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col s12 m6 xl3">
|
||||||
|
<div class="card dashcard">
|
||||||
|
<a href="{% url 'sync:media' %}">
|
||||||
|
<div class="card-content">
|
||||||
|
<h3>{{ disk_usage_bytes|filesizeformat }}</h3>
|
||||||
|
<div class="desc truncate">{{ disk_usage_bytes|intcomma }} bytes</div>
|
||||||
|
<div class="truncate">Avg. <strong>{{ average_bytes_per_media|filesizeformat }}</strong> per media</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col s12 l6">
|
||||||
|
<div class="card dashcard">
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
intro
|
<h4>Latest downloads</h4>
|
||||||
|
<div class="collection">
|
||||||
|
{% for media in latest_downloads %}
|
||||||
|
<a href="{% url 'sync:media-item' pk=media.pk %}" class="collection-item">
|
||||||
|
<div class="truncate"><strong>{{ media.name }}</strong> ({{ media.source }})</div>
|
||||||
|
<div class="truncate"><strong>{{ media.download_date|timesince:now }}</strong> ago from "{{ media.source.name }}"</div>
|
||||||
|
</a>
|
||||||
|
{% empty %}
|
||||||
|
<span class="collection-item">No media has been downloaded.</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col s12 l6">
|
||||||
|
<div class="card dashcard">
|
||||||
|
<div class="card-content">
|
||||||
|
<h4>Largest downloads</h4>
|
||||||
|
<div class="collection">
|
||||||
|
{% for media in largest_downloads %}
|
||||||
|
<a href="{% url 'sync:media-item' pk=media.pk %}" class="collection-item">
|
||||||
|
<div class="truncate">{{ media.name }}</div>
|
||||||
|
<div class="truncate"><strong>{{ media.downloaded_filesize|filesizeformat }}</strong>{% if media.downloaded_format %} in {{ media.downloaded_format }}{% endif %}</div>
|
||||||
|
</a>
|
||||||
|
{% empty %}
|
||||||
|
<span class="collection-item">No media has been downloaded.</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,7 +7,7 @@ from django.views.generic.edit import (FormView, FormMixin, CreateView, UpdateVi
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.db.models import Count
|
from django.db.models import Q, Count, Sum
|
||||||
from django.forms import ValidationError
|
from django.forms import ValidationError
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
@ -32,8 +32,43 @@ class DashboardView(TemplateView):
|
||||||
|
|
||||||
template_name = 'sync/dashboard.html'
|
template_name = 'sync/dashboard.html'
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def get_context_data(self, *args, **kwargs):
|
||||||
return super().dispatch(request, *args, **kwargs)
|
data = super().get_context_data(*args, **kwargs)
|
||||||
|
data['now'] = timezone.now()
|
||||||
|
# Sources
|
||||||
|
data['num_sources'] = Source.objects.all().count()
|
||||||
|
data['num_video_sources'] = Source.objects.filter(
|
||||||
|
~Q(source_resolution=Source.SOURCE_RESOLUTION_AUDIO)
|
||||||
|
).count()
|
||||||
|
data['num_audio_sources'] = data['num_sources'] - data['num_video_sources']
|
||||||
|
data['num_failed_sources'] = Source.objects.filter(has_failed=True).count()
|
||||||
|
# Media
|
||||||
|
data['num_media'] = Media.objects.all().count()
|
||||||
|
data['num_downloaded_media'] = Media.objects.filter(downloaded=True).count()
|
||||||
|
# Tasks
|
||||||
|
data['num_tasks'] = Task.objects.all().count()
|
||||||
|
data['num_completed_tasks'] = CompletedTask.objects.all().count()
|
||||||
|
# Disk usage
|
||||||
|
disk_usage = Media.objects.filter(
|
||||||
|
downloaded=True, downloaded_filesize__isnull=False
|
||||||
|
).aggregate(Sum('downloaded_filesize'))
|
||||||
|
data['disk_usage_bytes'] = disk_usage['downloaded_filesize__sum']
|
||||||
|
if not data['disk_usage_bytes']:
|
||||||
|
data['disk_usage_bytes'] = 0
|
||||||
|
if data['disk_usage_bytes'] and data['num_downloaded_media']:
|
||||||
|
data['average_bytes_per_media'] = round(data['disk_usage_bytes'] /
|
||||||
|
data['num_downloaded_media'])
|
||||||
|
else:
|
||||||
|
data['average_bytes_per_media'] = 0
|
||||||
|
# Latest downloads
|
||||||
|
data['latest_downloads'] = Media.objects.filter(
|
||||||
|
downloaded=True
|
||||||
|
).order_by('-download_date')[:10]
|
||||||
|
# Largest downloads
|
||||||
|
data['largest_downloads'] = Media.objects.filter(
|
||||||
|
downloaded=True, downloaded_filesize__isnull=False
|
||||||
|
).order_by('-downloaded_filesize')[:10]
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class SourcesView(ListView):
|
class SourcesView(ListView):
|
||||||
|
|
|
@ -17,6 +17,7 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
'django.contrib.humanize',
|
||||||
'sass_processor',
|
'sass_processor',
|
||||||
'background_task',
|
'background_task',
|
||||||
'common',
|
'common',
|
||||||
|
|
Loading…
Reference in New Issue