Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c86192d301 | ||
|
|
775c105d1d | ||
|
|
be85c0a560 | ||
|
|
70e1af15cc | ||
|
|
6afea91c5f | ||
|
|
7a4c892804 | ||
|
|
9b50b1ea42 | ||
|
|
52a18d21f1 | ||
|
|
8aaf312c67 | ||
|
|
ede06900e5 | ||
|
|
a42455c9dc | ||
|
|
547a84f2fc | ||
|
|
f56ea99dc2 | ||
|
|
4cea5d2310 | ||
|
|
e4f09b4e68 | ||
|
|
cc094fe04e | ||
|
|
ac5c743390 | ||
|
|
963db18642 | ||
|
|
748fb76eaf | ||
|
|
d93a698e87 | ||
|
|
ca9ee2f1f5 | ||
|
|
9146c889ac |
2
Pipfile
2
Pipfile
@@ -18,7 +18,7 @@ rules = "~=2.2"
|
||||
gunicorn = "~=20.0.4"
|
||||
psycopg2-binary = "~=2.8.5"
|
||||
redis = "~=3.5.3"
|
||||
django-redis-cache = "~=2.1.1"
|
||||
django-redis-cache = "~=3.0.0"
|
||||
pycountry = "~=19.8.18"
|
||||
html2text = "~=2020.1.16"
|
||||
django-health-check = "~=3.12.1"
|
||||
|
||||
60
Pipfile.lock
generated
60
Pipfile.lock
generated
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "f9ab7bf92fe2342e3d221ab34ac957bf217638062da0554b2328502650515741"
|
||||
"sha256": "73fbc4a0251ccae805550aa48b70ec9f668bee6fa4ff9503e023b3854a06bce8"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
@@ -23,10 +23,10 @@
|
||||
},
|
||||
"asgiref": {
|
||||
"hashes": [
|
||||
"sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a",
|
||||
"sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"
|
||||
"sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17",
|
||||
"sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0"
|
||||
],
|
||||
"version": "==3.2.10"
|
||||
"version": "==3.3.1"
|
||||
},
|
||||
"billiard": {
|
||||
"hashes": [
|
||||
@@ -45,10 +45,10 @@
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3",
|
||||
"sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"
|
||||
"sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd",
|
||||
"sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4"
|
||||
],
|
||||
"version": "==2020.6.20"
|
||||
"version": "==2020.11.8"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
@@ -66,11 +66,11 @@
|
||||
},
|
||||
"django": {
|
||||
"hashes": [
|
||||
"sha256:a2127ad0150ec6966655bedf15dbbff9697cc86d61653db2da1afa506c0b04cc",
|
||||
"sha256:c93c28ccf1d094cbd00d860e83128a39e45d2c571d3b54361713aaaf9a94cac4"
|
||||
"sha256:14a4b7cd77297fba516fc0d92444cc2e2e388aa9de32d7a68d4a83d58f5a4927",
|
||||
"sha256:14b87775ffedab2ef6299b73343d1b4b41e5d4e2aa58c6581f114dbec01e3f8f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.1.2"
|
||||
"version": "==3.1.3"
|
||||
},
|
||||
"django-allauth": {
|
||||
"hashes": [
|
||||
@@ -103,19 +103,18 @@
|
||||
},
|
||||
"django-redis-cache": {
|
||||
"hashes": [
|
||||
"sha256:9b2c45a1bc0f295bccd56c2542d937665ae98f3325f20b3d82fc620e14395d52",
|
||||
"sha256:e72691539be99c0b2dd64ac380e26f4d2be5c53c1b2d26845dd279ae38b47477"
|
||||
"sha256:9a2eebef421d996a82098a19d17ff6b321265cd73178fa398913019764e8394a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.1.3"
|
||||
"version": "==3.0.0"
|
||||
},
|
||||
"emoji-country-flag": {
|
||||
"hashes": [
|
||||
"sha256:67c0cb6a3765fb53f31b34160d6b1c8a5f44b297bc278d1835c6f2e5b0a9a592",
|
||||
"sha256:ae7edb38077b0840210fa9e37673f481f2b9c032446e13ad6dab2b1108cd7ad6"
|
||||
"sha256:256e47d30fb43bf154f370cc3c9e767f003aeb8653e31ba7b87151669c608e19",
|
||||
"sha256:3f6c32699c19489383497865b208260b1d55b8424d66e08049187b13db2f0b8a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.2.1"
|
||||
"version": "==1.2.3"
|
||||
},
|
||||
"geoip2": {
|
||||
"hashes": [
|
||||
@@ -173,9 +172,11 @@
|
||||
"sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c",
|
||||
"sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67",
|
||||
"sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0",
|
||||
"sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6",
|
||||
"sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db",
|
||||
"sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94",
|
||||
"sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52",
|
||||
"sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056",
|
||||
"sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b",
|
||||
"sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd",
|
||||
"sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550",
|
||||
@@ -223,21 +224,23 @@
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
|
||||
"sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"
|
||||
"sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268",
|
||||
"sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"
|
||||
],
|
||||
"version": "==2020.1"
|
||||
"version": "==2020.4"
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
"sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
|
||||
"sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76",
|
||||
"sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
|
||||
"sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e",
|
||||
"sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648",
|
||||
"sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
|
||||
"sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f",
|
||||
"sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2",
|
||||
"sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
|
||||
"sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a",
|
||||
"sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
|
||||
"sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
|
||||
"sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
|
||||
@@ -255,10 +258,10 @@
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
|
||||
"sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
|
||||
"sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8",
|
||||
"sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"
|
||||
],
|
||||
"version": "==2.24.0"
|
||||
"version": "==2.25.0"
|
||||
},
|
||||
"requests-oauthlib": {
|
||||
"hashes": [
|
||||
@@ -274,13 +277,6 @@
|
||||
"index": "pypi",
|
||||
"version": "==2.2"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
|
||||
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
|
||||
],
|
||||
"version": "==1.15.0"
|
||||
},
|
||||
"sqlparse": {
|
||||
"hashes": [
|
||||
"sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0",
|
||||
@@ -298,10 +294,10 @@
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a",
|
||||
"sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"
|
||||
"sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08",
|
||||
"sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"
|
||||
],
|
||||
"version": "==1.25.10"
|
||||
"version": "==1.26.2"
|
||||
},
|
||||
"user-agents": {
|
||||
"hashes": [
|
||||
|
||||
@@ -70,3 +70,6 @@ SHOW_SHYNET_VERSION=True
|
||||
# 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
|
||||
|
||||
# Should Shynet show third-party icons in the dashboard?
|
||||
SHOW_THIRD_PARTY_ICONS=True
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 215 KiB After Width: | Height: | Size: 263 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 360 KiB After Width: | Height: | Size: 240 KiB |
12
package-lock.json
generated
12
package-lock.json
generated
@@ -9,14 +9,14 @@
|
||||
"integrity": "sha512-OEdH7SyC1suTdhBGW91/zBfR6qaIhThbcN8PUXtXilY4GYnSBbVqOntdHbC1vXwsDnX0Qix2m2+DSU1J51ybOQ=="
|
||||
},
|
||||
"a17t": {
|
||||
"version": "0.2.9",
|
||||
"resolved": "https://registry.npmjs.org/a17t/-/a17t-0.2.9.tgz",
|
||||
"integrity": "sha512-PdkeIVJYTfKCrYkx64c8HEPvbiVo2Prx8NWMCsiXHbsvPLbai64FwydXnNSzq/hRBQ3Toi5qU8DNCxeX1AXBCw=="
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/a17t/-/a17t-0.5.1.tgz",
|
||||
"integrity": "sha512-peIPrH9eDiu49LLzLlSTFFrXj6WLlEX3TRsUkqyyOHi/i58ilJ/eERnu7AcswXhuCBx+/2W9EUuHM+8iAq4ipg=="
|
||||
},
|
||||
"apexcharts": {
|
||||
"version": "3.22.0",
|
||||
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.22.0.tgz",
|
||||
"integrity": "sha512-DDh2eXnAEA8GoKU/hdicOaS2jzGehXwv8Bj1djYYudkeQzEdglFoWsVyIxff+Ds7+aUtVAJzd/9ythZuyyIbXQ==",
|
||||
"version": "3.23.1",
|
||||
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.23.1.tgz",
|
||||
"integrity": "sha512-7fRpquXp725BUew5OO1mJWk16/IJPCUl0l8SjhISnAhAtbTaM9PnXPSmN2BvKO4RcT457CzMM7MCG5UokiTwcA==",
|
||||
"requires": {
|
||||
"svg.draggable.js": "^2.2.2",
|
||||
"svg.easing.js": "^2.0.0",
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
"homepage": "https://github.com/milesmcc/shynet#readme",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.15.1",
|
||||
"a17t": "^0.2.9",
|
||||
"apexcharts": "^3.22.0",
|
||||
"a17t": "^0.5.1",
|
||||
"apexcharts": "^3.23.1",
|
||||
"inter-ui": "^3.15.0",
|
||||
"litepicker": "^1.5.7",
|
||||
"turbolinks": "^5.2.0"
|
||||
|
||||
@@ -20,8 +20,8 @@ class Session(models.Model):
|
||||
identifier = models.TextField(blank=True, db_index=True)
|
||||
|
||||
# Time
|
||||
start_time = models.DateTimeField(auto_now_add=True, db_index=True)
|
||||
last_seen = models.DateTimeField(auto_now_add=True)
|
||||
start_time = models.DateTimeField(default=timezone.now, db_index=True)
|
||||
last_seen = models.DateTimeField(default=timezone.now)
|
||||
|
||||
# Core request information
|
||||
user_agent = models.TextField()
|
||||
@@ -78,8 +78,8 @@ class Hit(models.Model):
|
||||
initial = models.BooleanField(default=True, db_index=True)
|
||||
|
||||
# Base request information
|
||||
start_time = models.DateTimeField(auto_now_add=True, db_index=True)
|
||||
last_seen = models.DateTimeField(auto_now_add=True)
|
||||
start_time = models.DateTimeField(default=timezone.now, db_index=True)
|
||||
last_seen = models.DateTimeField(default=timezone.now)
|
||||
heartbeats = models.IntegerField(default=0)
|
||||
tracker = models.TextField(
|
||||
choices=[("JS", "JavaScript"), ("PIXEL", "Pixel (noscript)")]
|
||||
|
||||
@@ -9,7 +9,6 @@ from celery import shared_task
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.db.models import Q
|
||||
from django.utils import timezone
|
||||
|
||||
from core.models import Service
|
||||
|
||||
@@ -123,6 +122,8 @@ def ingress_request(
|
||||
browser=ua.browser.family or "",
|
||||
device=ua.device.family or ua.device.model or "",
|
||||
device_type=device_type,
|
||||
start_time=time,
|
||||
last_seen=time,
|
||||
os=ua.os.family or "",
|
||||
asn=ip_data.get("asn") or "",
|
||||
country=ip_data.get("country") or "",
|
||||
@@ -139,7 +140,7 @@ def ingress_request(
|
||||
log.debug("Updating old session with new data...")
|
||||
|
||||
# Update last seen time
|
||||
session.last_seen = timezone.now()
|
||||
session.last_seen = time
|
||||
if session.identifier == "" and identifier.strip() != "":
|
||||
session.identifier = identifier.strip()
|
||||
session.save()
|
||||
@@ -160,7 +161,7 @@ def ingress_request(
|
||||
# this is a heartbeat.
|
||||
log.debug("Hit is a heartbeat; updating old hit with new data...")
|
||||
hit.heartbeats += 1
|
||||
hit.last_seen = timezone.now()
|
||||
hit.last_seen = time
|
||||
hit.save()
|
||||
|
||||
if hit is None:
|
||||
@@ -176,6 +177,8 @@ def ingress_request(
|
||||
location=payload.get("location", location),
|
||||
referrer=payload.get("referrer", ""),
|
||||
load_time=payload.get("loadTime"),
|
||||
start_time=time,
|
||||
last_seen=time,
|
||||
)
|
||||
# Set idempotency (if applicable)
|
||||
if idempotency is not None:
|
||||
|
||||
@@ -5,7 +5,12 @@ from urllib.parse import urlparse
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
|
||||
from django.http import (
|
||||
Http404,
|
||||
HttpResponse,
|
||||
HttpResponseBadRequest,
|
||||
HttpResponseForbidden,
|
||||
)
|
||||
from django.shortcuts import render, reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.decorators import method_decorator
|
||||
@@ -53,7 +58,10 @@ class ValidateServiceOriginsMixin:
|
||||
|
||||
if origins != "*":
|
||||
remote_origin = request.META.get("HTTP_ORIGIN")
|
||||
if remote_origin is None and request.META.get("HTTP_REFERER") is not None:
|
||||
if (
|
||||
remote_origin is None
|
||||
and request.META.get("HTTP_REFERER") is not None
|
||||
):
|
||||
parsed = urlparse(request.META.get("HTTP_REFERER"))
|
||||
remote_origin = f"{parsed.scheme}://{parsed.netloc}".lower()
|
||||
origins = [origin.strip().lower() for origin in origins.split(",")]
|
||||
|
||||
114
shynet/core/management/commands/demo.py
Normal file
114
shynet/core/management/commands/demo.py
Normal file
@@ -0,0 +1,114 @@
|
||||
import traceback
|
||||
from django.utils.timezone import now
|
||||
from django.utils.timezone import timedelta
|
||||
import random
|
||||
import uuid
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.utils.crypto import get_random_string
|
||||
import user_agents
|
||||
from logging import info
|
||||
|
||||
from core.models import User, Service
|
||||
from analytics.models import Session, Hit
|
||||
from analytics.tasks import ingress_request
|
||||
|
||||
LOCATIONS = [
|
||||
"/",
|
||||
"/post/{rand}",
|
||||
"/login",
|
||||
"/me",
|
||||
]
|
||||
|
||||
REFERRERS = [
|
||||
"https://news.ycombinator.com/item?id=11116274",
|
||||
"https://news.ycombinator.com/item?id=24872911",
|
||||
"https://reddit.com",
|
||||
"https://facebook.com",
|
||||
"https://twitter.com/milesmccain",
|
||||
"https://twitter.com",
|
||||
"https://stanford.edu/~mccain/",
|
||||
"https://tiktok.com",
|
||||
"https://io.stanford.edu",
|
||||
"https://en.wikipedia.org",
|
||||
"https://stackoverflow.com",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
]
|
||||
|
||||
USER_AGENTS = [
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/43.4",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36",
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 11_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko)",
|
||||
"Version/10.0 Mobile/14E304 Safari/602.1",
|
||||
]
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Configures a Shynet demo service"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"name", type=str,
|
||||
)
|
||||
parser.add_argument("owner_email", type=str)
|
||||
parser.add_argument(
|
||||
"avg", type=int,
|
||||
)
|
||||
parser.add_argument("deviation", type=float, default=0.4)
|
||||
parser.add_argument(
|
||||
"days", type=int,
|
||||
)
|
||||
parser.add_argument("load_time", type=float, default=1000)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
owner = User.objects.get(email=options.get("owner_email"))
|
||||
service = Service.objects.create(name=options.get("name"), owner=owner)
|
||||
|
||||
print(
|
||||
f"Created demo service `{service.name}` (uuid: `{service.uuid}`, owner: {owner})"
|
||||
)
|
||||
|
||||
# Go through each day requested, creating sessions and hits
|
||||
for days in range(options.get("days")):
|
||||
day = (now() - timedelta(days=days)).replace(hour=0, minute=0, second=0)
|
||||
print(f"Populating info for {day}...")
|
||||
avg = options.get("avg")
|
||||
deviation = options.get("deviation")
|
||||
ips = [
|
||||
".".join(map(str, (random.randint(0, 255) for _ in range(4))))
|
||||
for _ in range(avg)
|
||||
]
|
||||
|
||||
n = avg + random.randrange(-1 * deviation * avg, deviation * avg)
|
||||
for _ in range(n):
|
||||
time = day + timedelta(
|
||||
hours=random.randrange(0, 23),
|
||||
minutes=random.randrange(0, 59),
|
||||
seconds=random.randrange(0, 59),
|
||||
)
|
||||
ip = random.choice(ips)
|
||||
load_time = random.normalvariate(options.get("load_time"), 500)
|
||||
referrer = random.choice(REFERRERS)
|
||||
location = "https://example.com" + random.choice(LOCATIONS).replace(
|
||||
"{rand}", str(random.randint(0, n))
|
||||
)
|
||||
user_agent = random.choice(USER_AGENTS)
|
||||
ingress_request(
|
||||
service.uuid,
|
||||
"JS",
|
||||
time,
|
||||
{"loadTime": load_time, "referrer": referrer},
|
||||
ip,
|
||||
location,
|
||||
user_agent,
|
||||
)
|
||||
|
||||
print(f"Created {n} demo hits on {day}!")
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f"Successfully created demo data!"))
|
||||
@@ -6,18 +6,20 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0007_service_ignore_robots'),
|
||||
("core", "0007_service_ignore_robots"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='service',
|
||||
name='script_inject',
|
||||
field=models.TextField(blank=True, default=''),
|
||||
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'),
|
||||
model_name="user",
|
||||
name="first_name",
|
||||
field=models.CharField(
|
||||
blank=True, max_length=150, verbose_name="first name"
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -18,7 +18,7 @@ class ServiceForm(forms.ModelForm):
|
||||
"hide_referrer_regex",
|
||||
"origins",
|
||||
"collaborators",
|
||||
"script_inject"
|
||||
"script_inject",
|
||||
]
|
||||
widgets = {
|
||||
"name": forms.TextInput(),
|
||||
@@ -28,7 +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})
|
||||
"script_inject": forms.Textarea(attrs={"class": "font-mono", "rows": 5}),
|
||||
}
|
||||
labels = {
|
||||
"origins": "Allowed origins",
|
||||
@@ -60,7 +60,9 @@ class ServiceForm(forms.ModelForm):
|
||||
|
||||
def clean_collaborators(self):
|
||||
collaborators = []
|
||||
users_to_emails = {} # maps users to the email they are listed under as a collaborator
|
||||
users_to_emails = (
|
||||
{}
|
||||
) # maps users to the email they are listed under as a collaborator
|
||||
for collaborator_email in self.cleaned_data["collaborators"].split(","):
|
||||
email = collaborator_email.strip()
|
||||
if email == "":
|
||||
@@ -72,7 +74,9 @@ class ServiceForm(forms.ModelForm):
|
||||
raise forms.ValidationError(f"Email '{email}' is not registered")
|
||||
user = collaborator_email_linked.user
|
||||
if user in collaborators:
|
||||
raise forms.ValidationError(f"The emails '{email}' and '{users_to_emails[user]}' both correspond to the same user")
|
||||
raise forms.ValidationError(
|
||||
f"The emails '{email}' and '{users_to_emails[user]}' both correspond to the same user"
|
||||
)
|
||||
users_to_emails[user] = email
|
||||
collaborators.append(collaborator_email_linked.user)
|
||||
return collaborators
|
||||
|
||||
@@ -13,4 +13,18 @@
|
||||
|
||||
.rf {
|
||||
text-align: right !important;
|
||||
}
|
||||
|
||||
:root {
|
||||
--color-neutral-000: white;
|
||||
--color-neutral-50: #F8FAFC;
|
||||
--color-neutral-100: #F1F5F9;
|
||||
--color-neutral-200: #E2E8F0;
|
||||
--color-neutral-300: #CBD5E1;
|
||||
--color-neutral-400: #94A3B8;
|
||||
--color-neutral-500: #64748B;
|
||||
--color-neutral-600: #475569;
|
||||
--color-neutral-700: #334155;
|
||||
--color-neutral-800: #1E293B;
|
||||
--color-neutral-900: #0F172A;
|
||||
}
|
||||
BIN
shynet/dashboard/static/dashboard/images/icon.png
Normal file
BIN
shynet/dashboard/static/dashboard/images/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -8,6 +8,7 @@
|
||||
<meta name="robots" content="noindex">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{% include 'a17t/includes/head.html' %}
|
||||
<link rel="icon" type="image/png" href="{% static 'dashboard/images/icon.png' %}">
|
||||
<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>
|
||||
@@ -17,11 +18,12 @@
|
||||
{% endblock %}
|
||||
</head>
|
||||
|
||||
<body class="bg-neutral-200 min-h-full">
|
||||
<body class="bg-neutral-100 min-h-full">
|
||||
{% block body %}
|
||||
|
||||
<section class="max-w-screen-xl mx-auto px-4 py-4 md:py-12 md:flex">
|
||||
<aside class="mb-8 md:w-2/12 md:pr-6 relative flex flex-wrap md:block justify-between items-center overflow-x-hidden">
|
||||
<aside
|
||||
class="mb-8 md:w-2/12 md:pr-6 relative flex flex-wrap md:block justify-between items-center overflow-x-hidden">
|
||||
<a class="icon ~urge ml-2 md:ml-6 md:mb-8 md:mt-3" href="{% url 'dashboard:dashboard' %}">
|
||||
<i class="fas fa-binoculars fa-3x text-urge-600 hidden md:block"></i>
|
||||
<i class="fas fa-binoculars fa-2x text-urge-600 md:hidden"></i>
|
||||
@@ -41,7 +43,7 @@
|
||||
|
||||
{% for service in user.owning_services.all %}
|
||||
{% url 'dashboard:service' service.uuid as url %}
|
||||
{% include 'dashboard/includes/sidebar_portal.html' with label=service.name|truncatechars:20 url=url %}
|
||||
{% include 'dashboard/includes/sidebar_portal.html' with label=service.name|truncatechars:16 url=url icon=service.link|iconify %}
|
||||
{% endfor %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
{% with stats=object.stats %}
|
||||
<div class="p-4 md:flex justify-between">
|
||||
<div class="flex items-center mb-4 md:mb-0">
|
||||
<h3 class="heading text-xl md:text-2xl mr-2 mb-1 text-urge-600">
|
||||
{{object.name}}
|
||||
<h3 class="heading text-xl md:text-2xl mr-2 mb-1 text-urge-600 flex items-center">
|
||||
{{object.link|iconify}}
|
||||
<span>{{object.name}}</span>
|
||||
</h3>
|
||||
{% include 'dashboard/includes/stats_status_chip.html' %}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% load helpers %}
|
||||
|
||||
<div>
|
||||
<a class="portal !low {% if request.get_full_path|startswith:url %}~urge active bg-neutral-100{% endif %}"
|
||||
{% if disable_turbolinks %}data-turbolinks="false"{% endif %} href="{{url}}">{{label}}</a>
|
||||
<a class="portal !low {% if request.get_full_path|startswith:url %}~urge active bg-neutral-100{% endif %} flex items-center"
|
||||
{% if disable_turbolinks %}data-turbolinks="false"{% endif %} href="{{url}}">{{icon}} {{label}}</a>
|
||||
</div>
|
||||
@@ -17,18 +17,18 @@
|
||||
<p class="label text-gray-400">Sessions</p>
|
||||
<p class="heading">
|
||||
{{stats.session_count|intcomma}}
|
||||
<div>
|
||||
{% compare stats.compare.session_count stats.session_count "UP" classes=classes good_classes=good_classes bad_classes=bad_classes neutral_classes=neutral_classes %}
|
||||
</div>
|
||||
<div>
|
||||
{% compare stats.compare.session_count stats.session_count "UP" classes=classes good_classes=good_classes bad_classes=bad_classes neutral_classes=neutral_classes %}
|
||||
</div>
|
||||
</p>
|
||||
</article>
|
||||
<article class="">
|
||||
<p class="label text-gray-400">Hits</p>
|
||||
<p class="heading">
|
||||
{{stats.hit_count|intcomma}}
|
||||
<div>
|
||||
{% compare stats.compare.hit_count stats.hit_count "UP" classes=classes good_classes=good_classes bad_classes=bad_classes neutral_classes=neutral_classes %}
|
||||
</div>
|
||||
<div>
|
||||
{% compare stats.compare.hit_count stats.hit_count "UP" classes=classes good_classes=good_classes bad_classes=bad_classes neutral_classes=neutral_classes %}
|
||||
</div>
|
||||
</p>
|
||||
</article>
|
||||
<article class="">
|
||||
@@ -39,9 +39,9 @@
|
||||
{% else %}
|
||||
?
|
||||
{% endif %}
|
||||
<div>
|
||||
{% compare stats.compare.avg_load_time stats.avg_load_time "DOWN" classes=classes good_classes=good_classes bad_classes=bad_classes neutral_classes=neutral_classes %}
|
||||
</div>
|
||||
<div>
|
||||
{% compare stats.compare.avg_load_time stats.avg_load_time "DOWN" classes=classes good_classes=good_classes bad_classes=bad_classes neutral_classes=neutral_classes %}
|
||||
</div>
|
||||
</p>
|
||||
</article>
|
||||
<article class="">
|
||||
@@ -52,9 +52,9 @@
|
||||
{% else %}
|
||||
?
|
||||
{% endif %}
|
||||
<div>
|
||||
{% compare stats.compare.bounce_rate_pct stats.bounce_rate_pct "DOWN" classes=classes good_classes=good_classes bad_classes=bad_classes neutral_classes=neutral_classes %}
|
||||
</div>
|
||||
<div>
|
||||
{% compare stats.compare.bounce_rate_pct stats.bounce_rate_pct "DOWN" classes=classes good_classes=good_classes bad_classes=bad_classes neutral_classes=neutral_classes %}
|
||||
</div>
|
||||
</p>
|
||||
</article>
|
||||
<article class="">
|
||||
@@ -65,9 +65,9 @@
|
||||
{% else %}
|
||||
?
|
||||
{% endif %}
|
||||
<div>
|
||||
{% compare stats.compare.avg_session_duration stats.avg_session_duration "UP" classes=classes good_classes=good_classes bad_classes=bad_classes neutral_classes=neutral_classes %}
|
||||
</div>
|
||||
<div>
|
||||
{% compare stats.compare.avg_session_duration stats.avg_session_duration "UP" classes=classes good_classes=good_classes bad_classes=bad_classes neutral_classes=neutral_classes %}
|
||||
</div>
|
||||
</p>
|
||||
</article>
|
||||
<article class="">
|
||||
@@ -78,9 +78,9 @@
|
||||
{% else %}
|
||||
?
|
||||
{% endif %}
|
||||
<div>
|
||||
{% compare stats.compare.avg_hits_per_session stats.avg_hits_per_session "UP" classes=classes good_classes=good_classes bad_classes=bad_classes neutral_classes=neutral_classes %}
|
||||
</div>
|
||||
<div>
|
||||
{% compare stats.compare.avg_hits_per_session stats.avg_hits_per_session "UP" classes=classes good_classes=good_classes bad_classes=bad_classes neutral_classes=neutral_classes %}
|
||||
</div>
|
||||
</p>
|
||||
</article>
|
||||
{% endwith %}
|
||||
@@ -166,7 +166,7 @@
|
||||
<tbody>
|
||||
{% for os in stats.operating_systems %}
|
||||
<tr>
|
||||
<td>{{os.os|default:"Unknown"}}</td>
|
||||
<td class="flex items-center">{{os.os|iconify}}<span>{{os.os|default:"Unknown"}}</span></td>
|
||||
<td class="rf">{{os.count|intcomma}}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
@@ -188,7 +188,8 @@
|
||||
<tbody>
|
||||
{% for browser in stats.browsers %}
|
||||
<tr>
|
||||
<td>{{browser.browser|default:"Unknown"}}</td>
|
||||
<td class="flex items-center">
|
||||
{{browser.browser|iconify}}<span>{{browser.browser|default:"Unknown"}}</span></td>
|
||||
<td class="rf">{{browser.count|intcomma}}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
@@ -225,7 +226,8 @@
|
||||
<div class="card ~neutral !low limited-height py-2">
|
||||
{% include 'dashboard/includes/session_list.html' %}
|
||||
<hr class="sep h-8">
|
||||
<a href="{% url 'dashboard:service_session_list' service.uuid %}" class="button ~neutral w-auto mb-2">View more sessions
|
||||
<a href="{% url 'dashboard:service_session_list' service.uuid %}" class="button ~neutral w-auto mb-2">View more
|
||||
sessions
|
||||
→</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -8,6 +8,7 @@
|
||||
<div class="md:flex justify-between items-center" id="heading">
|
||||
<a class="flex items-center mb-4 md:mb-0" href="{% url 'dashboard:service' object.uuid %}">
|
||||
<h3 class="heading leading-none mr-4">
|
||||
{{object.link|iconify}}
|
||||
{{object.name}}
|
||||
</h3>
|
||||
<div class='text-3xl'>
|
||||
|
||||
@@ -73,7 +73,7 @@ def percent_change_display(start, end):
|
||||
elif start == 0:
|
||||
pct_change = "0%"
|
||||
else:
|
||||
change = int(round(100 * abs(end - start) / start))
|
||||
change = int(round(100 * abs(end - start) / max(start, 1)))
|
||||
if change > 999:
|
||||
return "> 999%"
|
||||
else:
|
||||
@@ -84,8 +84,7 @@ def percent_change_display(start, end):
|
||||
|
||||
@register.inclusion_tag("dashboard/includes/sidebar_footer.html")
|
||||
def sidebar_footer():
|
||||
return {"version": "" if settings.SHOW_SHYNET_VERSION
|
||||
else settings.VERSION}
|
||||
return {"version": settings.VERSION if settings.SHOW_SHYNET_VERSION else ""}
|
||||
|
||||
|
||||
@register.inclusion_tag("dashboard/includes/stat_comparison.html")
|
||||
@@ -116,12 +115,60 @@ def startswith(text, starts):
|
||||
return False
|
||||
|
||||
|
||||
@register.filter
|
||||
def iconify(text):
|
||||
if not settings.SHOW_THIRD_PARTY_ICONS:
|
||||
return ""
|
||||
|
||||
text = text.lower()
|
||||
icons = {
|
||||
"chrome": "chrome.com",
|
||||
"safari": "www.apple.com",
|
||||
"windows": "windows.com",
|
||||
"edge": "microsoft.com",
|
||||
"firefox": "firefox.com",
|
||||
"opera": "opera.com",
|
||||
"unknown": "example.com",
|
||||
"linux": "kernel.org",
|
||||
"ios": "www.apple.com",
|
||||
"mac": "www.apple.com",
|
||||
"macos": "www.apple.com",
|
||||
"mac os x": "www.apple.com",
|
||||
"android": "android.com",
|
||||
"chrome os": "chrome.com",
|
||||
"ubuntu": "ubuntu.com",
|
||||
"fedora": "getfedora.org",
|
||||
"mobile safari": "www.apple.com",
|
||||
"chrome mobile ios": "chrome.com",
|
||||
"chrome mobile": "chrome.com",
|
||||
"samsung internet": "samsung.com",
|
||||
"google": "google.com",
|
||||
"chrome mobile webview": "chrome.com",
|
||||
"firefox mobile": "firefox.com",
|
||||
"edge mobile": "microsoft.com",
|
||||
"chromium": "chromium.org",
|
||||
}
|
||||
|
||||
domain = None
|
||||
if text.startswith("http"):
|
||||
domain = urlparse(text).netloc
|
||||
elif text in icons:
|
||||
domain = icons[text]
|
||||
else:
|
||||
# This fallback works better than you'd think!
|
||||
domain = text + ".com"
|
||||
|
||||
return SafeString(
|
||||
f'<span class="icon mr-1"><img src="https://icons.duckduckgo.com/ip3/{domain}.ico"></span>'
|
||||
)
|
||||
|
||||
|
||||
@register.filter
|
||||
def urldisplay(url):
|
||||
if url.startswith("http"):
|
||||
display_url = url.replace("http://", "").replace("https://", "")
|
||||
return SafeString(
|
||||
f"<a href='{url}' title='{url}' rel='nofollow'>{escape(display_url if len(display_url) < 40 else display_url[:40] + '...')}</a>"
|
||||
f"<a href='{url}' title='{url}' rel='nofollow' class='flex items-center'>{iconify(url)} {escape(display_url if len(display_url) < 40 else display_url[:40] + '...')}</a>"
|
||||
)
|
||||
else:
|
||||
return url
|
||||
|
||||
@@ -18,7 +18,7 @@ import urllib.parse as urlparse
|
||||
from django.contrib.messages import constants as messages
|
||||
|
||||
# Increment on new releases
|
||||
VERSION = "v0.6.7"
|
||||
VERSION = "v0.7.2"
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
@@ -318,3 +318,6 @@ SESSION_MEMORY_TIMEOUT = int(os.getenv("SESSION_MEMORY_TIMEOUT", "1800"))
|
||||
|
||||
# Should the Shynet version information be displayed?
|
||||
SHOW_SHYNET_VERSION = os.getenv("SHOW_SHYNET_VERSION", "True") == "True"
|
||||
|
||||
# Should Shynet show third-party icons in the dashboard?
|
||||
SHOW_THIRD_PARTY_ICONS = os.getenv("SHOW_THIRD_PARTY_ICONS", "True") == "True"
|
||||
|
||||
Reference in New Issue
Block a user