Compare commits

..

23 Commits

Author SHA1 Message Date
R. Miles McCain
17bb5cda0d Improve litepicker box shadow 2021-05-14 15:32:16 +00:00
R. Miles McCain
84c647ad43 Update packages 2021-05-14 15:32:08 +00:00
Paweł Jastrzębski
0e37e7f042 Update litepicker and add ranges plugin
Fix litepicker colors

Fix litepicker event

Add custom ranges to litepicker

Fix code style

Remove some date ranges

Fix date ranges

Replace yesterday date range with last 3 days
2021-05-14 15:09:14 +00:00
CasperVerswijvelt
a76e0feaf3 Add custom location url from environment variable
Remove trailing dollar in long and lat placeholder
2021-05-14 15:07:49 +00:00
CasperVerswijvelt
109d977932 Make favicon not squish and add ellipsis overflow
General styling improvements

Many UI Improvements

- Consistent spacing between titles and content
- Removed many ugly text squishing by hiding overflowing text with ellipsis
- Fixed Service favicon being squisched by long service name
- Hide scrollbar in 'more session' screen when content isn't scrollable
- Fix apexcharts tooltips and labels being cut off by card class

Disable wrapping in table cells, prefer ellipsis

Ellipsis overflow for long url on hit page

Fix flex grow in header not working as intended

Remove forgotten truncatechars

