Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
1a9d57ed0c
27
README.md
27
README.md
@ -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> • <a href="#features">Features</a> • <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** — 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** — 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** — Shynet is built using Django, so deploying, updating, and migrating can be done without headaches.
|
||||
* **Multiple users and sites** — A single Shynet instance can support multiple users, each tracking multiple different sites.
|
||||
* **Runs on a single machine** — 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** — 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** — Shynet is built using Django, so deploying, updating, and migrating can be done without headaches
|
||||
* **Multiple users and sites** — 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** — Administrators can easily share services with other users, as well
|
||||
* **Accounts (or not)** — Shynet has a fully featured account management workflow (powered by [Django Allauth](https://github.com/pennersr/django-allauth/)).
|
||||
* **Accounts (or not)** — 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
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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')}'"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -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}")
|
||||
|
@ -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')}'"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -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),
|
||||
),
|
||||
]
|
||||
|
@ -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)
|
||||
|
@ -1,6 +1,6 @@
|
||||
from datetime import datetime, time
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from datetime import time, datetime
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
|
@ -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":
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user