Compare commits

..

20 Commits

Author SHA1 Message Date
imgbot[bot]
c73f96525a [ImgBot] Optimize images (#55)
*Total -- 911.50kb -> 583.94kb (35.94%)

/images/slogo.png -- 2.51kb -> 0.91kb (63.77%)
/images/service.png -- 589.15kb -> 359.70kb (38.95%)
/images/homepage.png -- 307.99kb -> 214.75kb (30.28%)
/images/logo.png -- 11.85kb -> 8.58kb (27.54%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>

Co-authored-by: ImgBotApp <ImgBotHelp@gmail.com>
2020-06-28 15:11:48 -04:00
R. Miles McCain
510df192d8 Add script injection option 2020-06-28 18:59:05 +00:00
R. Miles McCain
2e7620f1eb Bump version 2020-06-28 17:56:29 +00:00
R. Miles McCain
93d4ee5241 Use specific version of pipenv in dockerfile 2020-06-28 17:56:09 +00:00
R. Miles McCain
1a7594be93 Use SHA256 for more secure session association 2020-06-28 17:55:59 +00:00
R. Miles McCain
f464a7ee67 Add automatic release drafter 2020-06-28 17:47:58 +00:00
R. Miles McCain
a1cd3d4609 Code cleanup 2020-06-28 17:36:20 +00:00
R. Miles McCain
358fb234a7 Fix multiple origin support (closes #52) 2020-06-28 17:36:12 +00:00
R. Miles McCain
94fed58de3 Fix charts 2020-06-28 17:18:58 +00:00
R. Miles McCain
49f452d9f2 Lock Python package versions 2020-06-28 17:11:08 +00:00
R. Miles McCain
40d07fe159 Add IE11 support 2020-06-28 04:10:06 +00:00
R. Miles McCain
e150e6bede Bump version 2020-06-28 04:01:50 +00:00
R. Miles McCain
87a411f42d Make ingress processing more resilient 2020-06-28 03:48:40 +00:00
R. Miles McCain
88f25b6743 Analytics script cleanup (closes #54) 2020-06-28 03:31:10 +00:00
R. Miles McCain
bb0dc2e90f Remove all external dependencies 2020-06-28 02:58:49 +00:00
R. Miles McCain
4a8939796e Arbitrarily update the pixel test 2020-06-28 02:45:46 +00:00
R. Miles McCain
ba795ccd5c Clarify collaborators description 2020-06-28 02:44:59 +00:00
Alexandre Bulté
c9b5a677d3 SIGNUPS_ENABLED -> ACCOUNT_SIGNUPS_ENABLED in docs (#51) 2020-06-18 15:15:10 -04:00
Alexandre Bulté
affcb893fa Add timeout to curl / geoip (#50)
W/o timeout, if the download is unresponsive it can be a pain on some environments (eg `dokku`) because the build hangs forever.
2020-06-18 14:00:06 -04:00
R. Miles McCain
e030807acb Fix GeoIP2 license keys 2020-06-18 16:46:39 +00:00
32 changed files with 3305 additions and 109 deletions

4
.github/release-drafter.yml vendored Normal file
View File

@@ -0,0 +1,4 @@
template: |
## Whats Changed
$CHANGES

14
.github/workflows/draft.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
name: Release Drafter
on:
push:
branches:
- master
jobs:
update_release_draft:
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@v5.11.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

3
.gitignore vendored
View File

@@ -3,6 +3,9 @@ __pycache__/
*.py[cod]
*$py.class
# JavaScript packages
node_modules/
# C extensions
*.so

View File

@@ -3,20 +3,22 @@ FROM python:3-alpine
# Getting things ready
WORKDIR /usr/src/shynet
COPY Pipfile.lock Pipfile ./
COPY package.json package-lock.json ../
# Django expects node_modules to be in its parent directory.
# Install dependencies & configure machine
ARG GF_UID="500"
ARG GF_GID="500"
RUN apk update && \
apk add gettext curl bash && \
# URL from https://github.com/shlinkio/shlink/issues/596 :)
curl "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-ASN&license_key=G4Lm0C60yJsnkdPi&suffix=tar.gz" | tar -xvz -C /tmp && \
curl "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=G4Lm0C60yJsnkdPi&suffix=tar.gz" | tar -xvz -C /tmp && \
apk add gettext curl bash npm && \
curl -m 180 "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-ASN&license_key=kKG1ebhL3iWVd0iv&suffix=tar.gz" | tar -xvz -C /tmp && \
curl -m 180 "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=kKG1ebhL3iWVd0iv&suffix=tar.gz" | tar -xvz -C /tmp && \
mv /tmp/GeoLite2*/*.mmdb /etc && \
apk del curl && \
apk add --no-cache postgresql-libs && \
apk add --no-cache --virtual .build-deps gcc musl-dev postgresql-dev && \
pip install pipenv && \
npm i -P --prefix .. && \
pip install pipenv~=2020.6.2 && \
pipenv install --system --deploy && \
apk --purge del .build-deps && \
rm -rf /var/lib/apt/lists/* && \

49
Pipfile
View File

@@ -3,29 +3,30 @@ name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
black = "*"
[packages]
django = "*"
django-allauth = "*"
geoip2 = "*"
whitenoise = "*"
celery = "*"
django-ipware = "*"
pyyaml = "*"
ua-parser = "*"
user-agents = "*"
emoji-country-flag = "*"
rules = "*"
gunicorn = "*"
psycopg2-binary = "*"
redis = "*"
django-redis-cache = "*"
pycountry = "*"
ipaddress = "*"
html2text = "*"
django-health-check = "*"
[pipenv]
allow_prereleases = true
[packages]
django = "~=3.0"
django-allauth = "~=0.42.0"
geoip2 = "~=3.0.0"
whitenoise = "~=5.1.0"
celery = "~=4.4.6"
django-ipware = "~=2.1.0"
pyyaml = "~=5.3.1"
ua-parser = "~=0.10.0"
user-agents = "~=2.1"
emoji-country-flag = "~=1.2.1"
rules = "~=2.2"
gunicorn = "~=20.0.4"
psycopg2-binary = "~=2.8.5"
redis = "~=3.5.3"
django-redis-cache = "~=2.1.1"
pycountry = "~=19.8.18"
ipaddress = "~=1.0.23"
html2text = "~=2020.1.16"
django-health-check = "~=3.12.1"
django-npm = "~=1.0.0"
[dev-packages]
black = "*"

51
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "327b897f359bad486c08fc88fb70a1f9d2edaf1aadafcb1d31e5b3e144125ff7"
"sha256": "ff989ac3413a6bd2253c9350c8f368a91942393221f1e47e5e39e60e457cc590"
},
"pipfile-spec": 6,
"requires": {},
@@ -23,10 +23,10 @@
},
"asgiref": {
"hashes": [
"sha256:8036f90603c54e93521e5777b2b9a39ba1bad05773fcf2d208f0299d1df58ce5",
"sha256:9ca8b952a0a9afa61d30aa6d3d9b570bb3fd6bafcf7ec9e6bed43b936133db1c"
"sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a",
"sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"
],
"version": "==3.2.7"
"version": "==3.2.10"
},
"billiard": {
"hashes": [
@@ -37,18 +37,18 @@
},
"celery": {
"hashes": [
"sha256:c3f4173f83ceb5a5c986c5fdaefb9456de3b0729a72a5776e46bd405fda7b647",
"sha256:d1762d6065522879f341c3d67c2b9fe4615eb79756d59acb1434601d4aca474b"
"sha256:ef17d7dffde7fc73ecab3a3b6389d93d3213bac53fa7f28e68e33647ad50b916",
"sha256:fd77e4248bb1b7af5f7922dd8e81156f540306e3a5c4b1c24167c1f5f06025da"
],
"index": "pypi",
"version": "==4.4.5"
"version": "==4.4.6"
},
"certifi": {
"hashes": [
"sha256:5ad7e9a056d25ffa5082862e36f119f7f7cec6457fa07ee2f8c339814b80c9b1",
"sha256:9cd41137dc19af6a5e03b630eefe7d1f458d964d406342dd3edf625839b944cc"
"sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3",
"sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"
],
"version": "==2020.4.5.2"
"version": "==2020.6.20"
},
"chardet": {
"hashes": [
@@ -66,11 +66,11 @@
},
"django": {
"hashes": [
"sha256:db4c9b29615d17f808f2b1914d5cd73cd457c9fd90581195172c0888c210d944",
"sha256:dd96f98ec1c3e60877d45cea7350215f16de409848d23cced8443db1b188bd9b"
"sha256:045be31d68dfed684831e39ab1d9e77a595f1a393935cb43b6c5451d2e78c8a4",
"sha256:ccf6c208424c0e1b0eaffd36efe12618a9ab4d0037e26f6ffceaa5277af985d7"
],
"index": "pypi",
"version": "==3.1a1"
"version": "==3.1b1"
},
"django-allauth": {
"hashes": [
@@ -94,6 +94,13 @@
"index": "pypi",
"version": "==2.1.0"
},
"django-npm": {
"hashes": [
"sha256:2e6bba65e728fa18b9db3c8dc0d4490b70cb7f43bacf60eb3654d7dcb6424272"
],
"index": "pypi",
"version": "==1.0.0"
},
"django-redis-cache": {
"hashes": [
"sha256:06d4e48545243883f88dc9263dda6c8a0012cb7d0cee2d8758d8917eca92cece",
@@ -142,10 +149,10 @@
},
"idna": {
"hashes": [
"sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
"sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
],
"version": "==2.9"
"version": "==2.10"
},
"ipaddress": {
"hashes": [
@@ -157,10 +164,10 @@
},
"kombu": {
"hashes": [
"sha256:437b9cdea193cc2ed0b8044c85fd0f126bb3615ca2f4d4a35b39de7cacfa3c1a",
"sha256:dc282bb277197d723bccda1a9ba30a27a28c9672d0ab93e9e51bb05a37bd29c3"
"sha256:be48cdffb54a2194d93ad6533d73f69408486483d189fe9f5990ee24255b0e0a",
"sha256:ca1b45faac8c0b18493d02a8571792f3c40291cf2bcf1f55afed3d8f3aa7ba74"
],
"version": "==4.6.10"
"version": "==4.6.11"
},
"maxminddb": {
"hashes": [
@@ -259,10 +266,10 @@
},
"requests": {
"hashes": [
"sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee",
"sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"
"sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
"sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
],
"version": "==2.23.0"
"version": "==2.24.0"
},
"requests-oauthlib": {
"hashes": [

View File

@@ -23,7 +23,7 @@ DJANGO_SECRET_KEY=random_string
ALLOWED_HOSTS=*
# Set to True (capitalized) if you want people to be able to sign up for your Shynet instance (not recommended)
SIGNUPS_ENABLED=False
ACCOUNT_SIGNUPS_ENABLED=False
# Should user email addresses be verified? Only set this to `required` if you've setup the email settings and allow
# public sign-ups; otherwise, it's unnecessary.
@@ -59,8 +59,8 @@ PORT=8080
# Don't uncomment these unless you know what you are doing!
# NUM_WORKERS=1
# Make sure you set a REDIS_CACHE_LOCATION if you have more than one frontend worker/instance.
# REDIS_CACHE_LOCATION=redis://redis.default.svc.cluster.local/0
# REDIS_CACHE_LOCATION=redis://redis.default.svc.cluster.local/0
# If CELERY_BROKER_URL is set, make sure CELERY_TASK_ALWAYS_EAGER is False and
# that you have a separate queue consumer running somewhere via `celeryworker.sh`.
# CELERY_TASK_ALWAYS_EAGER=False
# CELERY_BROKER_URL=redis://redis.default.svc.cluster.local/1
# CELERY_BROKER_URL=redis://redis.default.svc.cluster.local/1

View File

@@ -83,7 +83,7 @@
"value": "*",
"required": false
},
"SIGNUPS_ENABLED": {
"ACCOUNT_SIGNUPS_ENABLED": {
"description": "Set to True (capitalized) if you want people to be able to sign up for your Shynet instance (not recommended).",
"value": "False",
"required": false
@@ -119,4 +119,4 @@
"required": false
}
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 308 KiB

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 589 KiB

After

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 931 B

View File

@@ -8,7 +8,7 @@ stringData:
DEBUG: "False"
ALLOWED_HOSTS: "*" # For better security, set this to your deployment's domain. Comma separated.
DJANGO_SECRET_KEY: ""
SIGNUPS_ENABLED: "False"
ACCOUNT_SIGNUPS_ENABLED: "False"
TIME_ZONE: "America/New_York"
# Redis configuration (if you use the default Kubernetes config, this will work)

3032
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

28
package.json Normal file
View File

@@ -0,0 +1,28 @@
{
"name": "shynet",
"description": "Modern, privacy-friendly, and cookie-free web analytics.",
"repository": {
"type": "git",
"url": "git+https://github.com/milesmcc/shynet.git"
},
"keywords": [
"privacy",
"analytics",
"self-host"
],
"author": "R. Miles McCain <shynet@sendmiles.email>",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/milesmcc/shynet/issues"
},
"homepage": "https://github.com/milesmcc/shynet#readme",
"dependencies": {
"@fortawesome/fontawesome-free": "^5.13.1",
"a17t": "^0.1.4",
"apexcharts": "^3.19.3",
"inter-ui": "^3.13.1",
"litepicker": "^1.5.7",
"tailwindcss": "^1.4.6",
"turbolinks": "^5.2.0"
}
}

View File

@@ -1,7 +1,9 @@
<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">
{% load static %}
<link rel="stylesheet" href="{% static 'a17t/dist/a17t.css' %}">
<script async src="{% static '@fortawesome/fontawesome-free/js/all.min.js' %}" data-mutate-approach="sync"></script>
<link href="{% static 'tailwindcss/dist/tailwind.min.css' %}" rel="stylesheet">
<link href="{% static 'inter-ui/Inter (web)/inter.css' %}" 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";

View File

@@ -92,4 +92,6 @@ def is_file(field):
def add_class(field, css_class):
if len(field.errors) > 0:
css_class += " ~critical"
if field.field.widget.attrs.get("class") != None:
css_class += " " + field.field.widget.attrs["class"]
return field.as_widget(attrs={"class": field.css_classes(extra_classes=css_class)})

View File

@@ -1,7 +1,7 @@
import ipaddress
import json
import logging
from hashlib import sha1
from hashlib import sha256
import geoip2.database
import user_agents
@@ -73,7 +73,7 @@ def ingress_request(
if payload.get("loadTime", 1) <= 0:
payload["loadTime"] = None
association_id_hash = sha1()
association_id_hash = sha256()
association_id_hash.update(str(ip).encode("utf-8"))
association_id_hash.update(str(user_agent).encode("utf-8"))
session_cache_path = (
@@ -121,11 +121,11 @@ def ingress_request(
device=ua.device.family or ua.device.model or "",
device_type=device_type,
os=ua.os.family or "",
asn=ip_data.get("asn", ""),
country=ip_data.get("country", ""),
asn=ip_data.get("asn") or "",
country=ip_data.get("country") or "",
longitude=ip_data.get("longitude"),
latitude=ip_data.get("latitude"),
time_zone=ip_data.get("time_zone", ""),
time_zone=ip_data.get("time_zone") or "",
)
cache.set(
session_cache_path, session.pk, timeout=settings.SESSION_MEMORY_TIMEOUT

View File

@@ -1,8 +1,14 @@
window.onload = function () {
var idempotency =
Math.random().toString(36).substring(2, 15) +
Math.random().toString(36).substring(2, 15);
function sendUpdate() {
// This is a lightweight and privacy-friendly analytics script from Shynet, a self-hosted
// analytics tool. To give you full visibility into how your data is being monitored, this
// file is intentionally not minified or obfuscated. To learn more about Shynet (and to view
// its source code), visit <https://github.com/milesmcc/shynet>.
//
// This script only sends the current URL, the referrer URL, and the page load time. That's it!
var Shynet = {
idempotency: null,
heartbeatTaskId: null,
sendHeartbeat: function () {
try {
if (document.hidden) {
return;
@@ -16,7 +22,7 @@ window.onload = function () {
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(
JSON.stringify({
idempotency: idempotency,
idempotency: Shynet.idempotency,
referrer: document.referrer,
location: window.location.href,
loadTime:
@@ -24,8 +30,25 @@ window.onload = function () {
window.performance.timing.navigationStart,
})
);
} catch { }
} catch (e) { }
},
newPageLoad: function () {
if (Shynet.heartbeatTaskId != null) {
clearInterval(Shynet.heartbeatTaskId);
}
Shynet.idempotency = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
Shynet.heartbeatTaskId = setInterval(Shynet.sendHeartbeat, parseInt("{{heartbeat_frequency}}"));
Shynet.sendHeartbeat();
}
setInterval(sendUpdate, parseInt("{{heartbeat_frequency}}"));
sendUpdate();
};
window.addEventListener("load", Shynet.newPageLoad);
{% if script_inject %}
// The following is script is not part of Shynet, and was instead
// provided by this site's administrator.
//
// -- START --
{{script_inject|safe}}
// -- END --
{% endif %}

View File

@@ -1,5 +1,6 @@
import base64
import json
from urllib.parse import urlparse
from django.conf import settings
from django.core.cache import cache
@@ -49,7 +50,15 @@ class ValidateServiceOriginsMixin:
cache.set(f"service_origins_{service_uuid}", origins, timeout=3600)
resp = super().dispatch(request, *args, **kwargs)
resp["Access-Control-Allow-Origin"] = origins
if origins != "*":
remote_origin = request.META.get("HTTP_ORIGIN")
origins = [origin.strip() for origin in origins.split(",")]
if remote_origin in origins:
resp["Access-Control-Allow-Origin"] = remote_origin
else:
resp["Access-Control-Allow-Origin"] = "*"
resp["Access-Control-Allow-Methods"] = "GET,HEAD,OPTIONS,POST"
resp[
"Access-Control-Allow-Headers"
@@ -105,11 +114,14 @@ class ScriptView(ValidateServiceOriginsMixin, View):
return render(
self.request,
"analytics/scripts/page.js",
context={
"endpoint": endpoint,
"protocol": protocol,
"heartbeat_frequency": heartbeat_frequency,
},
context=dict(
{
"endpoint": endpoint,
"protocol": protocol,
"heartbeat_frequency": heartbeat_frequency,
"script_inject": self.get_script_inject(),
}
),
content_type="application/javascript",
)
@@ -125,3 +137,12 @@ class ScriptView(ValidateServiceOriginsMixin, View):
return HttpResponse(
json.dumps({"status": "OK"}), content_type="application/json"
)
def get_script_inject(self):
service_uuid = self.kwargs.get("service_uuid")
script_inject = cache.get(f"script_inject_{service_uuid}")
if script_inject == None:
service = Service.objects.get(uuid=service_uuid)
script_inject = service.script_inject
cache.set(f"script_inject_{service_uuid}", script_inject, timeout=3600)
return script_inject

View File

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

View File

@@ -0,0 +1,23 @@
# Generated by Django 3.1b1 on 2020-06-28 18:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0007_service_ignore_robots'),
]
operations = [
migrations.AddField(
model_name='service',
name='script_inject',
field=models.TextField(blank=True, default=''),
),
migrations.AlterField(
model_name='user',
name='first_name',
field=models.CharField(blank=True, max_length=150, verbose_name='first name'),
),
]

View File

@@ -73,6 +73,7 @@ class Service(models.Model):
hide_referrer_regex = models.TextField(
default="", blank=True, validators=[_validate_regex]
)
script_inject = models.TextField(default="", blank=True)
class Meta:
ordering = ["name", "uuid"]
@@ -234,7 +235,9 @@ class Service(models.Model):
"session_chart_data": json.dumps(
[
{"x": str(key), "y": value}
for key, value in session_chart_data.items()
for key, value in sorted(
session_chart_data.items(), key=lambda k: k[0]
)
]
),
"online": True,

View File

@@ -18,6 +18,7 @@ class ServiceForm(forms.ModelForm):
"hide_referrer_regex",
"origins",
"collaborators",
"script_inject"
]
widgets = {
"name": forms.TextInput(),
@@ -27,6 +28,7 @@ class ServiceForm(forms.ModelForm):
"collect_ips": forms.RadioSelect(choices=[(True, "Yes"), (False, "No")]),
"ignore_robots": forms.RadioSelect(choices=[(True, "Yes"), (False, "No")]),
"hide_referrer_regex": forms.TextInput(),
"script_inject": forms.Textarea(attrs={'class':'font-mono', 'rows': 5})
}
labels = {
"origins": "Allowed Hostnames",
@@ -35,6 +37,7 @@ class ServiceForm(forms.ModelForm):
"ignored_ips": "Ignored IP addresses",
"ignore_robots": "Ignore robots",
"hide_referrer_regex": "Hide specific referrers",
"script_inject": "Additional injected JS",
}
help_texts = {
"name": _("What should the service be called?"),
@@ -47,10 +50,11 @@ class ServiceForm(forms.ModelForm):
"ignored_ips": "A comma-separated list of IP addresses or IP ranges (IPv4 and IPv6) to exclude from tracking (e.g., '192.168.0.2, 127.0.0.1/32').",
"ignore_robots": "Should sessions generated by bots be excluded from tracking?",
"hide_referrer_regex": "Any referrers that match this <a href='https://regexr.com/'>RegEx</a> will not be listed in the referrer summary. Sessions will still be tracked normally. No effect if left blank.",
"script_inject": "Optional additional JavaScript to inject at the end of the Shynet script. This code will be injected on every page where this service is installed.",
}
collaborators = forms.CharField(
help_text="Which users should have read-only access to this service? (Comma separated list of emails.)",
help_text="Which users on this Shynet instance should have read-only access to this service? (Comma separated list of emails.)",
required=False,
)

View File

@@ -8,10 +8,10 @@
<meta name="robots" content="noindex">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% include 'a17t/head.html' %}
<script src="https://cdn.jsdelivr.net/npm/litepicker@1.2.0/dist/js/main.js"
integrity="sha256-mOlCEHUNWZPYIrc5OFL4Ab2rsJGzIPld3cy1ok7Cfx0=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/apexcharts@3.18.1/dist/apexcharts.min.js"
integrity="sha256-RalQXBZdisB04aaBsm+6YZ0b/iRYjX1MZn90m19AnCY=" crossorigin="anonymous"></script>
<script src="{% static 'apexcharts/dist/apexcharts.min.js'%}"></script>
<script src="{% static 'litepicker/dist/js/main.js' %}"></script>
<script src="{% static 'turbolinks/dist/turbolinks.js' %}"></script>
<script src="{% static 'dashboard/js/base.js' %}"></script>
<link rel="stylesheet" href="{% static 'dashboard/css/global.css' %}">
{% block extra_head %}
{% endblock %}
@@ -72,7 +72,7 @@
{% if user.is_superuser %}
{% url 'admin:index' as url %}
{% include 'dashboard/includes/sidebar_portal.html' with label="Admin" url=url %}
{% include 'dashboard/includes/sidebar_portal.html' with label="Admin" disable_turbolinks=True url=url %}
{% endif %}
{% url 'account_email' as url %}

View File

@@ -13,4 +13,5 @@
{{form.ignore_robots|a17t}}
{{form.hide_referrer_regex|a17t}}
{{form.origins|a17t}}
{{form.script_inject|a17t}}
</details>

View File

@@ -2,5 +2,5 @@
<div>
<a class="portal !low {% if request.get_full_path|startswith:url %}~urge active bg-gray-100{% endif %}"
href="{{url}}">{{label}}</a>
{% if disable_turbolinks %}data-turbolinks="false"{% endif %} href="{{url}}">{{label}}</a>
</div>

View File

@@ -84,6 +84,9 @@ class ServiceUpdateView(
cache.set(
f"service_origins_{self.object.uuid}", self.object.origins, timeout=3600
)
cache.set(
f"script_inject_{self.object.uuid}", self.object.script_inject, timeout=3600
)
return resp
def get_context_data(self, *args, **kwargs):

View File

@@ -9,6 +9,7 @@ https://docs.djangoproject.com/en/2.2/ref/settings/
"""
import os
# import module sys to get the type of exception
import sys
import urllib.parse as urlparse
@@ -17,7 +18,7 @@ import urllib.parse as urlparse
from django.contrib.messages import constants as messages
# Increment on new releases
VERSION = "v0.5.2"
VERSION = "v0.6.0"
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -116,24 +117,26 @@ else:
}
# Solution to removal of Heroku DB Injection
if 'DATABASE_URL' in os.environ:
if 'DATABASES' not in locals():
if "DATABASE_URL" in os.environ:
if "DATABASES" not in locals():
DATABASES = {}
url = urlparse.urlparse(os.environ['DATABASE_URL'])
url = urlparse.urlparse(os.environ["DATABASE_URL"])
# Ensure default database exists.
DATABASES['default'] = DATABASES.get('default', {})
DATABASES["default"] = DATABASES.get("default", {})
# Update with environment configuration.
DATABASES['default'].update({
'NAME': url.path[1:],
'USER': url.username,
'PASSWORD': url.password,
'HOST': url.hostname,
'PORT': url.port,
})
if url.scheme == 'postgres':
DATABASES['default']['ENGINE'] = 'django.db.backends.postgresql_psycopg2'
DATABASES["default"].update(
{
"NAME": url.path[1:],
"USER": url.username,
"PASSWORD": url.password,
"HOST": url.hostname,
"PORT": url.port,
}
)
if url.scheme == "postgres":
DATABASES["default"]["ENGINE"] = "django.db.backends.postgresql_psycopg2"
# Password validation
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
@@ -202,6 +205,11 @@ USE_TZ = True
STATIC_URL = "/static/"
STATIC_ROOT = "compiledstatic/"
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
STATICFILES_FINDERS = [
"npm.finders.NpmFinder",
"django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
]
# Redis
if not DEBUG and os.getenv("REDIS_CACHE_LOCATION") is not None:
@@ -273,6 +281,21 @@ else:
EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD")
EMAIL_USE_SSL = True
# NPM
NPM_ROOT_PATH = "../"
NPM_FILE_PATTERNS = {
"a17t": ["dist/a17t.css"],
"@fortawesome/fontawesome-free": ["js/all.min.js"],
"tailwindcss": ["dist/tailwind.min.css"],
"apexcharts": ["dist/apexcharts.min.js"],
"litepicker": ["dist/js/main.js"],
"turbolinks": ["dist/turbolinks.js"],
"stimulus": ["dist/stimulus.umd.js"],
"inter-ui": ["Inter (web)/*"],
}
# Shynet
# Can everyone create services, or only superusers?

View File

@@ -21,6 +21,6 @@ urlpatterns = [
path("accounts/", include("allauth.urls")),
path("ingress/", include(("analytics.ingress_urls", "ingress")), name="ingress"),
path("dashboard/", include(("dashboard.urls", "dashboard"), namespace="dashboard")),
path("healthz/", include('health_check.urls')),
path("healthz/", include("health_check.urls")),
path("", include(("core.urls", "core"), namespace="core")),
]

View File

@@ -7,8 +7,8 @@
</head>
<body>
<noscript><img src="//localhost:8000/ingress/test_uuid/pixel.gif"></noscript>
<script src="//localhost:8000/ingress/66015ce4-c69d-40fb-be8f-5535538d795e/script.js"></script>
<noscript><img src="http://localhost:8000/ingress/9b2c4e2f-8d29-4418-82d4-b68e06795025/pixel.gif"></noscript>
<script src="http://localhost:8000/ingress/9b2c4e2f-8d29-4418-82d4-b68e06795025/script.js"></script>
</body>
</html>