Compare commits
22 Commits
haaavk/mas
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d66f683104 | ||
|
|
b44642e023 | ||
|
|
c12a7e9e71 | ||
|
|
0294d31ea4 | ||
|
|
40cb5afbad | ||
|
|
073bd94112 | ||
|
|
3a01fefcff | ||
|
|
14a7ec68f3 | ||
|
|
fdf2ab719b | ||
|
|
737eeb5df4 | ||
|
|
cb4855e4fc | ||
|
|
f4127cf9b1 | ||
|
|
159015de1c | ||
|
|
a7548d7eba | ||
|
|
da87ddb18f | ||
|
|
4a76ab32fc | ||
|
|
4afeced7d3 | ||
|
|
2a6efe1b7f | ||
|
|
07f3926a9c | ||
|
|
14ed0b7979 | ||
|
|
df6786e037 | ||
|
|
6621625d90 |
46
.github/workflows/build-docker.yml
vendored
Normal file
46
.github/workflows/build-docker.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: Build docker images
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
publish_to_docker_hub:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Prepare tags
|
||||
id: prep
|
||||
run: |
|
||||
DOCKER_IMAGE=milesmcc/shynet
|
||||
VERSION=${GITHUB_REF#refs/tags/}
|
||||
TAGS="${DOCKER_IMAGE}:${VERSION},${DOCKER_IMAGE}:latest"
|
||||
echo ::set-output name=tags::${TAGS}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
||||
|
||||
- name: Build and push advanced image
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
tags: ${{ steps.prep.outputs.tags }}
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM python:3-alpine
|
||||
FROM python:alpine3.12
|
||||
|
||||
# Getting things ready
|
||||
WORKDIR /usr/src/shynet
|
||||
|
||||
@@ -91,4 +91,8 @@ AGGRESSIVE_HASH_SALTING=True
|
||||
# - 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
|
||||
LOCATION_URL=https://www.openstreetmap.org/?mlat=$LATITUDE&mlon=$LONGITUDE
|
||||
|
||||
# How many services should be displayed on dashboard page?
|
||||
# Set to big number if you don't want pagination at all.
|
||||
DASHBOARD_PAGE_SIZE=5
|
||||
|
||||
5
app.json
5
app.json
@@ -127,6 +127,11 @@
|
||||
"description": "Custom location url to link to in frontend.",
|
||||
"value": "https://www.openstreetmap.org/?mlat=$LATITUDE&mlon=$LONGITUDE",
|
||||
"required": false
|
||||
},
|
||||
"DASHBOARD_PAGE_SIZE": {
|
||||
"description": "How many services should be displayed on dashboard page?",
|
||||
"value": "5",
|
||||
"required": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,14 @@
|
||||
var Shynet = {
|
||||
idempotency: null,
|
||||
heartbeatTaskId: null,
|
||||
skipHeartbeat: false,
|
||||
sendHeartbeat: function () {
|
||||
try {
|
||||
if (document.hidden) {
|
||||
if (document.hidden || Shynet.skipHeartbeat) {
|
||||
return;
|
||||
}
|
||||
|
||||
Shynet.skipHeartbeat = true;
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open(
|
||||
"POST",
|
||||
@@ -20,6 +23,12 @@ var Shynet = {
|
||||
true
|
||||
);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.onload = function () {
|
||||
Shynet.skipHeartbeat = false;
|
||||
};
|
||||
xhr.onerror = function () {
|
||||
Shynet.skipHeartbeat = false;
|
||||
};
|
||||
xhr.send(
|
||||
JSON.stringify({
|
||||
idempotency: Shynet.idempotency,
|
||||
@@ -30,13 +39,14 @@ var Shynet = {
|
||||
window.performance.timing.navigationStart,
|
||||
})
|
||||
);
|
||||
} catch (e) { }
|
||||
} 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.skipHeartbeat = false;
|
||||
Shynet.heartbeatTaskId = setInterval(Shynet.sendHeartbeat, parseInt("{{heartbeat_frequency}}"));
|
||||
Shynet.sendHeartbeat();
|
||||
}
|
||||
@@ -51,4 +61,4 @@ window.addEventListener("load", Shynet.newPageLoad);
|
||||
// -- START --
|
||||
{{script_inject|safe}}
|
||||
// -- END --
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
@@ -8,12 +8,12 @@ from django.conf import settings
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.db.models.functions import TruncDate
|
||||
from django.db.models.functions import TruncDate, TruncHour
|
||||
from django.db.utils import NotSupportedError
|
||||
from django.shortcuts import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
# How long a session a needs to go without an update to no longer be considered 'active' (i.e., currently online)
|
||||
ACTIVE_USER_TIMEDELTA = timezone.timedelta(
|
||||
milliseconds=settings.SCRIPT_HEARTBEAT_FREQUENCY * 2
|
||||
)
|
||||
@@ -125,8 +125,10 @@ class Service(models.Model):
|
||||
Session = apps.get_model("analytics", "Session")
|
||||
Hit = apps.get_model("analytics", "Hit")
|
||||
|
||||
tz_now = timezone.now()
|
||||
|
||||
currently_online = Session.objects.filter(
|
||||
service=self, last_seen__gt=timezone.now() - ACTIVE_USER_TIMEDELTA
|
||||
service=self, last_seen__gt=tz_now - ACTIVE_USER_TIMEDELTA
|
||||
).count()
|
||||
|
||||
sessions = Session.objects.filter(
|
||||
@@ -210,17 +212,35 @@ class Service(models.Model):
|
||||
if session_count == 0:
|
||||
avg_session_duration = None
|
||||
|
||||
session_chart_data = {
|
||||
k["date"]: k["count"]
|
||||
for k in sessions.annotate(date=TruncDate("start_time"))
|
||||
.values("date")
|
||||
.annotate(count=models.Count("uuid"))
|
||||
.order_by("date")
|
||||
}
|
||||
for day_offset in range((end_time - start_time).days + 1):
|
||||
day = (start_time + timezone.timedelta(days=day_offset)).date()
|
||||
if day not in session_chart_data:
|
||||
session_chart_data[day] = 0
|
||||
# Show hourly chart for date ranges of 3 days or less, otherwise daily chart
|
||||
if (end_time - start_time).days < 3:
|
||||
session_chart_tooltip_format = "MM/dd HH:mm"
|
||||
session_chart_granularity = "hourly"
|
||||
session_chart_data = {
|
||||
k["hour"]: k["count"]
|
||||
for k in sessions.annotate(hour=TruncHour("start_time"))
|
||||
.values("hour")
|
||||
.annotate(count=models.Count("uuid"))
|
||||
.order_by("hour")
|
||||
}
|
||||
for hour_offset in range(int((end_time - start_time).total_seconds() / 3600) + 1):
|
||||
hour = (start_time + timezone.timedelta(hours=hour_offset))
|
||||
if hour not in session_chart_data:
|
||||
session_chart_data[hour] = 0 if hour <= tz_now else None
|
||||
else:
|
||||
session_chart_tooltip_format = "MMM d"
|
||||
session_chart_granularity = "daily"
|
||||
session_chart_data = {
|
||||
k["date"]: k["count"]
|
||||
for k in sessions.annotate(date=TruncDate("start_time"))
|
||||
.values("date")
|
||||
.annotate(count=models.Count("uuid"))
|
||||
.order_by("date")
|
||||
}
|
||||
for day_offset in range((end_time - start_time).days + 1):
|
||||
day = (start_time + timezone.timedelta(days=day_offset)).date()
|
||||
if day not in session_chart_data:
|
||||
session_chart_data[day] = 0 if day <= tz_now.date() else None
|
||||
|
||||
return {
|
||||
"currently_online": currently_online,
|
||||
@@ -249,6 +269,8 @@ class Service(models.Model):
|
||||
)
|
||||
]
|
||||
),
|
||||
"session_chart_tooltip_format": session_chart_tooltip_format,
|
||||
"session_chart_granularity": session_chart_granularity,
|
||||
"online": True,
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
</div>
|
||||
<hr class="sep h-4">
|
||||
<div style="bottom: -1px;">
|
||||
{% include 'dashboard/includes/time_chart.html' with data=stats.session_chart_data sparkline=True height=100 name=object.uuid %}
|
||||
{% include 'dashboard/includes/time_chart.html' with data=stats.session_chart_data sparkline=True height=100 name=object.uuid tooltip_format=stats.session_chart_tooltip_format granularity=stats.session_chart_granularity %}
|
||||
</div>
|
||||
{% endwith %}
|
||||
</a>
|
||||
@@ -6,6 +6,9 @@
|
||||
},
|
||||
tooltip: {
|
||||
shared: false,
|
||||
x: {
|
||||
format: '{{tooltip_format|default:"MMM d"}}',
|
||||
},
|
||||
},
|
||||
colors: ["#805AD5"],
|
||||
chart: {
|
||||
@@ -34,6 +37,14 @@
|
||||
stops: [0, 75, 100]
|
||||
},
|
||||
},
|
||||
{% if granularity == "daily" and click_zoom %}
|
||||
events: {
|
||||
markerClick: function(event, chartContext, { seriesIndex, dataPointIndex, w: {config}}) {
|
||||
const day = config.series[seriesIndex].data[dataPointIndex].x
|
||||
window.location.href = `?startDate=${day}&endDate=${day}`
|
||||
},
|
||||
},
|
||||
{% endif %}
|
||||
},
|
||||
grid: {
|
||||
padding: {
|
||||
@@ -63,6 +74,9 @@
|
||||
},
|
||||
xaxis: {
|
||||
type: "datetime",
|
||||
labels: {
|
||||
datetimeUTC: false
|
||||
},
|
||||
},
|
||||
stroke: {
|
||||
width: 1.5,
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
{% endwith %}
|
||||
</div>
|
||||
<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 tooltip_format=stats.session_chart_tooltip_format granularity=stats.session_chart_granularity click_zoom=True %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
||||
|
||||
@@ -25,7 +25,7 @@ from .mixins import DateRangeMixin
|
||||
class DashboardView(LoginRequiredMixin, DateRangeMixin, ListView):
|
||||
model = Service
|
||||
template_name = "dashboard/pages/dashboard.html"
|
||||
paginate_by = 5
|
||||
paginate_by = settings.DASHBOARD_PAGE_SIZE
|
||||
|
||||
def get_queryset(self):
|
||||
return Service.objects.filter(
|
||||
|
||||
@@ -347,3 +347,6 @@ 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")
|
||||
|
||||
# How many services should be displayed on dashboard page?
|
||||
DASHBOARD_PAGE_SIZE = int(os.getenv("DASHBOARD_PAGE_SIZE", "5"))
|
||||
|
||||
Reference in New Issue
Block a user