Compare commits

..

34 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
R. Miles McCain
4ced1365d4 Bump version 2020-06-15 20:14:56 +00:00
Ruben van Erk
dcdbb7cd45 Add option to ignore robots 2020-06-15 19:07:13 +02:00
R. Miles McCain
b3102f5f32 Remove erroneous app.json default 2020-06-14 02:05:11 +00:00
R. Miles McCain
2a61cf1b51 Update GUIDE structure 2020-06-14 01:51:44 +00:00
R. Miles McCain
0d7c9c4c33 Bump version 2020-06-14 01:49:24 +00:00
R. Miles McCain
6649aeaaf0 Update dependencies 2020-06-14 01:49:18 +00:00
R. Miles McCain
cb11dc0c4e Add mention of Heroku to docs 2020-06-14 00:12:31 +00:00
R. Miles McCain
4a4f2645df Add Heroku setup instructions 2020-06-14 00:05:39 +00:00
R. Miles McCain
81a836df53 Improve startup behavior 2020-06-13 23:59:36 +00:00
Thomas Letsch Groch
919ca52ca1 👌 Refine code review changes 2020-06-06 06:02:33 -03:00
Thomas Letsch Groch
f7ecb88659 👌 Code review changes
commented out mysql logic and refine GUIDE.md
2020-06-02 20:34:02 -03:00
Thomas Letsch Groch
1a0dcf7579 📝 Improving heroku form 2020-06-01 19:31:19 -03:00
Thomas Letsch Groch
0f3037b315 📝 update template on heroku button 2020-06-01 18:47:30 -03:00
Thomas Letsch Groch
b234ef2917 Add heroku compatibility
Recognize heroku with the environment variable DATABASE_URL, parse it and replace the others based on it.
2020-06-01 18:41:39 -03:00
36 changed files with 3512 additions and 125 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[cod]
*$py.class *$py.class
# JavaScript packages
node_modules/
# C extensions # C extensions
*.so *.so

View File

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

View File