Fix code checks, add button role and tabindex
2021-05-14 15:04:30 +00:00
CasperVerswijvelt
5c782ddb7d Use flag icons instead of emoji's 2021-05-14 14:52:11 +00:00
R. Miles McCain
a6a508899a Contextual date improvements 2021-05-14 14:50:04 +00:00
CasperVerswijvelt
2b003b8fa9 Preserve date range in urls in side menu 2021-05-08 12:27:32 +02:00
CasperVerswijvelt
023e0fde15 Preserve date range query parameters 2021-05-06 21:23:05 +02:00
R. Miles McCain
03f88af03c Update test for my new instance 2021-04-24 17:35:55 +00:00
R. Miles McCain
87b7ce2edc Merge branch 'improve_currently_online' into dev 2021-04-24 17:27:27 +00:00
R. Miles McCain
26c1ae2bce Fix ingest when MMDB not found 2021-04-24 17:21:29 +00:00
R. Miles McCain
36d72508e6 Merge branch 'CasperVerswijvelt/master' into dev 2021-04-24 17:07:24 +00:00
R. Miles McCain
68945df17d Improve service page when no hits are recorded 2021-04-24 17:06:32 +00:00
R. Miles McCain
fef531efa9 Improve homepage when no services exist 2021-04-24 17:06:19 +00:00
CasperVerswijvelt
46176b19fc Merge 2 steps 2021-04-24 13:04:21 +02:00
CasperVerswijvelt
94c53d2ab5 Show snippet on service page when not hits are recorded yet 2021-04-24 13:01:03 +02:00
Paweł Jastrzębski
71431fcaa6 Fix currently_online
Make currently_online aware of SCRIPT_HEARTBEAT_FREQUENCY
2021-04-23 11:00:32 +02:00
dependabot[bot]
351efff147 Bump django from 3.1.7 to 3.1.8 (#115)
Bumps [django](https://github.com/django/django) from 3.1.7 to 3.1.8.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.1.7...3.1.8)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-16 17:02:07 -04:00
dependabot[bot]
6867cbd282 Bump django-debug-toolbar from 3.2 to 3.2.1 (#117)
Bumps [django-debug-toolbar](https://github.com/jazzband/django-debug-toolbar) from 3.2 to 3.2.1.
- [Release notes](https://github.com/jazzband/django-debug-toolbar/releases)
- [Changelog](https://github.com/jazzband/django-debug-toolbar/blob/main/docs/changes.rst)
- [Commits](https://github.com/jazzband/django-debug-toolbar/compare/3.2...3.2.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-16 17:01:58 -04:00
CasperVerswijvelt
c03ef52ba8 Fix pixel request from not allowed origin triggering a hit 2021-04-02 21:21:24 +02:00
R. Miles McCain
9cb030ecbd Bump version 2021-03-29 15:09:27 +00:00
R. Miles McCain
8bab14cc8a Separate bounce migration into two 2021-03-29 15:04:36 +00:00
32 changed files with 456 additions and 160 deletions

View File

@@ -13,7 +13,6 @@ django-ipware = "~=2.1.0"
pyyaml = "~=5.4" pyyaml = "~=5.4"
ua-parser = "~=0.10.0" ua-parser = "~=0.10.0"
user-agents = "~=2.1" user-agents = "~=2.1"
emoji-country-flag = "~=1.2.1"
rules = "~=2.2" rules = "~=2.2"
gunicorn = "~=20.0.4" gunicorn = "~=20.0.4"
psycopg2-binary = "~=2.8.5" psycopg2-binary = "~=2.8.5"

49
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "f8c76565a776f1bd36364077a86d6c16fccc522d9d2024bb9b51be5cb9f8b4b5" "sha256": "c51ea0205c9ffe753b9ef5249cd49c2338bb50768ae104113bfb7b97b5f9d70c"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": {}, "requires": {},
@@ -19,23 +19,21 @@
"sha256:70cdb10628468ff14e57ec2f751c7aa9e48e7e3651cfd62d431213c0c4e58f21", "sha256:70cdb10628468ff14e57ec2f751c7aa9e48e7e3651cfd62d431213c0c4e58f21",
"sha256:aa7f313fb887c91f15474c1229907a04dac0b8135822d6603437803424c0aa59" "sha256:aa7f313fb887c91f15474c1229907a04dac0b8135822d6603437803424c0aa59"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.6.1" "version": "==2.6.1"
}, },
"asgiref": { "asgiref": {
"hashes": [ "hashes": [
"sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17", "sha256:92906c611ce6c967347bbfea733f13d6313901d54dcca88195eaeb52b2a8e8ee",
"sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0" "sha256:d1216dfbdfb63826470995d31caed36225dcaf34f182e0fa257a4dd9e86f1b78"
], ],
"markers": "python_version >= '3.5'", "version": "==3.3.4"
"version": "==3.3.1"
}, },
"billiard": { "billiard": {
"hashes": [ "hashes": [
"sha256:bff575450859a6e0fbc2f9877d9b715b0bbc07c3565bb7ed2280526a0cdf5ede", "sha256:299de5a8da28a783d51b197d496bef4f1595dd023a93a4f59dde1886ae905547",
"sha256:d91725ce6425f33a97dfa72fb6bfef0e47d4652acd98a032bd1a7fbf06d5fa6a" "sha256:87103ea78fa6ab4d5c751c4909bcff74617d985de7fa8b672cf8618afd5a875b"
], ],
"version": "==3.6.3.0" "version": "==3.6.4.0"
}, },
"celery": { "celery": {
"hashes": [ "hashes": [
@@ -57,7 +55,6 @@
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==4.0.0" "version": "==4.0.0"
}, },
"defusedxml": { "defusedxml": {
@@ -65,16 +62,15 @@
"sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69",
"sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61" "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==0.7.1" "version": "==0.7.1"
}, },
"django": { "django": {
"hashes": [ "hashes": [
"sha256:32ce792ee9b6a0cbbec340123e229ac9f765dff8c2a4ae9247a14b2ba3a365a7", "sha256:c348b3ddc452bf4b62361f0752f71a339140c777ebea3cdaaaa8fdb7f417a862",
"sha256:baf099db36ad31f970775d0be5587cc58a6256a6771a44eb795b554d45f211b8" "sha256:f8393103e15ec2d2d313ccbb95a3f1da092f9f58d74ac1c61ca2ac0436ae1eac"
], ],
"index": "pypi", "index": "pypi",
"version": "==3.1.7" "version": "==3.1.8"
}, },
"django-allauth": { "django-allauth": {
"hashes": [ "hashes": [
@@ -85,11 +81,11 @@
}, },
"django-debug-toolbar": { "django-debug-toolbar": {
"hashes": [ "hashes": [
"sha256:84e2607d900dbd571df0a2acf380b47c088efb787dce9805aefeb407341961d2", "sha256:a5ff2a54f24bf88286f9872836081078f4baa843dc3735ee88524e89f8821e33",
"sha256:9e5a25d0c965f7e686f6a8ba23613ca9ca30184daa26487706d4829f5cfb697a" "sha256:e759e63e3fe2d3110e0e519639c166816368701eab4a47fed75d7de7018467b9"
], ],
"index": "pypi", "index": "pypi",
"version": "==3.2" "version": "==3.2.1"
}, },
"django-health-check": { "django-health-check": {
"hashes": [ "hashes": [
@@ -120,14 +116,6 @@
"index": "pypi", "index": "pypi",
"version": "==3.0.0" "version": "==3.0.0"
}, },
"emoji-country-flag": {
"hashes": [
"sha256:338f5e374119dcde093cfeaa8ca3af372d4b8d984d89a7fb2fb0db0011662560",
"sha256:a3a068191294294143d8ef294fdfe9792c5c243753eac130798bf2fa5de38185"
],
"index": "pypi",
"version": "==1.2.4"
},
"geoip2": { "geoip2": {
"hashes": [ "hashes": [
"sha256:5869e987bc54c0d707264fec4710661332cc38d2dca5a7f9bb5362d0308e2ce0", "sha256:5869e987bc54c0d707264fec4710661332cc38d2dca5a7f9bb5362d0308e2ce0",
@@ -157,7 +145,6 @@
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.10" "version": "==2.10"
}, },
"kombu": { "kombu": {
@@ -165,14 +152,12 @@
"sha256:be48cdffb54a2194d93ad6533d73f69408486483d189fe9f5990ee24255b0e0a", "sha256:be48cdffb54a2194d93ad6533d73f69408486483d189fe9f5990ee24255b0e0a",
"sha256:ca1b45faac8c0b18493d02a8571792f3c40291cf2bcf1f55afed3d8f3aa7ba74" "sha256:ca1b45faac8c0b18493d02a8571792f3c40291cf2bcf1f55afed3d8f3aa7ba74"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==4.6.11" "version": "==4.6.11"
}, },
"maxminddb": { "maxminddb": {
"hashes": [ "hashes": [
"sha256:47e86a084dd814fac88c99ea34ba3278a74bc9de5a25f4b815b608798747c7dc" "sha256:47e86a084dd814fac88c99ea34ba3278a74bc9de5a25f4b815b608798747c7dc"
], ],
"markers": "python_version >= '3.6'",
"version": "==2.0.3" "version": "==2.0.3"
}, },
"oauthlib": { "oauthlib": {
@@ -180,7 +165,6 @@
"sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889", "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889",
"sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea" "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==3.1.0" "version": "==3.1.0"
}, },
"psycopg2-binary": { "psycopg2-binary": {
@@ -293,14 +277,12 @@
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.25.1" "version": "==2.25.1"
}, },
"requests-oauthlib": { "requests-oauthlib": {
"hashes": [ "hashes": [
"sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d", "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d",
"sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a", "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"
"sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"
], ],
"version": "==1.3.0" "version": "==1.3.0"
}, },
@@ -316,7 +298,6 @@
"sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0", "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0",
"sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8" "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"
], ],
"markers": "python_version >= '3.5'",
"version": "==0.4.1" "version": "==0.4.1"
}, },
"ua-parser": { "ua-parser": {
@@ -332,7 +313,6 @@
"sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df", "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df",
"sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937" "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
"version": "==1.26.4" "version": "==1.26.4"
}, },
"user-agents": { "user-agents": {
@@ -348,7 +328,6 @@
"sha256:133ee6d7a9016f177ddeaf191c1f58421a1dcc6ee9a42c58b34bed40e1d2cd87", "sha256:133ee6d7a9016f177ddeaf191c1f58421a1dcc6ee9a42c58b34bed40e1d2cd87",
"sha256:ea4947cc56d1fd6f2095c8d543ee25dad966f78692528e68b4fada11ba3f98af" "sha256:ea4947cc56d1fd6f2095c8d543ee25dad966f78692528e68b4fada11ba3f98af"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.3.0" "version": "==1.3.0"
}, },
"whitenoise": { "whitenoise": {

View File

@@ -82,4 +82,13 @@ BLOCK_ALL_IPS=False
# that IP collection is also disabled, and external keys (primary # that IP collection is also disabled, and external keys (primary
# keys) aren't supplied. It will also prevent sessions from spanning # keys) aren't supplied. It will also prevent sessions from spanning
# one day to another. # one day to another.
AGGRESSIVE_HASH_SALTING=True AGGRESSIVE_HASH_SALTING=True
# Custom location url to link to in frontend.
# $LATITUDE will get replaced by the latitude, $LONGITUDE will get
# replaced by the longitude.
# Examples:
# - https://www.openstreetmap.org/?mlat=$LATITUDE&mlon=$LONGITUDE (default)
# - https://www.google.com/maps/search/?api=1&query=$LATITUDE,$LONGITUDE
# - https://www.mapquest.com/near-$LATITUDE,$LONGITUDE
LOCATION_URL=https://www.openstreetmap.org/?mlat=$LATITUDE&mlon=$LONGITUDE

View File

@@ -122,6 +122,11 @@
"description": "Set to 'False' if you do not want the version to be displayed on the frontend.", "description": "Set to 'False' if you do not want the version to be displayed on the frontend.",
"value": "True", "value": "True",
"required": false "required": false
},
"LOCATION_URL": {
"description": "Custom location url to link to in frontend.",
"value": "https://www.openstreetmap.org/?mlat=$LATITUDE&mlon=$LONGITUDE",
"required": false
} }
} }
} }

172
package-lock.json generated
View File

@@ -1,12 +1,153 @@
{ {
"name": "shynet", "name": "shynet",
"lockfileVersion": 2,
"requires": true, "requires": true,
"lockfileVersion": 1, "packages": {
"": {
"license": "Apache-2.0",
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.1",
"a17t": "^0.5.1",
"apexcharts": "^3.24.0",
"flag-icon-css": "^3.5.0",
"inter-ui": "^3.15.0",
"litepicker": "^2.0.11"
}
},
"node_modules/@fortawesome/fontawesome-free": {
"version": "5.15.3",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.3.tgz",
"integrity": "sha512-rFnSUN/QOtnOAgqFRooTA3H57JLDm0QEG/jPdk+tLQNL/eWd+Aok8g3qCI+Q1xuDPWpGW/i9JySpJVsq8Q0s9w==",
"hasInstallScript": true,
"engines": {
"node": ">=6"
}
},
"node_modules/a17t": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/a17t/-/a17t-0.5.1.tgz",
"integrity": "sha512-peIPrH9eDiu49LLzLlSTFFrXj6WLlEX3TRsUkqyyOHi/i58ilJ/eERnu7AcswXhuCBx+/2W9EUuHM+8iAq4ipg=="
},
"node_modules/apexcharts": {
"version": "3.26.2",
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.26.2.tgz",
"integrity": "sha512-CD7bad4ygwc9rs9vOQDDagUcoJ1mcc9BwNSiQB14l6jiZBCQKrXxnG4I1ZjJ2MIel/Y5GmsJFs8HTcZBqpe/Ew==",
"dependencies": {
"svg.draggable.js": "^2.2.2",
"svg.easing.js": "^2.0.0",
"svg.filter.js": "^2.0.2",
"svg.pathmorphing.js": "^0.1.3",
"svg.resize.js": "^1.4.3",
"svg.select.js": "^3.0.1"
},
"funding": {
"url": "https://github.com/apexcharts/apexcharts.js?sponsor=1"
}
},
"node_modules/flag-icon-css": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/flag-icon-css/-/flag-icon-css-3.5.0.tgz",
"integrity": "sha512-pgJnJLrtb0tcDgU1fzGaQXmR8h++nXvILJ+r5SmOXaaL/2pocunQo2a8TAXhjQnBpRLPtZ1KCz/TYpqeNuE2ew=="
},
"node_modules/inter-ui": {
"version": "3.18.1",
"resolved": "https://registry.npmjs.org/inter-ui/-/inter-ui-3.18.1.tgz",
"integrity": "sha512-W3LnAirp6a1ixpAHZwr9gH52KlOQOAp0oqbmIoGi2dAIlcIB7auJgLr9XFHUzYy2FoZ0Nf7aPe/nHMZB4/Zvdg=="
},
"node_modules/litepicker": {
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/litepicker/-/litepicker-2.0.11.tgz",
"integrity": "sha512-7MECMp2EDGIYDIz9QT24t9hWpgBD9JD57ZdDrbffNMGfbw0JVhBhvlYsyaIUuYhywtLvgmI5lfulM7XF2HLEkg=="
},
"node_modules/svg.draggable.js": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz",
"integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==",
"dependencies": {
"svg.js": "^2.0.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/svg.easing.js": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz",
"integrity": "sha1-iqmUawqOJ4V6XEChDrpAkeVpHxI=",
"dependencies": {
"svg.js": ">=2.3.x"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/svg.filter.js": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz",
"integrity": "sha1-kQCOFROJ3ZIwd5/L5uLJo2LRwgM=",
"dependencies": {
"svg.js": "^2.2.5"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/svg.js": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz",
"integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA=="
},
"node_modules/svg.pathmorphing.js": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz",
"integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==",
"dependencies": {
"svg.js": "^2.4.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/svg.resize.js": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz",
"integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==",
"dependencies": {
"svg.js": "^2.6.5",
"svg.select.js": "^2.1.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/svg.resize.js/node_modules/svg.select.js": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz",
"integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==",
"dependencies": {
"svg.js": "^2.2.5"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/svg.select.js": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz",
"integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==",
"dependencies": {
"svg.js": "^2.6.5"
},
"engines": {
"node": ">= 0.8.0"
}
}
},
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": { "@fortawesome/fontawesome-free": {
"version": "5.15.1", "version": "5.15.3",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.1.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.3.tgz",
"integrity": "sha512-OEdH7SyC1suTdhBGW91/zBfR6qaIhThbcN8PUXtXilY4GYnSBbVqOntdHbC1vXwsDnX0Qix2m2+DSU1J51ybOQ==" "integrity": "sha512-rFnSUN/QOtnOAgqFRooTA3H57JLDm0QEG/jPdk+tLQNL/eWd+Aok8g3qCI+Q1xuDPWpGW/i9JySpJVsq8Q0s9w=="
}, },
"a17t": { "a17t": {
"version": "0.5.1", "version": "0.5.1",
@@ -14,9 +155,9 @@
"integrity": "sha512-peIPrH9eDiu49LLzLlSTFFrXj6WLlEX3TRsUkqyyOHi/i58ilJ/eERnu7AcswXhuCBx+/2W9EUuHM+8iAq4ipg==" "integrity": "sha512-peIPrH9eDiu49LLzLlSTFFrXj6WLlEX3TRsUkqyyOHi/i58ilJ/eERnu7AcswXhuCBx+/2W9EUuHM+8iAq4ipg=="
}, },
"apexcharts": { "apexcharts": {
"version": "3.24.0", "version": "3.26.2",
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.24.0.tgz", "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.26.2.tgz",
"integrity": "sha512-iT6czJCIVrmAtrcO90MZTQCvC+xi6R6Acf0jNH/d40FVTtCfcqECuKIh5iAMyOTtgUb7+fQ8rbadH2bm1kbL9Q==", "integrity": "sha512-CD7bad4ygwc9rs9vOQDDagUcoJ1mcc9BwNSiQB14l6jiZBCQKrXxnG4I1ZjJ2MIel/Y5GmsJFs8HTcZBqpe/Ew==",
"requires": { "requires": {
"svg.draggable.js": "^2.2.2", "svg.draggable.js": "^2.2.2",
"svg.easing.js": "^2.0.0", "svg.easing.js": "^2.0.0",
@@ -26,15 +167,20 @@
"svg.select.js": "^3.0.1" "svg.select.js": "^3.0.1"
} }
}, },
"flag-icon-css": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/flag-icon-css/-/flag-icon-css-3.5.0.tgz",
"integrity": "sha512-pgJnJLrtb0tcDgU1fzGaQXmR8h++nXvILJ+r5SmOXaaL/2pocunQo2a8TAXhjQnBpRLPtZ1KCz/TYpqeNuE2ew=="
},
"inter-ui": { "inter-ui": {
"version": "3.15.0", "version": "3.18.1",
"resolved": "https://registry.npmjs.org/inter-ui/-/inter-ui-3.15.0.tgz", "resolved": "https://registry.npmjs.org/inter-ui/-/inter-ui-3.18.1.tgz",
"integrity": "sha512-6v0WK8FHkVYbNQZ7L9O5tP8280pgTBR9ydxqYwssMuUH6SZO70ZFK/NQ1Ob8nNmOOzpUJAzT0WE73ty96z1tAQ==" "integrity": "sha512-W3LnAirp6a1ixpAHZwr9gH52KlOQOAp0oqbmIoGi2dAIlcIB7auJgLr9XFHUzYy2FoZ0Nf7aPe/nHMZB4/Zvdg=="
}, },
"litepicker": { "litepicker": {
"version": "1.5.7", "version": "2.0.11",
"resolved": "https://registry.npmjs.org/litepicker/-/litepicker-1.5.7.tgz", "resolved": "https://registry.npmjs.org/litepicker/-/litepicker-2.0.11.tgz",
"integrity": "sha512-4L2ZcF8iqCE4A/qGWS3PbdFplZR1g751x5SsZ87zCRZ4LQN1Fgezarnvqi0eHk/kDWK7Qx0HZ9Y4bNznJMF1xA==" "integrity": "sha512-7MECMp2EDGIYDIz9QT24t9hWpgBD9JD57ZdDrbffNMGfbw0JVhBhvlYsyaIUuYhywtLvgmI5lfulM7XF2HLEkg=="
}, },
"svg.draggable.js": { "svg.draggable.js": {
"version": "2.2.2", "version": "2.2.2",

View File

@@ -20,7 +20,8 @@
"@fortawesome/fontawesome-free": "^5.15.1", "@fortawesome/fontawesome-free": "^5.15.1",
"a17t": "^0.5.1", "a17t": "^0.5.1",
"apexcharts": "^3.24.0", "apexcharts": "^3.24.0",
"flag-icon-css": "^3.5.0",
"inter-ui": "^3.15.0", "inter-ui": "^3.15.0",
"litepicker": "^1.5.7" "litepicker": "^2.0.11"
} }
} }

View File

@@ -1,15 +1,6 @@
# Generated by Django 3.1.7 on 2021-03-28 21:38 # Generated by Django 3.1.7 on 2021-03-28 21:38
from django.db.models.expressions import F
from ..models import Session, Hit
from django.db import migrations, models from django.db import migrations, models
from django.db.models import Subquery, OuterRef
def update_bounce_stats(_a, _b):
Session.objects.all().annotate(hit_count=models.Count("hit")).filter(
hit_count__gt=1
).update(is_bounce=False)
class Migration(migrations.Migration): class Migration(migrations.Migration):
@@ -23,6 +14,5 @@ class Migration(migrations.Migration):
model_name="session", model_name="session",
name="is_bounce", name="is_bounce",
field=models.BooleanField(db_index=True, default=True), field=models.BooleanField(db_index=True, default=True),
), )
migrations.RunPython(update_bounce_stats, lambda: ()),
] ]

