Add basic stats list
This commit is contained in:
		
							parent
							
								
									6c3064f3ea
								
							
						
					
					
						commit
						15fa8226e0
					
				
							
								
								
									
										1
									
								
								Pipfile
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								Pipfile
									
									
									
									
									
								
							@ -15,6 +15,7 @@ django-ipware = "*"
 | 
				
			|||||||
pyyaml = "*"
 | 
					pyyaml = "*"
 | 
				
			||||||
ua-parser = "*"
 | 
					ua-parser = "*"
 | 
				
			||||||
user-agents = "*"
 | 
					user-agents = "*"
 | 
				
			||||||
 | 
					django-humanize = "*"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[requires]
 | 
					[requires]
 | 
				
			||||||
python_version = "3.6"
 | 
					python_version = "3.6"
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										16
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										16
									
								
								Pipfile.lock
									
									
									
										generated
									
									
									
								
							@ -1,7 +1,7 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    "_meta": {
 | 
					    "_meta": {
 | 
				
			||||||
        "hash": {
 | 
					        "hash": {
 | 
				
			||||||
            "sha256": "03f23e0c7409df9eb5a85b3df03d1975a1fe5563496602492cdcff46b8c5c829"
 | 
					            "sha256": "13b07e6285b739fe6f4987482aa448f1773d051de2de4806126a0e4bded150cf"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "pipfile-spec": 6,
 | 
					        "pipfile-spec": 6,
 | 
				
			||||||
        "requires": {
 | 
					        "requires": {
 | 
				
			||||||
@ -81,6 +81,13 @@
 | 
				
			|||||||
            "index": "pypi",
 | 
					            "index": "pypi",
 | 
				
			||||||
            "version": "==0.41.0"
 | 
					            "version": "==0.41.0"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "django-humanize": {
 | 
				
			||||||
 | 
					            "hashes": [
 | 
				
			||||||
 | 
					                "sha256:32491bf0209b89a277f7bfdab7fd6d4cc7944bb037f742d62e8e447a575c0028"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "index": "pypi",
 | 
				
			||||||
 | 
					            "version": "==0.1.2"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "django-ipware": {
 | 
					        "django-ipware": {
 | 
				
			||||||
            "hashes": [
 | 
					            "hashes": [
 | 
				
			||||||
                "sha256:a7c7a8fd019dbdc9c357e6e582f65034e897572fc79a7e467674efa8aef9d00b"
 | 
					                "sha256:a7c7a8fd019dbdc9c357e6e582f65034e897572fc79a7e467674efa8aef9d00b"
 | 
				
			||||||
@ -96,6 +103,13 @@
 | 
				
			|||||||
            "index": "pypi",
 | 
					            "index": "pypi",
 | 
				
			||||||
            "version": "==3.0.0"
 | 
					            "version": "==3.0.0"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "humanize": {
 | 
				
			||||||
 | 
					            "hashes": [
 | 
				
			||||||
 | 
					                "sha256:98b7ac9d1a70ad62175c8e0dd44beebbd92418727fc4e214468dfb2baa8ebfb5",
 | 
				
			||||||
 | 
					                "sha256:bc2a1ff065977011de2bc36197a4b14730c54bfc46ab12a153376684573a2dab"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "version": "==2.3.0"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "idna": {
 | 
					        "idna": {
 | 
				
			||||||
            "hashes": [
 | 
					            "hashes": [
 | 
				
			||||||
                "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
 | 
					                "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
 | 
				
			||||||
 | 
				
			|||||||
@ -1,2 +1,3 @@
 | 
				
			|||||||
 | 
					<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/a17t@0.1.3/dist/a17t.css">
 | 
				
			||||||
 | 
					<script async src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
 | 
				
			||||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
 | 
					<link href="https://cdn.jsdelivr.net/npm/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
 | 
				
			||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/a17t@latest/dist/a17t.css">
 | 
					 | 
				
			||||||
							
								
								
									
										34
									
								
								shynet/analytics/migrations/0004_auto_20200411_1541.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								shynet/analytics/migrations/0004_auto_20200411_1541.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					# Generated by Django 3.0.5 on 2020-04-11 19:41
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					import django.utils.timezone
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('analytics', '0003_auto_20200410_1325'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.RenameField(
 | 
				
			||||||
 | 
					            model_name='hit',
 | 
				
			||||||
 | 
					            old_name='start',
 | 
				
			||||||
 | 
					            new_name='start_time',
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.RenameField(
 | 
				
			||||||
 | 
					            model_name='session',
 | 
				
			||||||
 | 
					            old_name='first_seen',
 | 
				
			||||||
 | 
					            new_name='start_time',
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.RemoveField(
 | 
				
			||||||
 | 
					            model_name='hit',
 | 
				
			||||||
 | 
					            name='duration',
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='hit',
 | 
				
			||||||
 | 
					            name='last_seen',
 | 
				
			||||||
 | 
					            field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
 | 
				
			||||||
 | 
					            preserve_default=False,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
@ -18,7 +18,7 @@ class Session(models.Model):
 | 
				
			|||||||
    identifier = models.TextField(blank=True)
 | 
					    identifier = models.TextField(blank=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Time
 | 
					    # Time
 | 
				
			||||||
    first_seen = models.DateTimeField(auto_now_add=True)
 | 
					    start_time = models.DateTimeField(auto_now_add=True)
 | 
				
			||||||
    last_seen = models.DateTimeField(auto_now_add=True)
 | 
					    last_seen = models.DateTimeField(auto_now_add=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Core request information
 | 
					    # Core request information
 | 
				
			||||||
@ -40,8 +40,8 @@ class Hit(models.Model):
 | 
				
			|||||||
    session = models.ForeignKey(Session, on_delete=models.CASCADE)
 | 
					    session = models.ForeignKey(Session, on_delete=models.CASCADE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Base request information
 | 
					    # Base request information
 | 
				
			||||||
    start = models.DateTimeField(auto_now_add=True)
 | 
					    start_time = models.DateTimeField(auto_now_add=True)
 | 
				
			||||||
    duration = models.FloatField(default=0.0)  # Seconds spent on page
 | 
					    last_seen = models.DateTimeField(auto_now_add=True)
 | 
				
			||||||
    heartbeats = models.IntegerField(default=0)
 | 
					    heartbeats = models.IntegerField(default=0)
 | 
				
			||||||
    tracker = models.TextField()  # Tracking pixel or JS
 | 
					    tracker = models.TextField()  # Tracking pixel or JS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -46,7 +46,7 @@ def ingress_request(
 | 
				
			|||||||
    try:
 | 
					    try:
 | 
				
			||||||
        ip_data = _geoip2_lookup(ip)
 | 
					        ip_data = _geoip2_lookup(ip)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        service = Service.objects.get(uuid=service_uuid)
 | 
					        service = Service.objects.get(uuid=service_uuid, status=Service.ACTIVE)
 | 
				
			||||||
        log.debug(f"Linked to service {service}")
 | 
					        log.debug(f"Linked to service {service}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Create or update session
 | 
					        # Create or update session
 | 
				
			||||||
@ -95,7 +95,7 @@ def ingress_request(
 | 
				
			|||||||
                    # this is a heartbeat.
 | 
					                    # this is a heartbeat.
 | 
				
			||||||
                    log.debug("Hit is a heartbeat; updating old hit with new data...")
 | 
					                    log.debug("Hit is a heartbeat; updating old hit with new data...")
 | 
				
			||||||
                    hit.heartbeats += 1
 | 
					                    hit.heartbeats += 1
 | 
				
			||||||
                    hit.duration = (timezone.now() - hit.start).total_seconds()
 | 
					                    hit.last_seen = timezone.now()
 | 
				
			||||||
                    hit.save()
 | 
					                    hit.save()
 | 
				
			||||||
        if hit is None:
 | 
					        if hit is None:
 | 
				
			||||||
            log.debug("Hit is a page load; creating new hit...")
 | 
					            log.debug("Hit is a page load; creating new hit...")
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,9 @@ import uuid
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from django.contrib.auth.models import AbstractUser
 | 
					from django.contrib.auth.models import AbstractUser
 | 
				
			||||||
from django.db import models
 | 
					from django.db import models
 | 
				
			||||||
 | 
					from django.apps import apps
 | 
				
			||||||
 | 
					from django.utils import timezone
 | 
				
			||||||
 | 
					from django.db.utils import NotSupportedError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _default_uuid():
 | 
					def _default_uuid():
 | 
				
			||||||
@ -35,3 +38,63 @@ class Service(models.Model):
 | 
				
			|||||||
    status = models.CharField(
 | 
					    status = models.CharField(
 | 
				
			||||||
        max_length=2, choices=SERVICE_STATUSES, default=ACTIVE, db_index=True
 | 
					        max_length=2, choices=SERVICE_STATUSES, default=ACTIVE, db_index=True
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        return self.name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_daily_stats(self):
 | 
				
			||||||
 | 
					        return self.get_core_stats(
 | 
				
			||||||
 | 
					            start_time=timezone.now() - timezone.timedelta(days=1)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_core_stats(self, start_time=None, end_time=None):
 | 
				
			||||||
 | 
					        if start_time is None:
 | 
				
			||||||
 | 
					            start_time = timezone.now() - timezone.timedelta(days=30)
 | 
				
			||||||
 | 
					        if end_time is None:
 | 
				
			||||||
 | 
					            end_time = timezone.now()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Session = apps.get_model("analytics", "Session")
 | 
				
			||||||
 | 
					        Hit = apps.get_model("analytics", "Hit")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        currently_online = Session.objects.filter(
 | 
				
			||||||
 | 
					            service=self, start_time__gt=timezone.now() - timezone.timedelta(seconds=10)
 | 
				
			||||||
 | 
					        ).count()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sessions = Session.objects.filter(
 | 
				
			||||||
 | 
					            service=self, start_time__gt=start_time, start_time__lt=end_time
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        session_count = sessions.count()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        hits = Hit.objects.filter(
 | 
				
			||||||
 | 
					            session__service=self, start_time__lt=end_time, start_time__gt=start_time
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        hit_count = hits.count()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bounces = sessions.annotate(hit_count=models.Count("hit")).filter(hit_count=1)
 | 
				
			||||||
 | 
					        bounce_count = bounces.count()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            avg_session_duration = sessions.annotate(
 | 
				
			||||||
 | 
					                duration=models.F("last_seen") - models.F("start_time")
 | 
				
			||||||
 | 
					            ).aggregate(duration=models.Avg("duration"))["duration"]
 | 
				
			||||||
 | 
					        except NotSupportedError:
 | 
				
			||||||
 | 
					            avg_session_duration = (
 | 
				
			||||||
 | 
					                sum(
 | 
				
			||||||
 | 
					                    [
 | 
				
			||||||
 | 
					                        (session.last_seen - session.start_time).total_seconds()
 | 
				
			||||||
 | 
					                        for session in sessions
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                / session_count
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            "currently_online": currently_online,
 | 
				
			||||||
 | 
					            "sessions": session_count,
 | 
				
			||||||
 | 
					            "hits": hit_count,
 | 
				
			||||||
 | 
					            "avg_hits_per_session": hit_count / (max(session_count, 1)),
 | 
				
			||||||
 | 
					            "bounce_rate_pct": bounce_count * 100 / session_count,
 | 
				
			||||||
 | 
					            "avg_session_duration": avg_session_duration,
 | 
				
			||||||
 | 
					            "uptime": 99.9,
 | 
				
			||||||
 | 
					            "online": True,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -26,18 +26,18 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
          {{ emailaddress.email }}
 | 
					          {{ emailaddress.email }}
 | 
				
			||||||
          {% if emailaddress.verified %}
 | 
					          {% if emailaddress.verified %}
 | 
				
			||||||
          <span class="support ~positive">({% trans "Verified" %})</span>
 | 
					          <span class="badge ~positive">{% trans "Verified" %}</span>
 | 
				
			||||||
          {% else %}
 | 
					          {% else %}
 | 
				
			||||||
          <span class="support ~warning">({% trans "Unverified" %})</span>
 | 
					          <span class="badge ~critical">{% trans "Unverified" %}</span>
 | 
				
			||||||
          {% endif %}
 | 
					          {% endif %}
 | 
				
			||||||
          {% if emailaddress.primary %}<span class="support ~urge">({% trans "Primary" %})</span>{% endif %}
 | 
					          {% if emailaddress.primary %}<span class="badge ~urge">{% trans "Primary" %}</span>{% endif %}
 | 
				
			||||||
        </label>
 | 
					        </label>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      {% endfor %}
 | 
					      {% endfor %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <div class="block">
 | 
					      <div class="block mt-4">
 | 
				
			||||||
        <button class="button ~urge mb-1" type="submit" name="action_primary">{% trans 'Make Primary' %}</button>
 | 
					        <button class="button ~urge mb-1" type="submit" name="action_primary">{% trans 'Make Primary' %}</button>
 | 
				
			||||||
        <button class="button ~info mb-1" type="submit" name="action_send">{% trans 'Re-send Verification' %}</button>
 | 
					        <button class="button ~info mb-1" type="submit" name="action_send">{% trans 'Resend Verification' %}</button>
 | 
				
			||||||
        <button class="button ~critical mb-1" type="submit" name="action_remove">{% trans 'Remove' %}</button>
 | 
					        <button class="button ~critical mb-1" type="submit" name="action_remove">{% trans 'Remove' %}</button>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </fieldset>
 | 
					    </fieldset>
 | 
				
			||||||
@ -57,7 +57,7 @@
 | 
				
			|||||||
<form method="post" action="{% url 'account_email' %}" class="add_email max-w-lg">
 | 
					<form method="post" action="{% url 'account_email' %}" class="add_email max-w-lg">
 | 
				
			||||||
  {% csrf_token %}
 | 
					  {% csrf_token %}
 | 
				
			||||||
  {{ form|a17t }}
 | 
					  {{ form|a17t }}
 | 
				
			||||||
  <button name="button ~neutral !high" type="submit">{% trans "Add Email" %}</button>
 | 
					  <button name="action_add" class="button ~neutral !high" type="submit">{% trans "Add Email" %}</button>
 | 
				
			||||||
</form>
 | 
					</form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
 | 
				
			|||||||
@ -8,7 +8,7 @@
 | 
				
			|||||||
{% block content %}
 | 
					{% block content %}
 | 
				
			||||||
<div class="card ~neutral !low max-w-lg">
 | 
					<div class="card ~neutral !low max-w-lg">
 | 
				
			||||||
  <h1 class="heading mb-3">{% trans "Sign In" %}</h1>
 | 
					  <h1 class="heading mb-3">{% trans "Sign In" %}</h1>
 | 
				
			||||||
  <form class="login" method="POST" action="{% url 'account_login' %} max-w-lg">
 | 
					  <form class="login" method="POST" action="{% url 'account_login' %}">
 | 
				
			||||||
    {% csrf_token %}
 | 
					    {% csrf_token %}
 | 
				
			||||||
    {{ form|a17t }}
 | 
					    {{ form|a17t }}
 | 
				
			||||||
    {% if redirect_field_value %}
 | 
					    {% if redirect_field_value %}
 | 
				
			||||||
 | 
				
			|||||||
@ -14,7 +14,31 @@
 | 
				
			|||||||
<body class="bg-gray-100 min-h-full">
 | 
					<body class="bg-gray-100 min-h-full">
 | 
				
			||||||
  {% block body %}
 | 
					  {% block body %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <section class="max-w-4xl mx-auto px-6 md:py-12">
 | 
					  <section class="max-w-screen-lg mx-auto px-6 md:py-12 md:flex">
 | 
				
			||||||
 | 
					    <aside class="w-2/12 mr-4 block text-sm">
 | 
				
			||||||
 | 
					      <a class="icon ~urge ml-6 mb-8 mt-3" href="{% url 'core:dashboard' %}">
 | 
				
			||||||
 | 
					        <i class="fas fa-low-vision fa-3x text-purple-600"></i>
 | 
				
			||||||
 | 
					      </a>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {% if user.is_authenticated %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {% url 'account_email' as url %}
 | 
				
			||||||
 | 
					      {% include 'core/includes/sidebar_portal.html' with label="Emails" url=url %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {% url 'account_logout' as url %}
 | 
				
			||||||
 | 
					      {% include 'core/includes/sidebar_portal.html' with label="Log Out" url=url %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {% else %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {% url 'account_login' as url %}
 | 
				
			||||||
 | 
					      {% include 'core/includes/sidebar_portal.html' with label="Log In" url=url %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {% url 'account_signup' as url %}
 | 
				
			||||||
 | 
					      {% include 'core/includes/sidebar_portal.html' with label="Sign Up" url=url %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {% endif %}
 | 
				
			||||||
 | 
					    </aside>
 | 
				
			||||||
 | 
					    <div class="flex-grow">
 | 
				
			||||||
      {% if messages %}
 | 
					      {% if messages %}
 | 
				
			||||||
      <div>
 | 
					      <div>
 | 
				
			||||||
        {% for message in messages %}
 | 
					        {% for message in messages %}
 | 
				
			||||||
@ -23,23 +47,11 @@
 | 
				
			|||||||
        </ul>
 | 
					        </ul>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      {% endif %}
 | 
					      {% endif %}
 | 
				
			||||||
 | 
					 | 
				
			||||||
    <div>
 | 
					 | 
				
			||||||
      <strong>Menu:</strong>
 | 
					 | 
				
			||||||
      <ul>
 | 
					 | 
				
			||||||
        {% if user.is_authenticated %}
 | 
					 | 
				
			||||||
        <li><a href="{% url 'account_email' %}">Change Email</a></li>
 | 
					 | 
				
			||||||
        <li><a href="{% url 'account_logout' %}">Sign Out</a></li>
 | 
					 | 
				
			||||||
        {% else %}
 | 
					 | 
				
			||||||
        <li><a href="{% url 'account_login' %}">Sign In</a></li>
 | 
					 | 
				
			||||||
        <li><a href="{% url 'account_signup' %}">Sign Up</a></li>
 | 
					 | 
				
			||||||
        {% endif %}
 | 
					 | 
				
			||||||
      </ul>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
      <main>
 | 
					      <main>
 | 
				
			||||||
        {% block content %}
 | 
					        {% block content %}
 | 
				
			||||||
        {% endblock %}
 | 
					        {% endblock %}
 | 
				
			||||||
      </main>
 | 
					      </main>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
  </section>
 | 
					  </section>
 | 
				
			||||||
  {% endblock %}
 | 
					  {% endblock %}
 | 
				
			||||||
  {% block extra_body %}
 | 
					  {% block extra_body %}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										45
									
								
								shynet/core/templates/core/includes/service_overview.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								shynet/core/templates/core/includes/service_overview.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					{% load humanizelib %}
 | 
				
			||||||
 | 
					<article class="card ~neutral !low service mb-4">
 | 
				
			||||||
 | 
					    {% with stats=service.get_daily_stats %}
 | 
				
			||||||
 | 
					    <div class="md:flex justify-between">
 | 
				
			||||||
 | 
					        <div class="mr-4 md:w-1/4">
 | 
				
			||||||
 | 
					            <h3 class="heading text-gray-700 text-2xl mr-2 leading-none">
 | 
				
			||||||
 | 
					                {{service.name}}
 | 
				
			||||||
 | 
					            </h3>
 | 
				
			||||||
 | 
					            {% if stats.currently_online > 0 %}
 | 
				
			||||||
 | 
					            <span class="badge ~positive">
 | 
				
			||||||
 | 
					                {{stats.currently_online|intcomma}} online
 | 
				
			||||||
 | 
					            </span>
 | 
				
			||||||
 | 
					            {% elif stats.online == True %}
 | 
				
			||||||
 | 
					            <span class="badge ~positive">
 | 
				
			||||||
 | 
					                Online
 | 
				
			||||||
 | 
					            </span>
 | 
				
			||||||
 | 
					            {% elif stats.online == False %}
 | 
				
			||||||
 | 
					            <span class="badge ~critical">
 | 
				
			||||||
 | 
					                Offline
 | 
				
			||||||
 | 
					            </span>
 | 
				
			||||||
 | 
					            {% endif %}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="mr-2">
 | 
				
			||||||
 | 
					            <p class="font-medium text-sm">Sessions</p>
 | 
				
			||||||
 | 
					            <p class="text-xl text-purple-700 font-medium">{{stats.sessions|intcomma}}</p>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="mr-2">
 | 
				
			||||||
 | 
					            <p class="font-medium text-sm">Pages/Session</p>
 | 
				
			||||||
 | 
					            <p class="text-xl text-purple-700 font-medium">{{stats.avg_hits_per_session|floatformat:"-1"}}</p>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="mr-2">
 | 
				
			||||||
 | 
					            <p class="font-medium text-sm">Bounce Rate</p>
 | 
				
			||||||
 | 
					            <p class="text-xl text-purple-700 font-medium">{{stats.bounce_rate_pct|floatformat:"-1"}}%</p>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="mr-2">
 | 
				
			||||||
 | 
					            <p class="font-medium text-sm">Avg. Session</p>
 | 
				
			||||||
 | 
					            <p class="text-xl text-purple-700 font-medium">{{stats.avg_session_duration|naturaldelta}}</p>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="mr-2">
 | 
				
			||||||
 | 
					            <p class="font-medium text-sm">Uptime</p>
 | 
				
			||||||
 | 
					            <p class="text-xl text-purple-700 font-medium">{{stats.uptime|floatformat:"-1"}}%</p>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    {% endwith %}
 | 
				
			||||||
 | 
					</article>
 | 
				
			||||||
							
								
								
									
										1
									
								
								shynet/core/templates/core/includes/sidebar_portal.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								shynet/core/templates/core/includes/sidebar_portal.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					<a class="portal !low w-full {% if url == request.get_full_path %}~urge active bg-gray-100{% endif %}" href="{{url}}">{{label}}</a>
 | 
				
			||||||
							
								
								
									
										13
									
								
								shynet/core/templates/core/pages/dashboard.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								shynet/core/templates/core/pages/dashboard.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					{% extends "base.html" %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					<h4 class="heading text-lg leading-none text-purple-600">Shynet</h4>
 | 
				
			||||||
 | 
					<h4 class="heading text-4xl leading-none">Dashboard</h4>
 | 
				
			||||||
 | 
					<hr class="sep">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% for service in user.owning_services.all %}
 | 
				
			||||||
 | 
					{% include 'core/includes/service_overview.html' %}
 | 
				
			||||||
 | 
					{% empty %}
 | 
				
			||||||
 | 
					<p>You don't have any services.</p>
 | 
				
			||||||
 | 
					{% endfor %}
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
@ -5,4 +5,5 @@ from . import views
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
urlpatterns = [
 | 
					urlpatterns = [
 | 
				
			||||||
    path("", views.IndexView.as_view(), name="index"),
 | 
					    path("", views.IndexView.as_view(), name="index"),
 | 
				
			||||||
 | 
					    path("dash/", views.DashboardView.as_view(), name="dashboard"),
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,10 @@
 | 
				
			|||||||
from django.views.generic import TemplateView
 | 
					from django.views.generic import TemplateView
 | 
				
			||||||
 | 
					from django.contrib.auth.mixins import LoginRequiredMixin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class IndexView(TemplateView):
 | 
					class IndexView(TemplateView):
 | 
				
			||||||
    template_name = "core/pages/index.html"
 | 
					    template_name = "core/pages/index.html"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DashboardView(LoginRequiredMixin, TemplateView):
 | 
				
			||||||
 | 
					    template_name = "core/pages/dashboard.html"
 | 
				
			||||||
 | 
				
			|||||||
@ -38,6 +38,7 @@ INSTALLED_APPS = [
 | 
				
			|||||||
    "django.contrib.messages",
 | 
					    "django.contrib.messages",
 | 
				
			||||||
    "django.contrib.staticfiles",
 | 
					    "django.contrib.staticfiles",
 | 
				
			||||||
    "django.contrib.sites",
 | 
					    "django.contrib.sites",
 | 
				
			||||||
 | 
					    "django_humanize",
 | 
				
			||||||
    "a17t",
 | 
					    "a17t",
 | 
				
			||||||
    "core",
 | 
					    "core",
 | 
				
			||||||
    "analytics",
 | 
					    "analytics",
 | 
				
			||||||
@ -158,3 +159,4 @@ MESSAGE_TAGS = {
 | 
				
			|||||||
    messages.ERROR: "~critical",
 | 
					    messages.ERROR: "~critical",
 | 
				
			||||||
    messages.SUCCESS: "~positive",
 | 
					    messages.SUCCESS: "~positive",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user