@@ -3,6 +3,7 @@
## Table of Contents ## Table of Contents
- [Installation](#installation) - [Installation](#installation)
- [Heroku](#heroku)
- [Updating Your Configuration](#updating-your-configuration) - [Updating Your Configuration](#updating-your-configuration)
- [Enhancements](#enhancements) - [Enhancements](#enhancements)
* [Installation with SSL](#installation-with-ssl) * [Installation with SSL](#installation-with-ssl)
@@ -10,7 +11,6 @@
+ [Cloudflare](#cloudflare) + [Cloudflare](#cloudflare)
+ [Nginx](#nginx) + [Nginx](#nginx)
+ [Troubleshooting](#troubleshooting) + [Troubleshooting](#troubleshooting)
--- ---
## Staying Updated ## Staying Updated
@@ -46,6 +46,18 @@ Before continuing, please be sure to have the latest version of Docker installed
10. Finally, click on "Manage" in the top right of the service's page to get the tracking script code. Inject this script on all pages you'd like the service to track. 10. Finally, click on "Manage" in the top right of the service's page to get the tracking script code. Inject this script on all pages you'd like the service to track.
## Heroku
You may wish to deploy Shynet on Heroku. Note that Heroku's free offerings (namely the free Postgres addon) are unlikely to support running any Shynet instance that records more than a few hundred requests per day &mdash; the database will quickly fill up. In most cases, the more cost-effective option for running Shynet is renting a VPS from a full cloud service provider. However, if you're sure Heroku is the right option for you, or you just want to try Shynet out, you can use the Quick Deploy button then follow the steps below.
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/milesmcc/shynet/tree/master)
Once you deploy, you'll need to setup an admin user, whitelabel, and hostname before you can use Shynet. Do that with the following commands:
1. `heroku run --app=<your app> ./manage.py registeradmin <your email>`
2. `heroku run --app=<your app> ./manage.py hostname <the hostname where you will run Shynet>`
3. `heroku run --app=<your app> ./manage.py whitelabel "<your Shynet instance's name>"`
--- ---
## Enhancements ## Enhancements

49
Pipfile
View File

@@ -3,29 +3,30 @@ name = "pypi"
url = "https://pypi.org/simple" url = "https://pypi.org/simple"
verify_ssl = true 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] [pipenv]
allow_prereleases = true 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 = "*"

113
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "327b897f359bad486c08fc88fb70a1f9d2edaf1aadafcb1d31e5b3e144125ff7" "sha256": "ff989ac3413a6bd2253c9350c8f368a91942393221f1e47e5e39e60e457cc590"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": {}, "requires": {},
@@ -16,17 +16,17 @@
"default": { "default": {
"amqp": { "amqp": {
"hashes": [ "hashes": [
"sha256:6e649ca13a7df3faacdc8bbb280aa9a6602d22fd9d545336077e573a1f4ff3b8", "sha256:24dbaff8ce4f30566bb88976b398e8c4e77637171af3af6f1b9650f48890e60b",
"sha256:77f1aef9410698d20eaeac5b73a87817365f457a507d82edf292e12cbb83b08d" "sha256:bb68f8d2bced8f93ccfd07d96c689b716b3227720add971be980accfc2952139"
], ],
"version": "==2.5.2" "version": "==2.6.0"
}, },
"asgiref": { "asgiref": {
"hashes": [ "hashes": [
"sha256:8036f90603c54e93521e5777b2b9a39ba1bad05773fcf2d208f0299d1df58ce5", "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a",
"sha256:9ca8b952a0a9afa61d30aa6d3d9b570bb3fd6bafcf7ec9e6bed43b936133db1c" "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"
], ],
"version": "==3.2.7" "version": "==3.2.10"
}, },
"billiard": { "billiard": {
"hashes": [ "hashes": [
@@ -37,18 +37,18 @@
}, },
"celery": { "celery": {
"hashes": [ "hashes": [
"sha256:108a0bf9018a871620936c33a3ee9f6336a89f8ef0a0f567a9001f4aa361415f", "sha256:ef17d7dffde7fc73ecab3a3b6389d93d3213bac53fa7f28e68e33647ad50b916",
"sha256:5b4b37e276033fe47575107a2775469f0b721646a08c96ec2c61531e4fe45f2a" "sha256:fd77e4248bb1b7af5f7922dd8e81156f540306e3a5c4b1c24167c1f5f06025da"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.4.2" "version": "==4.4.6"
}, },
"certifi": { "certifi": {
"hashes": [ "hashes": [
"sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304", "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3",
"sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519" "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"
], ],
"version": "==2020.4.5.1" "version": "==2020.6.20"
}, },
"chardet": { "chardet": {
"hashes": [ "hashes": [
@@ -66,11 +66,11 @@
}, },
"django": { "django": {
"hashes": [ "hashes": [
"sha256:db4c9b29615d17f808f2b1914d5cd73cd457c9fd90581195172c0888c210d944", "sha256:045be31d68dfed684831e39ab1d9e77a595f1a393935cb43b6c5451d2e78c8a4",
"sha256:dd96f98ec1c3e60877d45cea7350215f16de409848d23cced8443db1b188bd9b" "sha256:ccf6c208424c0e1b0eaffd36efe12618a9ab4d0037e26f6ffceaa5277af985d7"
], ],
"index": "pypi", "index": "pypi",
"version": "==3.1a1" "version": "==3.1b1"
}, },
"django-allauth": { "django-allauth": {
"hashes": [ "hashes": [
@@ -94,6 +94,13 @@
"index": "pypi", "index": "pypi",
"version": "==2.1.0" "version": "==2.1.0"
}, },
"django-npm": {
"hashes": [
"sha256:2e6bba65e728fa18b9db3c8dc0d4490b70cb7f43bacf60eb3654d7dcb6424272"
],
"index": "pypi",
"version": "==1.0.0"
},
"django-redis-cache": { "django-redis-cache": {
"hashes": [ "hashes": [
"sha256:06d4e48545243883f88dc9263dda6c8a0012cb7d0cee2d8758d8917eca92cece", "sha256:06d4e48545243883f88dc9263dda6c8a0012cb7d0cee2d8758d8917eca92cece",
@@ -110,6 +117,12 @@
"index": "pypi", "index": "pypi",
"version": "==1.2.1" "version": "==1.2.1"
}, },
"future": {
"hashes": [
"sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"
],
"version": "==0.18.2"
},
"geoip2": { "geoip2": {
"hashes": [ "hashes": [
"sha256:5869e987bc54c0d707264fec4710661332cc38d2dca5a7f9bb5362d0308e2ce0", "sha256:5869e987bc54c0d707264fec4710661332cc38d2dca5a7f9bb5362d0308e2ce0",
@@ -136,10 +149,10 @@
}, },
"idna": { "idna": {
"hashes": [ "hashes": [
"sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
"sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
], ],
"version": "==2.9" "version": "==2.10"
}, },
"ipaddress": { "ipaddress": {
"hashes": [ "hashes": [
@@ -151,10 +164,10 @@
}, },
"kombu": { "kombu": {
"hashes": [ "hashes": [
"sha256:2d1cda774126a044d91a7ff5fa6d09edf99f46924ab332a810760fe6740e9b76", "sha256:be48cdffb54a2194d93ad6533d73f69408486483d189fe9f5990ee24255b0e0a",
"sha256:598e7e749d6ab54f646b74b2d2df67755dee13894f73ab02a2a9feb8870c7cb2" "sha256:ca1b45faac8c0b18493d02a8571792f3c40291cf2bcf1f55afed3d8f3aa7ba74"
], ],
"version": "==4.6.8" "version": "==4.6.11"
}, },
"maxminddb": { "maxminddb": {
"hashes": [ "hashes": [
@@ -245,18 +258,18 @@
}, },
"redis": { "redis": {
"hashes": [ "hashes": [
"sha256:2ef11f489003f151777c064c5dbc6653dfb9f3eade159bcadc524619fddc2242", "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2",
"sha256:6d65e84bc58091140081ee9d9c187aab0480097750fac44239307a3bdf0b1251" "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"
], ],
"index": "pypi", "index": "pypi",
"version": "==3.5.2" "version": "==3.5.3"
}, },
"requests": { "requests": {
"hashes": [ "hashes": [
"sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
"sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
], ],
"version": "==2.23.0" "version": "==2.24.0"
}, },
"requests-oauthlib": { "requests-oauthlib": {
"hashes": [ "hashes": [
@@ -364,29 +377,29 @@
}, },
"regex": { "regex": {
"hashes": [ "hashes": [
"sha256:1386e75c9d1574f6aa2e4eb5355374c8e55f9aac97e224a8a5a6abded0f9c927", "sha256:08997a37b221a3e27d68ffb601e45abfb0093d39ee770e4257bd2f5115e8cb0a",
"sha256:27ff7325b297fb6e5ebb70d10437592433601c423f5acf86e5bc1ee2919b9561", "sha256:112e34adf95e45158c597feea65d06a8124898bdeac975c9087fe71b572bd938",
"sha256:329ba35d711e3428db6b45a53b1b13a0a8ba07cbbcf10bbed291a7da45f106c3", "sha256:1700419d8a18c26ff396b3b06ace315b5f2a6e780dad387e4c48717a12a22c29",
"sha256:3a9394197664e35566242686d84dfd264c07b20f93514e2e09d3c2b3ffdf78fe", "sha256:2f6f211633ee8d3f7706953e9d3edc7ce63a1d6aad0be5dcee1ece127eea13ae",
"sha256:51f17abbe973c7673a61863516bdc9c0ef467407a940f39501e786a07406699c", "sha256:52e1b4bef02f4040b2fd547357a170fc1146e60ab310cdbdd098db86e929b387",
"sha256:579ea215c81d18da550b62ff97ee187b99f1b135fd894a13451e00986a080cad", "sha256:55b4c25cbb3b29f8d5e63aeed27b49fa0f8476b0d4e1b3171d85db891938cc3a",
"sha256:70c14743320a68c5dac7fc5a0f685be63bc2024b062fe2aaccc4acc3d01b14a1", "sha256:5aaa5928b039ae440d775acea11d01e42ff26e1561c0ffcd3d805750973c6baf",
"sha256:7e61be8a2900897803c293247ef87366d5df86bf701083b6c43119c7c6c99108", "sha256:654cb773b2792e50151f0e22be0f2b6e1c3a04c5328ff1d9d59c0398d37ef610",
"sha256:8044d1c085d49673aadb3d7dc20ef5cb5b030c7a4fa253a593dda2eab3059929", "sha256:690f858d9a94d903cf5cada62ce069b5d93b313d7d05456dbcd99420856562d9",
"sha256:89d76ce33d3266173f5be80bd4efcbd5196cafc34100fdab814f9b228dee0fa4", "sha256:6ad8663c17db4c5ef438141f99e291c4d4edfeaacc0ce28b5bba2b0bf273d9b5",
"sha256:99568f00f7bf820c620f01721485cad230f3fb28f57d8fbf4a7967ec2e446994", "sha256:89cda1a5d3e33ec9e231ece7307afc101b5217523d55ef4dc7fb2abd6de71ba3",
"sha256:a7c37f048ec3920783abab99f8f4036561a174f1314302ccfa4e9ad31cb00eb4", "sha256:92d8a043a4241a710c1cf7593f5577fbb832cf6c3a00ff3fc1ff2052aff5dd89",
"sha256:c2062c7d470751b648f1cacc3f54460aebfc261285f14bc6da49c6943bd48bdd", "sha256:95fa7726d073c87141f7bbfb04c284901f8328e2d430eeb71b8ffdd5742a5ded",
"sha256:c9bce6e006fbe771a02bda468ec40ffccbf954803b470a0345ad39c603402577", "sha256:97712e0d0af05febd8ab63d2ef0ab2d0cd9deddf4476f7aa153f76feef4b2754",
"sha256:ce367d21f33e23a84fb83a641b3834dd7dd8e9318ad8ff677fbfae5915a239f7", "sha256:b2ba0f78b3ef375114856cbdaa30559914d081c416b431f2437f83ce4f8b7f2f",
"sha256:ce450ffbfec93821ab1fea94779a8440e10cf63819be6e176eb1973a6017aff5", "sha256:bae83f2a56ab30d5353b47f9b2a33e4aac4de9401fb582b55c42b132a8ac3868",
"sha256:ce5cc53aa9fbbf6712e92c7cf268274eaff30f6bd12a0754e8133d85a8fb0f5f", "sha256:c78e66a922de1c95a208e4ec02e2e5cf0bb83a36ceececc10a72841e53fbf2bd",
"sha256:d466967ac8e45244b9dfe302bbe5e3337f8dc4dec8d7d10f5e950d83b140d33a", "sha256:cf59bbf282b627130f5ba68b7fa3abdb96372b24b66bdf72a4920e8153fc7910",
"sha256:d881c2e657c51d89f02ae4c21d9adbef76b8325fe4d5cf0e9ad62f850f3a98fd", "sha256:e3cdc9423808f7e1bb9c2e0bdb1c9dc37b0607b30d646ff6faf0d4e41ee8fee3",
"sha256:e565569fc28e3ba3e475ec344d87ed3cd8ba2d575335359749298a0899fe122e", "sha256:e9b64e609d37438f7d6e68c2546d2cb8062f3adb27e6336bc129b51be20773ac",
"sha256:ea55b80eb0d1c3f1d8d784264a6764f931e172480a2f1868f2536444c5f01e01" "sha256:fbff901c54c22425a5b809b914a3bfaf4b9570eee0e5ce8186ac71eb2025191c"
], ],
"version": "==2020.5.14" "version": "==2020.6.8"
}, },
"toml": { "toml": {
"hashes": [ "hashes": [

View File

@@ -93,8 +93,7 @@ Shynet is pretty simple, but there are a few key terms you need to know in order
## Installation ## Installation
You can find intructions on getting started and usage in the [Usage Guide](GUIDE.md#installation). Out of the box, we support deploying via a simple You can find intructions on getting started and usage in the [Usage Guide](GUIDE.md#installation). Out of the box, we support deploying via a simple Docker container, docker-compose, Heroku, or Kubernetes (see [kubernetes](/kubernetes)).
Docker container, docker-compose, or Kubernetes (see [kubernetes](/kubernetes)).
## FAQ ## FAQ

View File

@@ -13,6 +13,7 @@ DB_PORT=5432
EMAIL_HOST_USER=example EMAIL_HOST_USER=example
EMAIL_HOST_PASSWORD=example_password EMAIL_HOST_PASSWORD=example_password
EMAIL_HOST=smtp.example.com EMAIL_HOST=smtp.example.com
EMAIL_PORT=465
SERVER_EMAIL=<Shynet> noreply@shynet.example.com SERVER_EMAIL=<Shynet> noreply@shynet.example.com
# General Django settings # General Django settings
@@ -22,7 +23,7 @@ DJANGO_SECRET_KEY=random_string
ALLOWED_HOSTS=* ALLOWED_HOSTS=*
# Set to True (capitalized) if you want people to be able to sign up for your Shynet instance (not recommended) # 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 # 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. # public sign-ups; otherwise, it's unnecessary.
@@ -58,8 +59,8 @@ PORT=8080
# Don't uncomment these unless you know what you are doing! # Don't uncomment these unless you know what you are doing!
# NUM_WORKERS=1 # NUM_WORKERS=1
# Make sure you set a REDIS_CACHE_LOCATION if you have more than one frontend worker/instance. # 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 # 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`. # that you have a separate queue consumer running somewhere via `celeryworker.sh`.
# CELERY_TASK_ALWAYS_EAGER=False # 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

122
app.json Normal file
View File

@@ -0,0 +1,122 @@
{
"name": "Shynet",
"description":"Modern, privacy-friendly, and detailed web analytics that works without cookies or JS.",
"keywords":[
"app.json",
"shynet",
"heroku",
"analytics",
"privacy",
"friendly"
],
"website": "https://github.com/milesmcc/shynet",
"repository": "https://github.com/milesmcc/shynet",
"logo": "https://github.com/milesmcc/shynet/raw/master/images/slogo.png",
"success_url": "/",
"stack": "container",
"addons": [
"heroku-postgresql:hobby-dev"
],
"formation": {
"web": {
"quantity": 1,
"size": "free"
}
},
"env": {
"DB_NAME": {
"description": "Postgres database name (not required if using Postgres addon)",
"value": "shynet",
"required": false
},
"DB_USER": {
"description": "Postgres database username (not required if using Postgres addon)",
"value": "",
"required": false
},
"DB_PASSWORD": {
"description": "Postgres database password (not required if using Postgres addon)",
"value": "",
"required": false
},
"DB_HOST": {
"description": "Postgres database hostname (not required if using Postgres addon)",
"value": "",
"required": false
},
"DB_PORT": {
"description": "Postgres database port (not required if using Postgres addon)",
"value": "5432",
"required": false
},
"EMAIL_HOST": {
"description": "SMTP server hostname (for sending emails)",
"value": "smtp.gmail.com",
"required": false
},
"EMAIL_PORT": {
"description": "SMTP server port (for sending emails)",
"value": "465",
"required": false
},
"EMAIL_HOST_USER": {
"description": "SMTP server username (for sending emails)",
"value": "",
"required": false
},
"EMAIL_HOST_PASSWORD": {
"description": "SMTP server password (for sending emails)",
"value": "",
"required": false
},
"SERVER_EMAIL": {
"description": "Email address (for sending emails)",
"value": "<Shynet> noreply@shynet.example.com",
"required": false
},
"DJANGO_SECRET_KEY": {
"description": "Django secret key",
"generator": "secret"
},
"ALLOWED_HOSTS": {
"description": "For better security, set this to your deployment's domain. (Where you will actually host, not embed, Shynet.) Set to '*' to allow serving all domains.",
"value": "*",
"required": false
},
"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
},
"TIME_ZONE": {
"description": "The timezone of the admin panel. Affects how dates are displayed.",
"value": "America/New_York",
"required": false
},
"SCRIPT_USE_HTTPS": {
"description": "Set to 'False' if you will not be serving Shynet over HTTPS.",
"value": "True",
"required": false
},
"SCRIPT_HEARTBEAT_FREQUENCY": {
"description": "How frequently should the monitoring script 'phone home' (in ms)?",
"value": "5000",
"required": false
},
"SESSION_MEMORY_TIMEOUT": {
"description": "How much time can elapse between requests from the same user before a new session is created, in seconds?",
"value": "1800",
"required": false
},
"ONLY_SUPERUSERS_CREATE": {
"description": "Should only superusers (admins) be able to create tracked services?",
"value": "True",
"required": false
},
"PERFORM_CHECKS_AND_SETUP": {
"description": "Whether to perform checks and setup at startup. Recommended value is 'True' for Heroku users.",
"value": "True",
"required": false
}
}
}

3
heroku.yml Normal file
View File

@@ -0,0 +1,3 @@
build:
docker:
web: Dockerfile

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

BIN
images/slogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 931 B

View File

@@ -8,7 +8,7 @@ stringData:
DEBUG: "False" DEBUG: "False"
ALLOWED_HOSTS: "*" # For better security, set this to your deployment's domain. Comma separated. ALLOWED_HOSTS: "*" # For better security, set this to your deployment's domain. Comma separated.
DJANGO_SECRET_KEY: "" DJANGO_SECRET_KEY: ""
SIGNUPS_ENABLED: "False" ACCOUNT_SIGNUPS_ENABLED: "False"
TIME_ZONE: "America/New_York" TIME_ZONE: "America/New_York"
# Redis configuration (if you use the default Kubernetes config, this will work) # 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"> {% load static %}
<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 rel="stylesheet" href="{% static 'a17t/dist/a17t.css' %}">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet"> <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> <style>
:root { :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-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): def add_class(field, css_class):
if len(field.errors) > 0: if len(field.errors) > 0:
css_class += " ~critical" 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)}) return field.as_widget(attrs={"class": field.css_classes(extra_classes=css_class)})

View File

@@ -1,7 +1,7 @@
import ipaddress import ipaddress
import json import json
import logging import logging
from hashlib import sha1 from hashlib import sha256
import geoip2.database import geoip2.database
import user_agents import user_agents
@@ -73,7 +73,7 @@ def ingress_request(
if payload.get("loadTime", 1) <= 0: if payload.get("loadTime", 1) <= 0:
payload["loadTime"] = None 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(ip).encode("utf-8"))
association_id_hash.update(str(user_agent).encode("utf-8")) association_id_hash.update(str(user_agent).encode("utf-8"))
session_cache_path = ( session_cache_path = (
@@ -110,6 +110,8 @@ def ingress_request(
device_type = "TABLET" device_type = "TABLET"
elif ua.is_pc: elif ua.is_pc:
device_type = "DESKTOP" device_type = "DESKTOP"
if device_type == "ROBOT" and service.ignore_robots:
return
session = Session.objects.create( session = Session.objects.create(
service=service, service=service,
ip=ip if service.collect_ips else None, ip=ip if service.collect_ips else None,
@@ -119,11 +121,11 @@ def ingress_request(
device=ua.device.family or ua.device.model or "", device=ua.device.family or ua.device.model or "",
device_type=device_type, device_type=device_type,
os=ua.os.family or "", os=ua.os.family or "",
asn=ip_data.get("asn", ""), asn=ip_data.get("asn") or "",
country=ip_data.get("country", ""), country=ip_data.get("country") or "",
longitude=ip_data.get("longitude"), longitude=ip_data.get("longitude"),
latitude=ip_data.get("latitude"), latitude=ip_data.get("latitude"),
time_zone=ip_data.get("time_zone", ""), time_zone=ip_data.get("time_zone") or "",
) )
cache.set( cache.set(
session_cache_path, session.pk, timeout=settings.SESSION_MEMORY_TIMEOUT session_cache_path, session.pk, timeout=settings.SESSION_MEMORY_TIMEOUT

View File

@@ -1,8 +1,14 @@
window.onload = function () { // This is a lightweight and privacy-friendly analytics script from Shynet, a self-hosted
var idempotency = // analytics tool. To give you full visibility into how your data is being monitored, this
Math.random().toString(36).substring(2, 15) + // file is intentionally not minified or obfuscated. To learn more about Shynet (and to view
Math.random().toString(36).substring(2, 15); // its source code), visit <https://github.com/milesmcc/shynet>.
function sendUpdate() { //
// 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 { try {
if (document.hidden) { if (document.hidden) {
return; return;
@@ -16,7 +22,7 @@ window.onload = function () {
xhr.setRequestHeader("Content-Type", "application/json"); xhr.setRequestHeader("Content-Type", "application/json");
xhr.send( xhr.send(
JSON.stringify({ JSON.stringify({
idempotency: idempotency, idempotency: Shynet.idempotency,
referrer: document.referrer, referrer: document.referrer,
location: window.location.href, location: window.location.href,
loadTime: loadTime:
@@ -24,8 +30,25 @@ window.onload = function () {
window.performance.timing.navigationStart, 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 base64
import json import json
from urllib.parse import urlparse
from django.conf import settings from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
@@ -49,7 +50,15 @@ class ValidateServiceOriginsMixin:
cache.set(f"service_origins_{service_uuid}", origins, timeout=3600) cache.set(f"service_origins_{service_uuid}", origins, timeout=3600)
resp = super().dispatch(request, *args, **kwargs) 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-Methods"] = "GET,HEAD,OPTIONS,POST"
resp[ resp[
"Access-Control-Allow-Headers" "Access-Control-Allow-Headers"
@@ -105,11 +114,14 @@ class ScriptView(ValidateServiceOriginsMixin, View):
return render( return render(
self.request, self.request,
"analytics/scripts/page.js", "analytics/scripts/page.js",
context={ context=dict(
"endpoint": endpoint, {
"protocol": protocol, "endpoint": endpoint,
"heartbeat_frequency": heartbeat_frequency, "protocol": protocol,
}, "heartbeat_frequency": heartbeat_frequency,
"script_inject": self.get_script_inject(),
}
),
content_type="application/javascript", content_type="application/javascript",
) )
@@ -125,3 +137,12 @@ class ScriptView(ValidateServiceOriginsMixin, View):
return HttpResponse( return HttpResponse(
json.dumps({"status": "OK"}), content_type="application/json" 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

@@ -0,0 +1,18 @@
# Generated by Django 3.0.6 on 2020-06-15 16:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0006_service_hide_referrer_regex"),
]
operations = [
migrations.AddField(
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

@@ -65,6 +65,7 @@ class Service(models.Model):
max_length=2, choices=SERVICE_STATUSES, default=ACTIVE, db_index=True max_length=2, choices=SERVICE_STATUSES, default=ACTIVE, db_index=True
) )
respect_dnt = models.BooleanField(default=True) respect_dnt = models.BooleanField(default=True)
ignore_robots = models.BooleanField(default=False)
collect_ips = models.BooleanField(default=True) collect_ips = models.BooleanField(default=True)
ignored_ips = models.TextField( ignored_ips = models.TextField(
default="", blank=True, validators=[_validate_network_list] default="", blank=True, validators=[_validate_network_list]
@@ -72,6 +73,7 @@ class Service(models.Model):
hide_referrer_regex = models.TextField( hide_referrer_regex = models.TextField(
default="", blank=True, validators=[_validate_regex] default="", blank=True, validators=[_validate_regex]
) )
script_inject = models.TextField(default="", blank=True)
class Meta: class Meta:
ordering = ["name", "uuid"] ordering = ["name", "uuid"]
@@ -233,7 +235,9 @@ class Service(models.Model):
"session_chart_data": json.dumps( "session_chart_data": json.dumps(
[ [
{"x": str(key), "y": value} {"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, "online": True,

View File

@@ -14,9 +14,11 @@ class ServiceForm(forms.ModelForm):
"respect_dnt", "respect_dnt",
"collect_ips", "collect_ips",
"ignored_ips", "ignored_ips",
"ignore_robots",
"hide_referrer_regex", "hide_referrer_regex",
"origins", "origins",
"collaborators", "collaborators",
"script_inject"
] ]
widgets = { widgets = {
"name": forms.TextInput(), "name": forms.TextInput(),
@@ -24,14 +26,18 @@ class ServiceForm(forms.ModelForm):
"ignored_ips": forms.TextInput(), "ignored_ips": forms.TextInput(),
"respect_dnt": forms.RadioSelect(choices=[(True, "Yes"), (False, "No")]), "respect_dnt": forms.RadioSelect(choices=[(True, "Yes"), (False, "No")]),
"collect_ips": forms.RadioSelect(choices=[(True, "Yes"), (False, "No")]), "collect_ips": forms.RadioSelect(choices=[(True, "Yes"), (False, "No")]),
"ignore_robots": forms.RadioSelect(choices=[(True, "Yes"), (False, "No")]),
"hide_referrer_regex": forms.TextInput(), "hide_referrer_regex": forms.TextInput(),
"script_inject": forms.Textarea(attrs={'class':'font-mono', 'rows': 5})
} }
labels = { labels = {
"origins": "Allowed Hostnames", "origins": "Allowed Hostnames",
"respect_dnt": "Respect DNT", "respect_dnt": "Respect DNT",
"collect_ips": "Collect IP addresses", "collect_ips": "Collect IP addresses",
"ignored_ips": "Ignored IP addresses", "ignored_ips": "Ignored IP addresses",
"ignore_robots": "Ignore robots",
"hide_referrer_regex": "Hide specific referrers", "hide_referrer_regex": "Hide specific referrers",
"script_inject": "Additional injected JS",
} }
help_texts = { help_texts = {
"name": _("What should the service be called?"), "name": _("What should the service be called?"),
@@ -42,11 +48,13 @@ 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?", "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?",
"collect_ips": "Should individual IP addresses be collected? IP metadata (location, host, etc) will still be collected.", "collect_ips": "Should individual IP addresses be collected? IP metadata (location, host, etc) will still be collected.",
"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').", "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.", "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( 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, required=False,
) )

View File

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

View File

@@ -10,6 +10,8 @@
{{form.respect_dnt|a17t}} {{form.respect_dnt|a17t}}
{{form.collect_ips|a17t}} {{form.collect_ips|a17t}}
{{form.ignored_ips|a17t}} {{form.ignored_ips|a17t}}
{{form.ignore_robots|a17t}}
{{form.hide_referrer_regex|a17t}} {{form.hide_referrer_regex|a17t}}
{{form.origins|a17t}} {{form.origins|a17t}}
{{form.script_inject|a17t}}
</details> </details>

View File

@@ -2,5 +2,5 @@
<div> <div>
<a class="portal !low {% if request.get_full_path|startswith:url %}~urge active bg-gray-100{% endif %}" <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> </div>

View File

@@ -84,6 +84,9 @@ class ServiceUpdateView(
cache.set( cache.set(
f"service_origins_{self.object.uuid}", self.object.origins, timeout=3600 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 return resp
def get_context_data(self, *args, **kwargs): def get_context_data(self, *args, **kwargs):

View File

@@ -10,11 +10,15 @@ https://docs.djangoproject.com/en/2.2/ref/settings/
import os import os
# import module sys to get the type of exception
import sys
import urllib.parse as urlparse
# Messages # Messages
from django.contrib.messages import constants as messages from django.contrib.messages import constants as messages
# Increment on new releases # Increment on new releases
VERSION = "v0.5.0" VERSION = "v0.6.0"
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -112,6 +116,27 @@ else:
} }
} }
# Solution to removal of Heroku DB Injection
if "DATABASE_URL" in os.environ:
if "DATABASES" not in locals():
DATABASES = {}
url = urlparse.urlparse(os.environ["DATABASE_URL"])
# Ensure default database exists.
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"
# Password validation # Password validation
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
@@ -180,6 +205,11 @@ USE_TZ = True
STATIC_URL = "/static/" STATIC_URL = "/static/"
STATIC_ROOT = "compiledstatic/" STATIC_ROOT = "compiledstatic/"
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
STATICFILES_FINDERS = [
"npm.finders.NpmFinder",
"django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
]
# Redis # Redis
if not DEBUG and os.getenv("REDIS_CACHE_LOCATION") is not None: if not DEBUG and os.getenv("REDIS_CACHE_LOCATION") is not None:
@@ -251,6 +281,21 @@ else:
EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD") EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD")
EMAIL_USE_SSL = True 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 # Shynet
# Can everyone create services, or only superusers? # Can everyone create services, or only superusers?

View File

@@ -21,6 +21,6 @@ urlpatterns = [
path("accounts/", include("allauth.urls")), path("accounts/", include("allauth.urls")),
path("ingress/", include(("analytics.ingress_urls", "ingress")), name="ingress"), path("ingress/", include(("analytics.ingress_urls", "ingress")), name="ingress"),
path("dashboard/", include(("dashboard.urls", "dashboard"), namespace="dashboard")), 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")), path("", include(("core.urls", "core"), namespace="core")),
] ]

View File

@@ -12,13 +12,13 @@ if [[ ${sanity_results[0]} == True ]]; then
else else
echo "Database is ready to go." echo "Database is ready to go."
fi fi
if [[ -n $SHYNET_ADMIN_EMAIL && ${sanity_results[1]} == True ]]; then if [[ ${sanity_results[1]} == True ]]; then
echo "Warning: no admin user available. Consult docs for instructions." echo "Warning: no admin user available. Consult docs for instructions."
fi fi
if [[ -n $SHYNET_HOST && ${sanity_results[2]} == True ]]; then if [[ ${sanity_results[2]} == True ]]; then
echo "Warning: Shynet's hostname is not set. The script won't work correctly. Consult docs for instructions." echo "Warning: Shynet's hostname is not set. The script won't work correctly. Consult docs for instructions."
fi fi
if [[ -n $SHYNET_WHITELABEL && ${sanity_results[3]} == True ]]; then if [[ ${sanity_results[3]} == True ]]; then
echo "Warning: Shynet's whitelabel is not set. Consult docs for instructions." echo "Warning: Shynet's whitelabel is not set. Consult docs for instructions."
fi fi
echo "Startup checks complete!" echo "Startup checks complete!"

View File

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