View File

@@ -0,0 +1,21 @@
# Generated by Django 3.1.7 on 2021-03-29 15:00
from django.db import migrations, models
from ..models import Session
def update_bounce_stats(_a, _b):
Session.objects.all().annotate(hit_count=models.Count("hit")).filter(
hit_count__gt=1
).update(is_bounce=False)
class Migration(migrations.Migration):
dependencies = [
("analytics", "0008_session_is_bounce"),
]
operations = [
migrations.RunPython(update_bounce_stats, lambda: ()),
]

View File

@@ -1,12 +1,10 @@
import json
import uuid import uuid
from django.conf import settings
from django.db import models from django.db import models
from django.shortcuts import reverse from django.shortcuts import reverse
from django.utils import timezone from django.utils import timezone
from core.models import Service from core.models import Service, ACTIVE_USER_TIMEDELTA
def _default_uuid(): def _default_uuid():
@@ -61,9 +59,7 @@ class Session(models.Model):
@property @property
def is_currently_active(self): def is_currently_active(self):
return timezone.now() - self.last_seen < timezone.timedelta( return timezone.now() - self.last_seen < ACTIVE_USER_TIMEDELTA
milliseconds=settings.SCRIPT_HEARTBEAT_FREQUENCY * 2
)
@property @property
def duration(self): def duration(self):

