Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
23f1fdbb3f
30
GUIDE.md
30
GUIDE.md
@ -1,11 +1,4 @@
|
||||
<p align="center">
|
||||
<h3 align="center">🔭 Shynet 🔭</h3>
|
||||
|
||||
<p align="center">
|
||||
Web analytics that's self hosted, cookie free, privacy friendly, and useful(?)
|
||||
<br>
|
||||
</p>
|
||||
</p>
|
||||
# Getting Started
|
||||
|
||||
## Table of Contents
|
||||
|
||||
@ -50,6 +43,8 @@ DB_PORT=<your db port>
|
||||
DJANGO_SECRET_KEY=<your Django secret key; just a random string>
|
||||
# Don't leak error details to visitors, very important
|
||||
DEBUG=False
|
||||
# Unless you are using an external Celery task queue, make sure this
|
||||
# is set to True.
|
||||
CELERY_TASK_ALWAYS_EAGER=True
|
||||
# For better security, set this to your deployment's domain. Comma separated.
|
||||
ALLOWED_HOSTS=*
|
||||
@ -74,6 +69,14 @@ SERVER_EMAIL=Shynet <noreply@shynet.example.com>
|
||||
REDIS_CACHE_LOCATION=redis://redis.default.svc.cluster.local/0
|
||||
# If set, make sure CELERY_TASK_ALWAYS_EAGER is False
|
||||
CELERY_BROKER_URL=redis://redis.default.svc.cluster.local/1
|
||||
|
||||
# Other Shynet settings
|
||||
# How frequently should the monitoring script "phone home" (in ms)?
|
||||
SCRIPT_HEARTBEAT_FREQUENCY=5000
|
||||
# Should only superusers (admins) be able to create services? This is helpful
|
||||
# when you'd like to invite others to your Shynet instance but don't want
|
||||
# them to be able to create services of their own.
|
||||
ONLY_SUPERUSERS_CREATE=False
|
||||
```
|
||||
|
||||
4. Setup the Shynet database by running `docker run --env-file=<your env file> milesmcc/shynet:latest python manage.py migrate`.
|
||||
@ -138,6 +141,8 @@ DB_PORT=<your db port>
|
||||
DJANGO_SECRET_KEY=<your Django secret key; just a random string>
|
||||
# Don't leak error details to visitors, very important
|
||||
DEBUG=False
|
||||
# Unless you are using an external Celery task queue, make sure this
|
||||
# is set to True.
|
||||
CELERY_TASK_ALWAYS_EAGER=True
|
||||
# For better security, set this to your deployment's domain. Comma separated.
|
||||
ALLOWED_HOSTS=*
|
||||
@ -162,6 +167,15 @@ SERVER_EMAIL=Shynet <noreply@shynet.example.com>
|
||||
REDIS_CACHE_LOCATION=redis://redis.default.svc.cluster.local/0
|
||||
# If set, make sure CELERY_TASK_ALWAYS_EAGER is False
|
||||
CELERY_BROKER_URL=redis://redis.default.svc.cluster.local/1
|
||||
|
||||
|
||||
# Other Shynet settings
|
||||
# How frequently should the monitoring script "phone home" (in ms)?
|
||||
SCRIPT_HEARTBEAT_FREQUENCY=5000
|
||||
# Should only superusers (admins) be able to create services? This is helpful
|
||||
# when you'd like to invite others to your Shynet instance but don't want
|
||||
# them to be able to create services of their own.
|
||||
ONLY_SUPERUSERS_CREATE=False
|
||||
```
|
||||
|
||||
9. Setup the Shynet database by running `docker run --env-file=<your env file> milesmcc/shynet:latest-ssl python manage.py migrate`.
|
||||
|
16
README.md
16
README.md
@ -1,14 +1,8 @@
|
||||
<p align="center">
|
||||
<h3 align="center">🔭 Shynet 🔭</h3>
|
||||
<img src="images/logo.png" height="50" alt="Shynet logo">
|
||||
|
||||
Modern, privacy-friendly, and cookie-free web analytics.
|
||||
|
||||
<p align="center">
|
||||
Web analytics that's self hosted, cookie free, privacy friendly, and useful(?)
|
||||
<br>
|
||||
<br>
|
||||
<a href="#installation"><strong>Getting started »</strong></a>
|
||||
<br>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
## Motivation
|
||||
|
||||
@ -85,7 +79,7 @@ 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 our [Getting Started Guide](GUIDE.md#installation).
|
||||
|
||||
## FAQ
|
||||
|
||||
@ -118,4 +112,4 @@ Shynet is made available under the [Apache License, version 2.0](LICENSE).
|
||||
|
||||
---
|
||||
|
||||
a17t was created by [Miles McCain](https://miles.land) at the [Recurse Center](https://recurse.com) using [a17t](https://a17t.miles.land).
|
||||
Shynet was created by [Miles McCain](https://miles.land) ([@MilesMcCain](https://twitter.com/MilesMcCain)) at the [Recurse Center](https://recurse.com) using [a17t](https://a17t.miles.land).
|
||||
|
BIN
images/logo.png
Normal file
BIN
images/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
@ -1,3 +1,10 @@
|
||||
<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://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--family-primary: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--family-secondary: var(--family-primary);
|
||||
}
|
||||
</style>
|
@ -4,6 +4,9 @@ window.onload = function () {
|
||||
Math.random().toString(36).substring(2, 15);
|
||||
function sendUpdate() {
|
||||
try {
|
||||
if (document.hidden) {
|
||||
return;
|
||||
}
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open(
|
||||
"POST",
|
||||
@ -23,6 +26,6 @@ window.onload = function () {
|
||||
);
|
||||
} catch { }
|
||||
}
|
||||
setInterval(sendUpdate, 5000);
|
||||
setInterval(sendUpdate, parseInt("{{heartbeat_frequency}}"));
|
||||
sendUpdate();
|
||||
};
|
||||
|
@ -8,7 +8,9 @@ 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
|
||||
|
||||
@ -58,8 +60,15 @@ class PixelView(View):
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class ScriptView(View):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
service_uuid = self.kwargs.get("service_uuid")
|
||||
origins = cache.get(f"service_origins_{service_uuid}")
|
||||
if origins is None:
|
||||
service = Service.objects.get(uuid=service_uuid)
|
||||
origins = service.origins
|
||||
cache.set(f"service_origins_{service_uuid}", origins, timeout=3600)
|
||||
|
||||
resp = super().dispatch(request, *args, **kwargs)
|
||||
resp["Access-Control-Allow-Origin"] = "*"
|
||||
resp["Access-Control-Allow-Origin"] = origins
|
||||
resp["Access-Control-Allow-Methods"] = "GET,HEAD,OPTIONS,POST"
|
||||
resp[
|
||||
"Access-Control-Allow-Headers"
|
||||
@ -82,10 +91,15 @@ class ScriptView(View):
|
||||
},
|
||||
)
|
||||
)
|
||||
heartbeat_frequency = settings.SCRIPT_HEARTBEAT_FREQUENCY
|
||||
return render(
|
||||
self.request,
|
||||
"analytics/scripts/page.js",
|
||||
context={"endpoint": endpoint, "protocol": protocol},
|
||||
context={
|
||||
"endpoint": endpoint,
|
||||
"protocol": protocol,
|
||||
"heartbeat_frequency": heartbeat_frequency,
|
||||
},
|
||||
content_type="application/javascript",
|
||||
)
|
||||
|
||||
|
@ -8,7 +8,7 @@ from allauth.account.admin import EmailAddress
|
||||
class ServiceForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Service
|
||||
fields = ["name", "link", "respect_dnt", "collaborators"]
|
||||
fields = ["name", "link", "respect_dnt", "origins", "collaborators"]
|
||||
widgets = {
|
||||
"name": forms.TextInput(),
|
||||
"origins": forms.TextInput(),
|
||||
@ -29,7 +29,6 @@ class ServiceForm(forms.ModelForm):
|
||||
|
||||
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 = []
|
||||
for collaborator_email in self.cleaned_data["collaborators"].split(","):
|
||||
|
@ -0,0 +1,12 @@
|
||||
{% load a17t_tags %}
|
||||
|
||||
{{form.name|a17t}}
|
||||
{{form.link|a17t}}
|
||||
{{form.collaborators|a17t}}
|
||||
|
||||
<details class="p-4 border rounded">
|
||||
<summary class="cursor-pointer text-sm">Advanced settings</summary>
|
||||
<hr class="sep h-4">
|
||||
{{form.respect_dnt|a17t}}
|
||||
{{form.origins|a17t}}
|
||||
</details>
|
@ -10,7 +10,7 @@
|
||||
<form class="card ~neutral !low p-0 max-w-xl" method="POST">
|
||||
{% csrf_token %}
|
||||
<div class="p-4">
|
||||
{{form|a17t}}
|
||||
{% include 'dashboard/includes/service_form.html' %}
|
||||
</div>
|
||||
<div class="section ~urge !normal p-4">
|
||||
<button type="submit" class="button ~urge !high">Create</button>
|
||||
|
@ -23,7 +23,7 @@
|
||||
<form class="card ~neutral !low p-0" method="POST">
|
||||
{% csrf_token %}
|
||||
<div class="p-4">
|
||||
{{form|a17t}}
|
||||
{% include 'dashboard/includes/service_form.html' %}
|
||||
</div>
|
||||
<div class="section ~neutral !normal p-4 flex justify-between">
|
||||
<div>
|
||||
|
@ -12,6 +12,7 @@ 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
|
||||
@ -77,6 +78,13 @@ class ServiceUpdateView(
|
||||
def get_success_url(self):
|
||||
return reverse("dashboard:service", kwargs={"pk": self.object.uuid})
|
||||
|
||||
def form_valid(self, *args, **kwargs):
|
||||
resp = super().form_valid(*args, **kwargs)
|
||||
cache.set(
|
||||
f"service_origins_{self.object.uuid}", self.object.origins, timeout=3600
|
||||
)
|
||||
return resp
|
||||
|
||||
|
||||
class ServiceDeleteView(
|
||||
LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, DeleteView
|
||||
|
@ -256,3 +256,7 @@ ONLY_SUPERUSERS_CREATE = os.getenv("ONLY_SUPERUSERS_CREATE", "True") == "True"
|
||||
# Should the script use HTTPS to send the POST requests? The hostname is from
|
||||
# the django SITE default. (Edit it using the admin panel.)
|
||||
SCRIPT_USE_HTTPS = os.getenv("SCRIPT_USE_HTTPS", "True") == "True"
|
||||
|
||||
# How frequently should the tracking script "phone home" with a heartbeat, in
|
||||
# milliseconds?
|
||||
SCRIPT_HEARTBEAT_FREQUENCY = int(os.getenv("SCRIPT_HEARTBEAT_FREQUENCY", "5000"))
|
||||
|
@ -7,8 +7,8 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript><img src="//localhost:8000/ingress/211410e6-b401-4fe8-8740-7926368590be/pixel.gif"></noscript>
|
||||
<script src="//localhost:8000/ingress/211410e6-b401-4fe8-8740-7926368590be/script.js"></script>
|
||||
<noscript><img src="//localhost:8000/ingress/test_uuid/pixel.gif"></noscript>
|
||||
<script src="//localhost:8000/ingress/test_uuid/script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user