Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Jason Carpenter 2020-04-24 17:14:46 -04:00
commit 1a9d57ed0c
11 changed files with 73 additions and 54 deletions

View File

@ -1,6 +1,15 @@
<img src="images/logo.png" height="50" alt="Shynet logo">
Modern, privacy-friendly, and cookie-free web analytics.
<p align="center">
<img align="center" src="images/logo.png" height="50" alt="Shynet logo">
<br>
<p align="center">
Modern, privacy-friendly, and cookie-free web analytics.
<br>
<strong><a href="#installation">Getting started »</a></strong>
</p>
<p align="center"><a href="#screenshots">Screenshots</a> &bull; <a href="#features">Features</a> &bull; <a href="#roadmap">Roadmap</a></p>
</p>
<br>
@ -31,10 +40,10 @@ Not shown: service view, management view, session view, full service view. (You'
#### Architecture
* **Runs on a single machine** &mdash; Because it's so small, Shynet can easily run as a single docker container on a single small VPS.
* **...or across a giant Kubernetes cluster** &mdash; For higher traffic installations, Shynet can be deployed with as many parallelized ingress nodes as needed, with Redis caching and separate backend workers for database IO.
* **Built using Django** &mdash; Shynet is built using Django, so deploying, updating, and migrating can be done without headaches.
* **Multiple users and sites** &mdash; A single Shynet instance can support multiple users, each tracking multiple different sites.
* **Runs on a single machine** &mdash; Because it's so small, Shynet can easily run as a single docker container on a single small VPS
* **...or across a giant Kubernetes cluster** &mdash; For higher traffic installations, Shynet can be deployed with as many parallelized ingress nodes as needed, with Redis caching and separate backend workers for database IO
* **Built using Django** &mdash; Shynet is built using Django, so deploying, updating, and migrating can be done without headaches
* **Multiple users and sites** &mdash; A single Shynet instance can support multiple users, each tracking multiple different sites
#### Tracking
@ -61,7 +70,7 @@ Here's the information Shynet can give you about your visitors:
#### Workflow
* **Collaboration built-in** &mdash; Administrators can easily share services with other users, as well
* **Accounts (or not)** &mdash; Shynet has a fully featured account management workflow (powered by [Django Allauth](https://github.com/pennersr/django-allauth/)).
* **Accounts (or not)** &mdash; Shynet has a fully featured account management workflow (powered by [Django Allauth](https://github.com/pennersr/django-allauth/))
## Recommendations
@ -79,13 +88,13 @@ Shynet is pretty simple, but there are a few key terms you need to know in order
## Installation
You can find installation instructions in our [Getting Started Guide](GUIDE.md#installation).
You can find installation instructions in the [Getting Started Guide](GUIDE.md#installation).
## FAQ
**Does Shynet respond to Do Not Track (DNT) signals?** Yes. While there isn't any standardized way to handle DNT requests, Shynet allows you to specify whether you want to collect any data from users with DNT enabled on a per-service basis. (By default, Shynet will _not_ collect any data from users who specify DNT.)
**Is this GDPR compliant?** It also depends on how you use it. If you're worried about GDPR, you should talk to a lawyer about your particular data collection practices. I'm not a lawyer. (And this isn't legal advice.)
**Is this GDPR compliant?** It depends on how you use it. If you're worried about GDPR, you should talk to a lawyer about your particular data collection practices. I'm not a lawyer. (And this isn't legal advice.)
## Roadmap

View File

@ -42,7 +42,15 @@ def _geoip2_lookup(ip):
@shared_task
def ingress_request(
service_uuid, tracker, time, payload, ip, location, user_agent, dnt=False, identifier=""
service_uuid,
tracker,
time,
payload,
ip,
location,
user_agent,
dnt=False,
identifier="",
):
try:
service = Service.objects.get(pk=service_uuid, status=Service.ACTIVE)
@ -78,7 +86,8 @@ def ingress_request(
if (
ua.is_bot
or (ua.browser.family or "").strip().lower() == "googlebot"
or (ua.device.family or ua.device.model or "").strip().lower() == "spider"
or (ua.device.family or ua.device.model or "").strip().lower()
== "spider"
):
device_type = "ROBOT"
elif ua.is_mobile:

View File

@ -2,14 +2,15 @@ import base64
import json
from django.conf import settings
from django.core.cache import cache
from django.http import HttpResponse
from django.shortcuts import render, reverse
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import TemplateView, View
from django.core.cache import cache
from ipware import get_client_ip
from core.models import Service
from ..tasks import ingress_request

View File

@ -1,10 +1,12 @@
from django.core.management.base import BaseCommand, CommandError
import traceback
import uuid
from django.conf import settings
from django.contrib.sites.models import Site
from core.models import User
from django.core.management.base import BaseCommand, CommandError
from django.utils.crypto import get_random_string
import uuid
import traceback
from core.models import User
class Command(BaseCommand):
@ -12,8 +14,7 @@ class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument(
"hostname",
type=str,
"hostname", type=str,
)
def handle(self, *args, **options):
@ -24,4 +25,4 @@ class Command(BaseCommand):
self.style.SUCCESS(
f"Successfully set the hostname to '{options.get('hostname')}'"
)
)
)

View File

@ -1,10 +1,12 @@
from django.core.management.base import BaseCommand, CommandError
import traceback
import uuid
from django.conf import settings
from django.contrib.sites.models import Site
from core.models import User
from django.core.management.base import BaseCommand, CommandError
from django.utils.crypto import get_random_string
import uuid
import traceback
from core.models import User
class Command(BaseCommand):
@ -12,18 +14,13 @@ class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument(
"email",
type=str,
"email", type=str,
)
def handle(self, *args, **options):
email = options.get("email")
password = get_random_string()
User.objects.create_superuser(
str(uuid.uuid4()), email=email, password=password
)
self.stdout.write(
self.style.SUCCESS("Successfully created a Shynet superuser")
)
User.objects.create_superuser(str(uuid.uuid4()), email=email, password=password)
self.stdout.write(self.style.SUCCESS("Successfully created a Shynet superuser"))
self.stdout.write(f"Email address: {email}")
self.stdout.write(f"Password: {password}")

View File

@ -1,10 +1,12 @@
from django.core.management.base import BaseCommand, CommandError
import traceback
import uuid
from django.conf import settings
from django.contrib.sites.models import Site
from core.models import User
from django.core.management.base import BaseCommand, CommandError
from django.utils.crypto import get_random_string
import uuid
import traceback
from core.models import User
class Command(BaseCommand):
@ -12,8 +14,7 @@ class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument(
"name",
type=str,
"name", type=str,
)
def handle(self, *args, **options):
@ -24,4 +25,4 @@ class Command(BaseCommand):
self.style.SUCCESS(
f"Successfully set the whitelabel to '{options.get('name')}'"
)
)
)

View File

@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0002_auto_20200415_1742'),
("core", "0002_auto_20200415_1742"),
]
operations = [
migrations.AddField(
model_name='service',
name='respect_dnt',
model_name="service",
name="respect_dnt",
field=models.BooleanField(default=True),
),
]

View File

@ -1,8 +1,8 @@
from allauth.account.admin import EmailAddress
from django import forms
from django.utils.translation import gettext_lazy as _
from core.models import Service, User
from allauth.account.admin import EmailAddress
class ServiceForm(forms.ModelForm):
@ -27,7 +27,10 @@ class ServiceForm(forms.ModelForm):
"respect_dnt": "Should visitors who have enabled <a href='https://en.wikipedia.org/wiki/Do_Not_Track'>Do Not Track</a> be excluded from all data?",
}
collaborators = forms.CharField(help_text="Which users should have read-only access to this service? (Comma separated list of emails.)", required=False)
collaborators = forms.CharField(
help_text="Which users should have read-only access to this service? (Comma separated list of emails.)",
required=False,
)
def clean_collaborators(self):
collaborators = []
@ -35,7 +38,9 @@ class ServiceForm(forms.ModelForm):
email = collaborator_email.strip()
if email == "":
continue
collaborator_email_linked = EmailAddress.objects.filter(email__iexact=email).first()
collaborator_email_linked = EmailAddress.objects.filter(
email__iexact=email
).first()
if collaborator_email_linked is None:
raise forms.ValidationError(f"Email '{email}' is not registered")
collaborators.append(collaborator_email_linked.user)

View File

@ -1,6 +1,6 @@
from datetime import datetime, time
from urllib.parse import urlparse
from datetime import time, datetime
from django.utils import timezone

View File

@ -1,6 +1,7 @@
from urllib.parse import urlparse
import pycountry
import flag
import pycountry
from django import template
from django.utils import timezone
from django.utils.html import escape
@ -41,17 +42,12 @@ def country_name(isocode):
@register.simple_tag
def relative_stat_tone(
start,
end,
good="UP",
good_classes=None,
bad_classes=None,
neutral_classes=None,
start, end, good="UP", good_classes=None, bad_classes=None, neutral_classes=None,
):
good_classes = good_classes or "~positive"
bad_classes = bad_classes or "~critical"
neutral_classes = neutral_classes or "~neutral"
if start == None or end == None or start == end:
return neutral_classes
if good == "UP":

View File

@ -1,5 +1,6 @@
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin
from django.core.cache import cache
from django.db.models import Q
from django.shortcuts import get_object_or_404, reverse
from django.utils import timezone
@ -12,7 +13,6 @@ from django.views.generic import (
UpdateView,
)
from rules.contrib.views import PermissionRequiredMixin
from django.core.cache import cache
from analytics.models import Session
from core.models import Service