View File

@@ -39,6 +39,9 @@ def _geoip2_lookup(ip):
} }
except geoip2.errors.AddressNotFoundError: except geoip2.errors.AddressNotFoundError:
return {} return {}
except FileNotFoundError as e:
log.exception("Unable to perform GeoIP lookup: %s", e)
return {}
@shared_task @shared_task
@@ -58,6 +61,7 @@ def ingress_request(
log.debug(f"Linked to service {service}") log.debug(f"Linked to service {service}")
if dnt and service.respect_dnt: if dnt and service.respect_dnt:
log.debug("Ignoring because of DNT")
return return
try: try:
@@ -67,6 +71,7 @@ def ingress_request(
ignored_network.version == remote_ip.version ignored_network.version == remote_ip.version
and ignored_network.supernet_of(remote_ip) and ignored_network.supernet_of(remote_ip)
): ):
log.debug("Ignoring because of ignored IP")
return return
except ValueError as e: except ValueError as e:
log.exception(e) log.exception(e)
@@ -197,4 +202,5 @@ def ingress_request(
) )
except Exception as e: except Exception as e:
log.exception(e) log.exception(e)
print(e)
raise e raise e

View File

@@ -54,7 +54,7 @@ class ValidateServiceOriginsMixin:
origins = service.origins origins = service.origins
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) allow_origin = "*"
if origins != "*": if origins != "*":
remote_origin = request.META.get("HTTP_ORIGIN") remote_origin = request.META.get("HTTP_ORIGIN")
@@ -66,12 +66,12 @@ class ValidateServiceOriginsMixin:
remote_origin = f"{parsed.scheme}://{parsed.netloc}".lower() remote_origin = f"{parsed.scheme}://{parsed.netloc}".lower()
origins = [origin.strip().lower() for origin in origins.split(",")] origins = [origin.strip().lower() for origin in origins.split(",")]
if remote_origin in origins: if remote_origin in origins:
resp["Access-Control-Allow-Origin"] = remote_origin allow_origin = remote_origin
else: else:
return HttpResponseForbidden() return HttpResponseForbidden()
else:
resp["Access-Control-Allow-Origin"] = "*"
resp = super().dispatch(request, *args, **kwargs)
resp["Access-Control-Allow-Origin"] = 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"

View File

@@ -4,6 +4,7 @@ import re
import uuid import uuid
from django.apps import apps from django.apps import apps
from django.conf import settings
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
@@ -13,6 +14,11 @@ from django.shortcuts import reverse
from django.utils import timezone from django.utils import timezone
ACTIVE_USER_TIMEDELTA = timezone.timedelta(
milliseconds=settings.SCRIPT_HEARTBEAT_FREQUENCY * 2
)
def _default_uuid(): def _default_uuid():
return str(uuid.uuid4()) return str(uuid.uuid4())
@@ -120,7 +126,7 @@ class Service(models.Model):
Hit = apps.get_model("analytics", "Hit") Hit = apps.get_model("analytics", "Hit")
currently_online = Session.objects.filter( currently_online = Session.objects.filter(
service=self, last_seen__gt=timezone.now() - timezone.timedelta(seconds=10) service=self, last_seen__gt=timezone.now() - ACTIVE_USER_TIMEDELTA
).count() ).count()
sessions = Session.objects.filter( sessions = Session.objects.filter(
@@ -133,6 +139,8 @@ class Service(models.Model):
) )
hit_count = hits.count() hit_count = hits.count()
has_hits = Hit.objects.filter(service=self).exists()
bounces = sessions.filter(is_bounce=True) bounces = sessions.filter(is_bounce=True)
bounce_count = bounces.count() bounce_count = bounces.count()
@@ -218,6 +226,7 @@ class Service(models.Model):
"currently_online": currently_online, "currently_online": currently_online,
"session_count": session_count, "session_count": session_count,
"hit_count": hit_count, "hit_count": hit_count,
"has_hits": has_hits,
"avg_hits_per_session": hit_count / (max(session_count, 1)), "avg_hits_per_session": hit_count / (max(session_count, 1)),
"bounce_rate_pct": bounce_count * 100 / session_count "bounce_rate_pct": bounce_count * 100 / session_count
if session_count > 0 if session_count > 0

View File

@@ -1,12 +1,11 @@
from datetime import datetime, time from datetime import datetime, time
from urllib.parse import urlparse
from django.utils import timezone from django.utils import timezone
class DateRangeMixin: class DateRangeMixin:
def get_start_date(self): def get_start_date(self):
if self.request.GET.get("startDate") != None: if self.request.GET.get("startDate") is not None:
found_time = timezone.datetime.strptime( found_time = timezone.datetime.strptime(
self.request.GET.get("startDate"), "%Y-%m-%d" self.request.GET.get("startDate"), "%Y-%m-%d"
) )
@@ -15,7 +14,7 @@ class DateRangeMixin:
return timezone.now() - timezone.timedelta(days=30) return timezone.now() - timezone.timedelta(days=30)
def get_end_date(self): def get_end_date(self):
if self.request.GET.get("endDate") != None: if self.request.GET.get("endDate") is not None:
found_time = timezone.datetime.strptime( found_time = timezone.datetime.strptime(
self.request.GET.get("endDate"), "%Y-%m-%d" self.request.GET.get("endDate"), "%Y-%m-%d"
) )
@@ -23,8 +22,45 @@ class DateRangeMixin:
else: else:
return timezone.now() return timezone.now()
def get_date_ranges(self):
now = timezone.now()
return [
{
'name': 'Last 3 days',
'start': now - timezone.timedelta(days=2),
'end': now,
},
{
'name': 'Last 30 days',
'start': now - timezone.timedelta(days=29),
'end': now,
},
{
'name': 'Last 90 days',
'start': now - timezone.timedelta(days=89),
'end': now,
},
{
'name': 'This month',
'start': now.replace(day=1),
'end': now,
},
{
'name': 'Last month',
'start': now.replace(day=1, month=now.month - 1),
'end': now.replace(day=1, month=now.month) - timezone.timedelta(days=1),
},
{
'name': 'This year',
'start': now.replace(day=1, month=1),
'end': now,
},
]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs) data = super().get_context_data(**kwargs)
data["start_date"] = self.get_start_date() data["start_date"] = self.get_start_date()
data["end_date"] = self.get_end_date() data["end_date"] = self.get_end_date()
data["date_ranges"] = self.get_date_ranges()
return data return data

View File

@@ -15,6 +15,14 @@
text-align: right !important; text-align: right !important;
} }
.chart-card .apexcharts-svg {
border-radius: 0 0 var(--border-radius-lg, 0.5rem) var(--border-radius-lg, 0.5rem);
}
.max-w-0 {
max-width: 0;
}
:root { :root {
--color-neutral-000: white; --color-neutral-000: white;
--color-neutral-50: #F8FAFC; --color-neutral-50: #F8FAFC;

View File

@@ -1,8 +1,8 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<div> <div class="flex-1 truncate">
<h4 class="heading">{% block page_title %}{% endblock %}</h4> <h4 class="heading truncate">{% block page_title %}{% endblock %}</h4>
</div> </div>
<hr class="sep"> <hr class="sep">
{% block main %} {% block main %}

View File

@@ -10,30 +10,34 @@
{% include 'a17t/includes/head.html' %} {% include 'a17t/includes/head.html' %}
<link rel="icon" type="image/png" href="{% static 'dashboard/images/icon.png' %}"> <link rel="icon" type="image/png" href="{% static 'dashboard/images/icon.png' %}">
<script src="{% static 'apexcharts/dist/apexcharts.min.js'%}"></script> <script src="{% static 'apexcharts/dist/apexcharts.min.js'%}"></script>
<script src="{% static 'litepicker/dist/js/main.js' %}"></script> <script src="{% static 'litepicker/dist/nocss/litepicker.js' %}"></script>
<script src="{% static 'litepicker/dist/plugins/ranges.js' %}"></script>
<link rel="stylesheet" href="{% static 'litepicker/dist/css/litepicker.css' %}">
<script src="{% static 'dashboard/js/base.js' %}"></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' %}">
<link rel="stylesheet" href="{% static 'flag-icon-css/css/flag-icon.min.css' %}">
{% block extra_head %} {% block extra_head %}
{% endblock %} {% endblock %}
</head> </head>
<body class="bg-neutral-100 min-h-full"> <body class="bg-neutral-100 min-h-full overflow-x-hidden">
{% block body %} {% block body %}
<section class="max-w-screen-xl mx-auto px-4 py-4 md:py-12 md:flex"> <section class="max-w-screen-xl mx-auto px-4 py-4 md:py-12 md:flex">
<aside <aside
class="mb-8 md:w-2/12 md:pr-6 relative flex flex-wrap md:block justify-between items-center overflow-x-hidden"> class="mb-2 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' %}"> <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-3x text-urge-600 hidden md:block"></i>
<i class="fas fa-binoculars fa-2x text-urge-600 md:hidden"></i> <i class="fas fa-binoculars fa-2x text-urge-600 md:hidden"></i>
</a> </a>
<button class="button ~neutral !low md:hidden" <a tabindex="0" role="button" class="button ~neutral !low md:hidden"
onclick="document.getElementById('navMenuExpanded').classList.toggle('hidden')"> onclick="document.getElementById('navMenuExpanded').classList.toggle('hidden')">
<span class="icon"> <span class="icon">
<i class="fas fa-bars"></i> <i class="fas fa-bars"></i>
</span> </span>
</button> </a>
<hr class="sep h-4 md:h-8 w-full"> <hr class="sep h-4 md:h-8 w-full">
<div id="navMenuExpanded" <div id="navMenuExpanded"
class="bg-neutral-000 shadow-lg md:shadow-none p-4 hidden rounded-lg md:block md:bg-transparent md:border-none md:p-0 w-full"> class="bg-neutral-000 shadow-lg md:shadow-none p-4 hidden rounded-lg md:block md:bg-transparent md:border-none md:p-0 w-full">
@@ -41,8 +45,8 @@
<p class="ml-2 mb-1 supra font-medium text-gray-500 pointer-events-none">Services</p> <p class="ml-2 mb-1 supra font-medium text-gray-500 pointer-events-none">Services</p>
{% for service in user.owning_services.all %} {% for service in user.owning_services.all %}
{% url 'dashboard:service' service.uuid as url %} {% contextual_url 'dashboard:service' service.uuid as url %}
{% include 'dashboard/includes/sidebar_portal.html' with label=service.name|truncatechars:16 url=url icon=service.link|iconify %} {% include 'dashboard/includes/sidebar_portal.html' with label=service.name url=url icon=service.link|iconify %}
{% endfor %} {% endfor %}
{% endif %} {% endif %}
@@ -60,8 +64,8 @@
<p class="ml-2 mb-1 supra font-medium text-gray-500 pointer-events-none">Collaborations</p> <p class="ml-2 mb-1 supra font-medium text-gray-500 pointer-events-none">Collaborations</p>
{% for service in user.collaborating_services.all %} {% for service in user.collaborating_services.all %}
{% url 'dashboard:service' service.uuid as url %} {% contextual_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 url=url %}
{% endfor %} {% endfor %}
<hr class="sep h-8"> <hr class="sep h-8">
@@ -121,4 +125,4 @@
{% endblock %} {% endblock %}
</body> </body>
</html> </html>

View File

@@ -2,32 +2,48 @@
<input type="hidden" name="startDate" value="{{start_date.isoformat}}" id="startDate"> <input type="hidden" name="startDate" value="{{start_date.isoformat}}" id="startDate">
<input type="hidden" name="endDate" value="{{end_date.isoformat}}" id="endDate"> <input type="hidden" name="endDate" value="{{end_date.isoformat}}" id="endDate">
</form> </form>
<input type="input" id="rangePicker" placeholder="Date range" class="input ~neutral bg-neutral-000 cursor-pointer" readonly> <input type="input" id="rangePicker" placeholder="Date range" class="input ~neutral bg-neutral-000 cursor-pointer w-auto" readonly>
<style> <style>
:root { :root {
--litepickerMonthButtonHover: var(--color-urge); --litepicker-button-prev-month-color-hover: var(--color-urge);
--litepickerDayColorHover: var(--color-urge); --litepicker-button-next-month-color-hover: var(--color-urge);
--litepickerDayIsTodayColor: var(--color-urge); --litepicker-day-color-hover: var(--color-urge);
--litepickerDayIsInRange: var(--color-urge-normal-fill); --litepicker-is-today-color: var(--color-urge);
--litepickerDayIsStartBg: var(--color-urge); --litepicker-is-in-range-color: var(--color-urge-normal-fill);
--litepickerDayIsEndBg: var(--color-urge); --litepicker-is-start-color-bg: var(--color-urge);
--litepickerButtonApplyBg: var(--color-urge); --litepicker-is-end-color-bg: var(--color-urge);
--litepicker-button-apply-color-bg: var(--color-urge);
}
.litepicker .container__predefined-ranges, .litepicker .container__months {
box-shadow: var(--fallback-box-shadow-normal) !important;
} }
</style> </style>
<script> <script>
var picker = new Litepicker({ var picker = new Litepicker({
element: document.getElementById('rangePicker'), element: document.getElementById('rangePicker'),
plugins: ['ranges'],
singleMode: false, singleMode: false,
format: 'MMM D, YYYY', format: "MMM DD 'YY",
maxDate: new Date(), maxDate: new Date(),
startDate: Date.parse(document.getElementById("startDate").getAttribute("value")), startDate: Date.parse(document.getElementById("startDate").getAttribute("value")),
endDate: Date.parse(document.getElementById("endDate").getAttribute("value")), endDate: Date.parse(document.getElementById("endDate").getAttribute("value")),
onSelect: function (startDate, endDate) { ranges: {
document.getElementById("startDate").setAttribute("value", startDate.getFullYear() + customRanges: {
"-" + (startDate.getMonth() + 1) + "-" + startDate.getDate()); {% for date_range in date_ranges %}
document.getElementById("endDate").setAttribute("value", endDate.getFullYear() + "-" + '{{ date_range.name }}': [
(endDate.getMonth() + 1) + "-" + endDate.getDate()); new Date('{{ date_range.start.isoformat }}'),
document.getElementById("datePicker").submit(); new Date('{{ date_range.end.isoformat }}')
],
{% endfor %}
}
} }
}); });
</script> picker.on('selected', (startDate, endDate) => {
document.getElementById("startDate").setAttribute("value", startDate.getFullYear() +
"-" + (startDate.getMonth() + 1) + "-" + startDate.getDate());
document.getElementById("endDate").setAttribute("value", endDate.getFullYear() + "-" +
(endDate.getMonth() + 1) + "-" + endDate.getDate());
document.getElementById("datePicker").submit();
});
</script>

View File

@@ -1,16 +1,16 @@
{% load humanize helpers %} {% load humanize helpers %}
<a class="card ~neutral !low service mb-6 p-0" href="{% url 'dashboard:service' object.uuid %}"> <a class="card chart-card overflow-visible ~neutral !low service mb-6 p-0" href="{% contextual_url 'dashboard:service' object.uuid %}">
{% with stats=object.stats %} {% with stats=object.stats %}
<div class="p-4 md:flex justify-between"> <div class="p-4 md:flex justify-between overflow-none">
<div class="flex items-center mb-4 md:mb-0"> <div class="flex items-center mb-4 md:mb-0 md:flex-1 md:min-w-0 truncate pr-0 md:pr-2">
<h3 class="heading text-xl md:text-2xl mr-2 mb-1 text-urge-600 flex items-center"> <h3 class="heading text-xl md:text-2xl mr-2 mb-1 text-urge-600 flex items-center truncate" title="{{object.name}}">
{{object.link|iconify}} {{object.link|iconify}}
<span>{{object.name}}</span> <span class="truncate">{{object.name}}</span>
</h3> </h3>
{% include 'dashboard/includes/stats_status_chip.html' %} {% include 'dashboard/includes/stats_status_chip.html' %}
</div> </div>
<div class="grid grid-cols-2 md:grid-cols-4 gap-6"> <div class="grid grid-cols-2 md:grid-cols-4 gap-6 md:gap-3 lg:gap-6 md:flex-none">
<div> <div>
<p>Sessions</p> <p>Sessions</p>
<p class="label"> <p class="label">

View File

@@ -0,0 +1,5 @@
<div class="card ~neutral !high font-mono text-sm whitespace-pre-wrap break-all">{% filter force_escape %}<noscript>
<img src="{{script_protocol}}{{request.site.domain}}{% url 'ingress:endpoint_pixel' object.uuid %}">
</noscript>
<script defer src="{{script_protocol}}{{request.site.domain}}{% url 'ingress:endpoint_script' object.uuid %}"></script>{% endfilter %}
</div>

View File

@@ -12,7 +12,7 @@
{% for session in object_list %} {% for session in object_list %}
<tr> <tr>
<td> <td>
<a href="{% url 'dashboard:service_session' object.pk session.pk %}" <a href="{% contextual_url 'dashboard:service_session' object.pk session.pk %}"
class="font-medium text-urge-700"> class="font-medium text-urge-700">
{{session.start_time|date:"M j Y, g:i a"|capfirst}} {{session.start_time|date:"M j Y, g:i a"|capfirst}}
{% if session.is_currently_active %} {% if session.is_currently_active %}
@@ -27,7 +27,7 @@
<span class="text-gray-600">&mdash;</span> <span class="text-gray-600">&mdash;</span>
{% endif %} {% endif %}
</td> </td>
<td>{{session.country|flag_emoji}} {{session.asn|default:"Unknown"}}</td> <td><span class="{{session.country|flag_class}}"></span>{{session.asn|default:"Unknown"}}</td>
<td class="rf">{{session.duration|naturaldelta}}</td> <td class="rf">{{session.duration|naturaldelta}}</td>
<td class="rf">{{session.hit_set.count|intcomma}}</td> <td class="rf">{{session.hit_set.count|intcomma}}</td>
</tr> </tr>

View File

@@ -1,6 +1,6 @@
{% load helpers %} {% load helpers %}
<div> <div>
<a class="portal !low {% if request.get_full_path|startswith:url %}~urge active bg-neutral-100{% endif %} flex items-center" <a class="portal !low {% if request.get_full_path|startswith:url %}~urge active bg-neutral-100{% endif %} flex items-center" title="{{label}}"
{% if disable_turbolinks %}data-turbolinks="false"{% endif %} href="{{url}}">{{icon}} {{label}}</a> {% if disable_turbolinks %}data-turbolinks="false"{% endif %} href="{{url}}">{{icon}} <span class="truncate">{{label}}</span></a>
</div> </div>

View File

@@ -2,7 +2,7 @@
{% with stats=object.get_daily_stats %} {% with stats=object.get_daily_stats %}
{% if stats.currently_online > 0 %} {% if stats.currently_online > 0 %}
<span class="chip ~positive !high"> <span class="chip ~positive !high whitespace-nowrap">
{{stats.currently_online|intcomma}} online {{stats.currently_online|intcomma}} online
</span> </span>
{% endif %} {% endif %}

View File

@@ -4,8 +4,8 @@
{% block content %} {% block content %}
<div class="md:flex justify-between items-center"> <div class="md:flex justify-between items-center">
<div> <div class="flex-1 truncate display-none md:display-block mr-4 md:mb-0 mb-4" title="{{request.site.name|default:"Dashboard"}}">
<h4 class="heading">{{request.site.name|default:"Dashboard"}}</h4> <h4 class="heading truncate">{{request.site.name|default:"Dashboard"}}</h4>
</div> </div>
<div class="flex items-center"> <div class="flex items-center">
<div class="mr-1"> <div class="mr-1">
@@ -17,13 +17,15 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<hr class="sep"> <hr class="sep h-8 md:h-12">
{% for object in object_list|dictsortreversed:"stats.session_count" %} {% for object in object_list|dictsortreversed:"stats.session_count" %}
{% include 'dashboard/includes/service_overview.html' %} {% include 'dashboard/includes/service_overview.html' %}
{% empty %} {% empty %}
<p>You don't have any services on {{request.site.name|default:"Shynet"}} yet.</p> <p class="aside ~urge !high">You don't have any services yet. {% if can_create %}Get started by <a href="{% url 'dashboard:service_create' %}" class="underline">creating one</a>.{% endif %}</p>
{% endfor %} {% endfor %}
{% if object_list %}
{% pagination page_obj request %} {% pagination page_obj request %}
{% endif %}
{% endblock %} {% endblock %}

View File

@@ -6,11 +6,19 @@
<div class="mr-2">{% include 'dashboard/includes/date_range.html' %}</div> <div class="mr-2">{% include 'dashboard/includes/date_range.html' %}</div>
{% has_perm 'core.change_service' user object as can_update %} {% has_perm 'core.change_service' user object as can_update %}
{% if can_update %} {% if can_update %}
<a href="{% url 'dashboard:service_update' service.uuid %}" class="button field bg-neutral-000 w-auto">Manage &rarr;</a> <a href="{% contextual_url 'dashboard:service_update' service.uuid %}" class="button field bg-neutral-000 w-auto">Manage &rarr;</a>
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block service_content %} {% block service_content %}
{% if not stats.has_hits %}
<div class="content mb-6">
<p>
This service hasn't collected any data yet. To get started, place the following code snippet at the end of the <code>&lt;body&gt;</code> tag on any page you'd like to track.
</p>
{% include 'dashboard/includes/service_snippet.html' %}
</div>
{% else %}
<div class="grid grid-cols-2 gap-6 md:flex justify-between mb-6 card ~neutral !high px-6" id="stats"> <div class="grid grid-cols-2 gap-6 md:flex justify-between mb-6 card ~neutral !high px-6" id="stats">
{% with classes="text-sm font-semibold" good_classes="text-positive-400" bad_classes="text-critical-400" neutral_classes="text-gray-400" %} {% with classes="text-sm font-semibold" good_classes="text-positive-400" bad_classes="text-critical-400" neutral_classes="text-gray-400" %}
<article class=""> <article class="">
@@ -85,9 +93,10 @@
</article> </article>
{% endwith %} {% endwith %}
</div> </div>
<div class="card ~neutral !low py-0 mb-6"> <div class="card overflow-visible ~neutral !low py-0 mb-6">
{% include 'dashboard/includes/time_chart.html' with data=stats.session_chart_data %} {% include 'dashboard/includes/time_chart.html' with data=stats.session_chart_data %}
</div> </div>
{% endif %}
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6"> <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<div class="card ~neutral !low limited-height py-2"> <div class="card ~neutral !low limited-height py-2">
<table class="table"> <table class="table">
@@ -100,7 +109,7 @@
<tbody> <tbody>
{% for location in stats.locations %} {% for location in stats.locations %}
<tr> <tr>
<td>{{location.location|default:"Unknown"|urldisplay}}</td> <td class="truncate w-full max-w-0">{{location.location|default:"Unknown"|urldisplay}}</td>
<td class="rf">{{location.count|intcomma}}</td> <td class="rf">{{location.count|intcomma}}</td>
</tr> </tr>
{% empty %} {% empty %}
@@ -122,7 +131,7 @@
<tbody> <tbody>
{% for referrer in stats.referrers %} {% for referrer in stats.referrers %}
<tr> <tr>
<td>{{referrer.referrer|default:"Direct"|urldisplay}}</td> <td class="truncate w-full max-w-0">{{referrer.referrer|default:"Direct"|urldisplay}}</td>
<td class="rf">{{referrer.count|intcomma}}</td> <td class="rf">{{referrer.count|intcomma}}</td>
</tr> </tr>
{% empty %} {% empty %}
@@ -144,7 +153,9 @@
<tbody> <tbody>
{% for country in stats.countries %} {% for country in stats.countries %}
<tr> <tr>
<td>{{country.country|flag_emoji}} {{country.country|country_name}}</td> <td class="truncate w-full max-w-0" title="{{country.country|country_name}}">
<span class="{{country.country|flag_class}}"></span> {{country.country|country_name}}
</td>
<td class="rf">{{country.count|intcomma}}</td> <td class="rf">{{country.count|intcomma}}</td>
</tr> </tr>
{% empty %} {% empty %}
@@ -166,7 +177,9 @@
<tbody> <tbody>
{% for os in stats.operating_systems %} {% for os in stats.operating_systems %}
<tr> <tr>
<td class="flex items-center">{{os.os|iconify}}<span>{{os.os|default:"Unknown"}}</span></td> <td class="flex items-center truncate w-full max-w-0" title="{{os.os|default:'Unknown'}}">
{{os.os|iconify}}<span>{{os.os|default:"Unknown"}}</span>
</td>
<td class="rf">{{os.count|intcomma}}</td> <td class="rf">{{os.count|intcomma}}</td>
</tr> </tr>
{% empty %} {% empty %}
@@ -188,7 +201,7 @@
<tbody> <tbody>
{% for browser in stats.browsers %} {% for browser in stats.browsers %}
<tr> <tr>
<td class="flex items-center"> <td class="flex items-center truncate w-full max-w-0" title="{{browser.browser|default:'Unknown'}}">
{{browser.browser|iconify}}<span>{{browser.browser|default:"Unknown"}}</span></td> {{browser.browser|iconify}}<span>{{browser.browser|default:"Unknown"}}</span></td>
<td class="rf">{{browser.count|intcomma}}</td> <td class="rf">{{browser.count|intcomma}}</td>
</tr> </tr>
@@ -211,7 +224,7 @@
<tbody> <tbody>
{% for device_type in stats.device_types %} {% for device_type in stats.device_types %}
<tr> <tr>
<td>{{device_type.device_type|default:"Unknown"|title}}</td> <td class="truncate w-full max-w-0">{{device_type.device_type|default:"Unknown"|title}}</td>
<td class="rf">{{device_type.count|intcomma}}</td> <td class="rf">{{device_type.count|intcomma}}</td>
</tr> </tr>
{% empty %} {% empty %}
@@ -225,8 +238,8 @@
</div> </div>
<div class="card ~neutral !low limited-height py-2"> <div class="card ~neutral !low limited-height py-2">
{% include 'dashboard/includes/session_list.html' %} {% include 'dashboard/includes/session_list.html' %}
<hr class="sep h-8"> <hr class="sep h-8 md:h-12">
<a href="{% url 'dashboard:service_session_list' service.uuid %}" class="button ~neutral w-auto mb-2">View more <a href="{% contextual_url 'dashboard:service_session_list' service.uuid %}" class="button ~neutral w-auto mb-2">View more
sessions sessions
&rarr;</a> &rarr;</a>
</div> </div>

View File

@@ -5,7 +5,7 @@
{% block head_title %}{{object.name}} Session{% endblock %} {% block head_title %}{{object.name}} Session{% endblock %}
{% block service_actions %} {% block service_actions %}
<a href="{% url 'dashboard:service' object.uuid %}" class="button field bg-neutral-000 w-auto">Analytics &rarr;</a> <a href="{% contextual_url 'dashboard:service' object.uuid %}" class="button field bg-neutral-000 w-auto">Analytics &rarr;</a>
{% endblock %} {% endblock %}
{% block service_content %} {% block service_content %}
@@ -21,7 +21,7 @@
{{session.last_seen|date:"g:i a"}}{% if session.is_currently_active %} <span class="chip ~positive !high text-base">Online</span>{% endif %}</p> {{session.last_seen|date:"g:i a"}}{% if session.is_currently_active %} <span class="chip ~positive !high text-base">Online</span>{% endif %}</p>
</div> </div>
</div> </div>
<hr class="sep h-8"> <hr class="sep h-8 md:h-12">
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 text-gray-400 font-medium"> <div class="grid grid-cols-2 md:grid-cols-4 gap-4 text-gray-400 font-medium">
<div> <div>
<p>Browser</p> <p>Browser</p>
@@ -45,13 +45,13 @@
</div> </div>
<div> <div>
<p>Country</p> <p>Country</p>
<p class="label">{{session.country|flag_emoji}} {{session.country|country_name}}</p> <p class="label"><span class="{{session.country|flag_class}}"></span>{{session.country|country_name}}</p>
</div> </div>
<div> <div>
<p>Location</p> <p>Location</p>
<p class="label"> <p class="label">
{% if session.latitude %} {% if session.latitude %}
<a href="https://www.google.com/maps/search/?api=1&query={{session.latitude}},{{session.longitude}}">Open <a href="{{session|location_url}}" target="_blank">Open
in Maps &nearr;</a> in Maps &nearr;</a>
{% else %} {% else %}
Unknown Unknown
@@ -72,7 +72,7 @@
</div> </div>
<div class="md:flex card ~neutral !low flex-grow justify-between"> <div class="md:flex card ~neutral !low flex-grow justify-between">
<div class="mb-4 md:mb-0 md:w-1/2"> <div class="mb-4 md:mb-0 md:w-1/2">
<p class="label font-medium text-lg">{{hit.location|default:"Unknown"|urlize}}</p> <p class="label font-medium text-lg truncate">{{hit.location|default:"Unknown"|urlize}}</p>
{% if hit.referrer %} {% if hit.referrer %}
<p>via {{hit.referrer|urlize}}<p> <p>via {{hit.referrer|urlize}}<p>
{% endif %} {% endif %}

View File

@@ -6,11 +6,11 @@
{% block service_actions %} {% block service_actions %}
<div class="mr-2">{% include 'dashboard/includes/date_range.html' %}</div> <div class="mr-2">{% include 'dashboard/includes/date_range.html' %}</div>
<a href="{% url 'dashboard:service' object.uuid %}" class="button field ~neutral bg-neutral-000 w-auto">Analytics &rarr;</a> <a href="{% contextual_url 'dashboard:service' object.uuid %}" class="button field ~neutral bg-neutral-000 w-auto">Analytics &rarr;</a>
{% endblock %} {% endblock %}
{% block service_content %} {% block service_content %}
<div class="card ~neutral !low mb-8 pt-2 max-w-full overflow-x-scroll"> <div class="card ~neutral !low mb-8 pt-2 max-w-full overflow-x-auto">
{% include 'dashboard/includes/session_list.html' %} {% include 'dashboard/includes/session_list.html' %}
</div> </div>
{% pagination page_obj request %} {% pagination page_obj request %}

View File

@@ -12,12 +12,7 @@
<div class="max-w-xl content"> <div class="max-w-xl content">
<h5>Installation</h5> <h5>Installation</h5>
<p>Place the following snippet at the end of the <code>&lt;body&gt;</code> tag on any page you'd like to track.</p> <p>Place the following snippet at the end of the <code>&lt;body&gt;</code> tag on any page you'd like to track.</p>
<div class="card ~neutral !high font-mono text-sm"> {% include 'dashboard/includes/service_snippet.html' %}
{% filter force_escape %}<noscript><img
src="{{script_protocol}}{{request.site.domain}}{% url 'ingress:endpoint_pixel' object.uuid %}"></noscript>
<script defer src="{{script_protocol}}{{request.site.domain}}{% url 'ingress:endpoint_script' object.uuid %}"></script>
{% endfilter %}
</div>
<hr class="sep h-4"> <hr class="sep h-4">
<h5>Settings</h5> <h5>Settings</h5>
<form class="card ~neutral !low p-0" method="POST"> <form class="card ~neutral !low p-0" method="POST">

View File

@@ -6,21 +6,21 @@
{% block content %} {% block content %}
<div class="md:flex justify-between items-center" id="heading"> <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 %}"> <a class="flex items-center mb-4 md:mb-0 truncate" href="{% contextual_url 'dashboard:service' object.uuid %}">
<h3 class="heading leading-none mr-4"> <h3 class="heading items-center mr-4 md:mr-2 flex truncate">
{{object.link|iconify}} {{object.link|iconify}}
{{object.name}} <span class="flex-1 truncate ml-2" title="{{object.name}}">{{object.name}}</span>
</h3> </h3>
<div class='text-3xl'> <div class="text-3xl md:mr-2">
{% include 'dashboard/includes/stats_status_chip.html' %} {% include 'dashboard/includes/stats_status_chip.html' %}
</div> </div>
</a> </a>
<div class="flex items-center"> <div class="flex items-center flex-none">
{% block service_actions %} {% block service_actions %}
{% endblock %} {% endblock %}
</div> </div>
</div> </div>
<hr class="sep h-8"> <hr class="sep h-8 md:h-12">
{% block service_content %} {% block service_content %}
{% endblock %} {% endblock %}
{% endblock %} {% endblock %}

View File

@@ -1,13 +1,14 @@
from datetime import timedelta from datetime import timedelta
from urllib.parse import urlparse from urllib.parse import urlparse
import urllib
import flag
import pycountry import pycountry
from django import template from django import template
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone
from django.utils.html import escape from django.utils.html import escape
from django.utils.safestring import SafeString from django.utils.safestring import SafeString
from django.template.defaulttags import url as url_tag
register = template.Library() register = template.Library()
@@ -27,11 +28,11 @@ def naturaldelta(timedelta):
@register.filter @register.filter
def flag_emoji(isocode): def flag_class(isocode):
try: if isocode:
return flag.flag(isocode) return "mr-1 flag-icon flag-icon-" + isocode.lower()
except: else:
return "" return "hidden"
@register.filter @register.filter
@@ -171,7 +172,7 @@ def iconify(text):
domain = text + ".com" domain = text + ".com"
return SafeString( return SafeString(
f'<span class="icon mr-1"><img src="https://icons.duckduckgo.com/ip3/{domain}.ico"></span>' f'<span class="icon mr-1 flex-none"><img src="https://icons.duckduckgo.com/ip3/{domain}.ico"></span>'
) )
@@ -180,7 +181,50 @@ def urldisplay(url):
if url.startswith("http"): if url.startswith("http"):
display_url = url.replace("http://", "").replace("https://", "") display_url = url.replace("http://", "").replace("https://", "")
return SafeString( return SafeString(
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>" f"<a href='{url}' title='{url}' rel='nofollow' class='flex items-center mr-1'>{iconify(url)}<span class='truncate'>{escape(display_url)}</span></a>"
) )
else: else:
return url return url
class ContextualURLNode(template.Node):
"""Extension of the Django URLNode to support including contextual parameters in URL outputs. In other words, URLs generated will keep the start and end date parameters."""
CONTEXT_PARAMS = ["startDate", "endDate"]
def __init__(self, urlnode):
self.urlnode = urlnode
def __repr__(self):
return self.urlnode.__repr__()
def render(self, context):
url = self.urlnode.render(context)
if self.urlnode.asvar:
url = context[self.urlnode.asvar]
url_parts = list(urlparse(url))
query = dict(urllib.parse.parse_qsl(url_parts[4]))
query.update({
param: context.request.GET.get(param) for param in self.CONTEXT_PARAMS if param in context.request.GET and param not in query
})
url_parts[4] = urllib.parse.urlencode(query)
url_final = urllib.parse.urlunparse(url_parts)
if self.urlnode.asvar:
context[self.urlnode.asvar] = url_final
return ''
else:
return url_final
@register.tag
def contextual_url(*args, **kwargs):
urlnode = url_tag(*args, **kwargs)
return ContextualURLNode(urlnode)
@register.filter
def location_url(session):
return settings.LOCATION_URL.replace("$LATITUDE", str(session.latitude)).replace("$LONGITUDE", str(session.longitude))

View File

@@ -66,6 +66,7 @@ class ServiceView(
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs) data = super().get_context_data(**kwargs)
data["script_protocol"] = "https://" if settings.SCRIPT_USE_HTTPS else "http://"
data["stats"] = self.object.get_core_stats(data["start_date"], data["end_date"]) data["stats"] = self.object.get_core_stats(data["start_date"], data["end_date"])
data["object_list"] = Session.objects.filter( data["object_list"] = Session.objects.filter(
service=self.get_object(), service=self.get_object(),

View File

@@ -18,7 +18,7 @@ import urllib.parse as urlparse
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.8.1" VERSION = "v0.8.2"
# 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__)))
@@ -301,11 +301,16 @@ NPM_ROOT_PATH = "../"
NPM_FILE_PATTERNS = { NPM_FILE_PATTERNS = {
"a17t": [os.path.join("dist", "a17t.css"), os.path.join("dist", "tailwind.css")], "a17t": [os.path.join("dist", "a17t.css"), os.path.join("dist", "tailwind.css")],
"apexcharts": [os.path.join("dist", "apexcharts.min.js")], "apexcharts": [os.path.join("dist", "apexcharts.min.js")],
"litepicker": [os.path.join("dist", "js", "main.js")], "litepicker": [
os.path.join("dist", "nocss", "litepicker.js"),
os.path.join("dist", "css", "litepicker.css"),
os.path.join("dist", "plugins", "ranges.js"),
],
"turbolinks": [os.path.join("dist", "turbolinks.js")], "turbolinks": [os.path.join("dist", "turbolinks.js")],
"stimulus": [os.path.join("dist", "stimulus.umd.js")], "stimulus": [os.path.join("dist", "stimulus.umd.js")],
"inter-ui": [os.path.join("Inter (web)", "*")], "inter-ui": [os.path.join("Inter (web)", "*")],
"@fortawesome": [os.path.join("fontawesome-free", "js", "all.min.js")], "@fortawesome": [os.path.join("fontawesome-free", "js", "all.min.js")],
"flag-icon-css": [os.path.join("css", "flag-icon.min.css"), os.path.join("flags", "*")],
} }
# Shynet # Shynet
@@ -339,3 +344,6 @@ BLOCK_ALL_IPS = os.getenv("BLOCK_ALL_IPS", "False") == "True"
# Include date and service ID in salt? # Include date and service ID in salt?
AGGRESSIVE_HASH_SALTING = os.getenv("AGGRESSIVE_HASH_SALTING", "False") == "True" AGGRESSIVE_HASH_SALTING = os.getenv("AGGRESSIVE_HASH_SALTING", "False") == "True"
# What location url should be linked to in the frontend?
LOCATION_URL = os.getenv("LOCATION_URL", "https://www.openstreetmap.org/?mlat=$LATITUDE&mlon=$LONGITUDE")

View File

@@ -3,11 +3,14 @@
<html> <html>
<head> <head>
<title>Pixel test</title> <title>JS test</title>
</head> </head>
<body> <body>
<script src="http://localhost:8000/ingress/9b2c4e2f-8d29-4418-82d4-b68e06795025/script.js"></script> <noscript>
<img src="http://localhost:8000/ingress/0ca733e8-c41f-462b-a11a-4ba0cea29948/pixel.gif">
</noscript>
<script defer src="http://localhost:8000/ingress/0ca733e8-c41f-462b-a11a-4ba0cea29948/script.js"></script>
</body> </body>
</html> </html>