Polish analytics & add collaboration
This commit is contained in:
parent
e486397c9d
commit
cfe3dac408
@ -6,6 +6,16 @@
|
|||||||
{{ field }}
|
{{ field }}
|
||||||
<span>{{ field.label }}</span>
|
<span>{{ field.label }}</span>
|
||||||
</label>
|
</label>
|
||||||
|
{% elif field|is_multiple_checkbox %}
|
||||||
|
{% if field.auto_id %}
|
||||||
|
<label class="label my-1" for="{{field.auto_id}}">{{ field.label }}</label>
|
||||||
|
{% endif %}
|
||||||
|
{% for choice in field %}
|
||||||
|
<label class="switch my-1 block">
|
||||||
|
{{ choice.tag }}
|
||||||
|
<span>{{ choice.choice_label }}</span>
|
||||||
|
</label>
|
||||||
|
{% endfor %}
|
||||||
{% elif field|is_radio %}
|
{% elif field|is_radio %}
|
||||||
{% if field.auto_id %}
|
{% if field.auto_id %}
|
||||||
<label class="label my-1" for="{{field.auto_id}}">{{ field.label }}</label>
|
<label class="label my-1" for="{{field.auto_id}}">{{ field.label }}</label>
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class CoreConfig(AppConfig):
|
class CoreConfig(AppConfig):
|
||||||
name = "core"
|
name = "core"
|
||||||
|
|
||||||
|
# def ready(self):
|
||||||
|
# import core.rules
|
||||||
|
@ -7,10 +7,11 @@ from .models import Service
|
|||||||
class ServiceForm(forms.ModelForm):
|
class ServiceForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Service
|
model = Service
|
||||||
fields = ["name", "link", "origins"]
|
fields = ["name", "link", "origins", "collaborators"]
|
||||||
widgets = {
|
widgets = {
|
||||||
"name": forms.TextInput(),
|
"name": forms.TextInput(),
|
||||||
"origins": forms.TextInput(),
|
"origins": forms.TextInput(),
|
||||||
|
"collaborators": forms.CheckboxSelectMultiple(),
|
||||||
}
|
}
|
||||||
labels = {
|
labels = {
|
||||||
"origins": "Allowed Hostnames",
|
"origins": "Allowed Hostnames",
|
||||||
|
@ -10,13 +10,13 @@ def is_service_creator(user):
|
|||||||
|
|
||||||
|
|
||||||
@rules.predicate
|
@rules.predicate
|
||||||
def is_service_owner(service, user):
|
def is_service_owner(user, service):
|
||||||
return service.owner == user
|
return service.owner == user
|
||||||
|
|
||||||
|
|
||||||
@rules.predicate
|
@rules.predicate
|
||||||
def is_service_collaborator(service, user):
|
def is_service_collaborator(user, service):
|
||||||
return user in service.collaborators.all()
|
return service.collaborators.filter(pk=user.pk).exists()
|
||||||
|
|
||||||
|
|
||||||
rules.add_perm("core.view_service", is_service_owner | is_service_collaborator)
|
rules.add_perm("core.view_service", is_service_owner | is_service_collaborator)
|
||||||
|
@ -6,7 +6,7 @@ This message can be safely ignored if you did not request a password reset. Clic
|
|||||||
|
|
||||||
{{ password_reset_url }}
|
{{ password_reset_url }}
|
||||||
|
|
||||||
{% endif %}{% blocktrans with site_name=current_site.name site_domain=current_site.domain %}Thank you,
|
{% blocktrans with site_name=current_site.name site_domain=current_site.domain %}Thank you,
|
||||||
{{ site_name }}
|
{{ site_name }}
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
{% endautoescape %}
|
{% endautoescape %}
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
<form method="POST" action="{{ action_url }}" class="max-w-lg">
|
<form method="POST" action="{{ action_url }}" class="max-w-lg">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form|a17t }}
|
{{ form|a17t }}
|
||||||
<button type="submit" name="action" class="button ~urge !high">{% trans 'Change Password'}</button>
|
<button type="submit" name="action" class="button ~urge !high">{% trans 'Change Password' %}</button>
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>{% trans 'Your password is now changed.' %}</p>
|
<p>{% trans 'Your password is now changed.' %}</p>
|
||||||
|
@ -6,4 +6,5 @@
|
|||||||
|
|
||||||
{% block card %}
|
{% block card %}
|
||||||
<p>{% trans 'Your password is now changed.' %}</p>
|
<p>{% trans 'Your password is now changed.' %}</p>
|
||||||
|
<a href="{% url 'account_login' %}" class="button ~urge !high">Log In</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -48,19 +48,23 @@
|
|||||||
{% if can_create %}
|
{% if can_create %}
|
||||||
{% url 'core:service_create' as url %}
|
{% url 'core:service_create' as url %}
|
||||||
{% include 'core/includes/sidebar_portal.html' with label="+ Create" url=url %}
|
{% include 'core/includes/sidebar_portal.html' with label="+ Create" url=url %}
|
||||||
|
|
||||||
|
<hr class="sep h-8">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
{% if user.collaborating_services.all %}
|
{% if user.collaborating_services.all %}
|
||||||
<p class="ml-2 mt-8 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 'core:service' service.uuid as url %}
|
{% url 'core:service' service.uuid as url %}
|
||||||
{% include 'core/includes/sidebar_portal.html' with label=service.name url=url %}
|
{% include 'core/includes/sidebar_portal.html' with label=service.name url=url %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
<hr class="sep h-8">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<p class="ml-2 mt-8 mb-1 supra font-medium text-gray-500 pointer-events-none">Account</p>
|
<p class="ml-2 mb-1 supra font-medium text-gray-500 pointer-events-none">Account</p>
|
||||||
|
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
|
|
||||||
|
35
shynet/core/templates/core/includes/session_list.html
Normal file
35
shynet/core/templates/core/includes/session_list.html
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{% load humanize helpers %}
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<th>Session Start</th>
|
||||||
|
<th>Identity</th>
|
||||||
|
<th>Network</th>
|
||||||
|
<th class="rf">Duration</th>
|
||||||
|
<th class="rf">Hits</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for session in object_list %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="{% url 'core:service_session' object.pk session.pk %}" class="font-medium text-purple-700">
|
||||||
|
{{session.start_time|date:"M j Y, g:i a"|capfirst}}
|
||||||
|
{% if session.is_currently_active %}
|
||||||
|
<span class="badge ~positive">Online</span>
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if session.identifier %}
|
||||||
|
<span class="chip ~neutral">{{session.identifier}}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-gray-600">—</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{session.country|flag_emoji}} {{session.asn|default:"Unknown"}}</td>
|
||||||
|
<td class="rf">{{session.duration|naturaldelta}}</td>
|
||||||
|
<td class="rf">{{session.hit_set.count|intcomma}}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
@ -21,6 +21,6 @@
|
|||||||
{% for object in services %}
|
{% for object in services %}
|
||||||
{% include 'core/includes/service_overview.html' %}
|
{% include 'core/includes/service_overview.html' %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<p>You don't have any services.</p>
|
<p>You don't have any services on this Shynet instance yet.</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -178,6 +178,10 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="{% url 'core:service_session_list' service.uuid %}" class="button field w-auto">View individual sessions
|
<div class="card ~neutral !low">
|
||||||
|
{% include 'core/includes/session_list.html' %}
|
||||||
|
<hr class="sep h-8">
|
||||||
|
<a href="{% url 'core:service_session_list' service.uuid %}" class="button ~neutral w-auto">View more sessions
|
||||||
→</a>
|
→</a>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -11,39 +11,7 @@
|
|||||||
|
|
||||||
{% 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-scroll">
|
||||||
<table class="table">
|
{% include 'core/includes/session_list.html' %}
|
||||||
<thead>
|
|
||||||
<th>Time</th>
|
|
||||||
<th>Identity</th>
|
|
||||||
<th>Network</th>
|
|
||||||
<th class="rf">Duration</th>
|
|
||||||
<th class="rf">Hits</th>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for session in object_list %}
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="{% url 'core:service_session' object.pk session.pk %}" class="font-medium text-purple-700">
|
|
||||||
{{session.start_time|date:"M j Y, g:i a"|capfirst}}
|
|
||||||
{% if session.is_currently_active %}
|
|
||||||
<span class="badge ~positive">Online</span>
|
|
||||||
{% endif %}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{% if session.identifier %}
|
|
||||||
<span class="chip ~neutral">{{session.identifier}}</span>
|
|
||||||
{% else %}
|
|
||||||
<span class="text-gray-600">—</span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>{{session.country|flag_emoji}} {{session.asn|default:"Unknown"}}</td>
|
|
||||||
<td class="rf">{{session.duration|naturaldelta}}</td>
|
|
||||||
<td class="rf">{{session.hit_set.count|intcomma}}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
{% pagination page_obj request %}
|
{% pagination page_obj request %}
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -11,12 +11,16 @@
|
|||||||
{% block service_content %}
|
{% block service_content %}
|
||||||
<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><body></code> tag on any page you'd like to track.</p>
|
||||||
<div class="card ~neutral !high font-mono text-sm">
|
<div class="card ~neutral !high font-mono text-sm">
|
||||||
{% filter force_escape %}<noscript><img src="{{base_url}}/ingress/fc4008d3-f2fa-4500-9968-d96719e3819c/pixel.gif"></noscript><script src="{{base_url}}/ingress/fc4008d3-f2fa-4500-9968-d96719e3819c/identifier/script.js"></script>{% endfilter %}
|
{% filter force_escape %}<noscript><img
|
||||||
|
src="{{base_url}}/ingress/fc4008d3-f2fa-4500-9968-d96719e3819c/pixel.gif"></noscript>
|
||||||
|
<script src="{{base_url}}/ingress/fc4008d3-f2fa-4500-9968-d96719e3819c/identifier/script.js"></script>
|
||||||
|
{% endfilter %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<hr class="sep h-4">
|
||||||
<hr class="sep">
|
<h5>Settings</h5>
|
||||||
<form class="card ~neutral !low p-0 max-w-xl" method="POST">
|
<form class="card ~neutral !low p-0" method="POST">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
{{form|a17t}}
|
{{form|a17t}}
|
||||||
@ -30,5 +34,6 @@
|
|||||||
<a href="{% url 'core:service_delete' object.uuid %}" class="button ~critical !high">Delete</a>
|
<a href="{% url 'core:service_delete' object.uuid %}" class="button ~critical !high">Delete</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -1,9 +1,16 @@
|
|||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.shortcuts import get_object_or_404, reverse
|
from django.shortcuts import get_object_or_404, reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.views.generic import (CreateView, DeleteView, DetailView, ListView,
|
from django.views.generic import (
|
||||||
TemplateView, UpdateView)
|
CreateView,
|
||||||
|
DeleteView,
|
||||||
|
DetailView,
|
||||||
|
ListView,
|
||||||
|
TemplateView,
|
||||||
|
UpdateView,
|
||||||
|
)
|
||||||
from rules.contrib.views import PermissionRequiredMixin
|
from rules.contrib.views import PermissionRequiredMixin
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
from analytics.models import Session
|
from analytics.models import Session
|
||||||
|
|
||||||
@ -21,7 +28,9 @@ class DashboardView(LoginRequiredMixin, DateRangeMixin, TemplateView):
|
|||||||
|
|
||||||
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["services"] = Service.objects.filter(owner=self.request.user)
|
data["services"] = Service.objects.filter(
|
||||||
|
Q(owner=self.request.user) | Q(collaborators__in=[self.request.user])
|
||||||
|
)
|
||||||
for service in data["services"]:
|
for service in data["services"]:
|
||||||
service.stats = service.get_core_stats(data["start_date"], data["end_date"])
|
service.stats = service.get_core_stats(data["start_date"], data["end_date"])
|
||||||
return data
|
return data
|
||||||
@ -51,6 +60,11 @@ 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["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(
|
||||||
|
service=self.get_object(),
|
||||||
|
start_time__lt=self.get_end_date(),
|
||||||
|
start_time__gt=self.get_start_date(),
|
||||||
|
).order_by("-start_time")[:10]
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
@ -92,7 +106,7 @@ class ServiceSessionsListView(
|
|||||||
service=self.get_object(),
|
service=self.get_object(),
|
||||||
start_time__lt=self.get_end_date(),
|
start_time__lt=self.get_end_date(),
|
||||||
start_time__gt=self.get_start_date(),
|
start_time__gt=self.get_start_date(),
|
||||||
)
|
).order_by("-start_time")
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
data = super().get_context_data(**kwargs)
|
data = super().get_context_data(**kwargs)
|
||||||
|
@ -26,9 +26,9 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|||||||
SECRET_KEY = "q@@928+gjkhmcdpuwse0awn@#ygm#0etg11jlny+b*^cm5m-x!"
|
SECRET_KEY = "q@@928+gjkhmcdpuwse0awn@#ygm#0etg11jlny+b*^cm5m-x!"
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = os.getenv("DEBUG", "True") == "True"
|
||||||
|
|
||||||
ALLOWED_HOSTS = []
|
ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "*").split(",")
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
@ -42,7 +42,7 @@ INSTALLED_APPS = [
|
|||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
"django.contrib.sites",
|
"django.contrib.sites",
|
||||||
"django.contrib.humanize",
|
"django.contrib.humanize",
|
||||||
"rules",
|
"rules.apps.AutodiscoverRulesConfig",
|
||||||
"a17t",
|
"a17t",
|
||||||
"core",
|
"core",
|
||||||
"analytics",
|
"analytics",
|
||||||
@ -168,6 +168,24 @@ MESSAGE_TAGS = {
|
|||||||
messages.SUCCESS: "~positive",
|
messages.SUCCESS: "~positive",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Email
|
||||||
|
|
||||||
|
SERVER_EMAIL = os.getenv("SERVER_EMAIL", "Shynet <noreply@shynet.example.com>")
|
||||||
|
DEFAULT_FROM_EMAIL = SERVER_EMAIL
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||||
|
else:
|
||||||
|
EMAIL_HOST = os.environ.get("EMAIL_HOST")
|
||||||
|
EMAIL_PORT = int(os.environ.get("EMAIL_PORT", 465))
|
||||||
|
EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER")
|
||||||
|
EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD")
|
||||||
|
EMAIL_USE_SSL = True
|
||||||
|
|
||||||
# Shynet
|
# Shynet
|
||||||
|
|
||||||
ONLY_SUPERUSERS_CREATE = True # Can everyone create services, or only superusers?
|
ONLY_SUPERUSERS_CREATE = True
|
||||||
|
# Can everyone create services, or only superusers?
|
||||||
|
# Note that in the current version of Shynet, being able to edit a service allows
|
||||||
|
# you to see every registered user on the Shynet instance. This will be changed in
|
||||||
|
# a future version.
|
||||||
|
Loading…
Reference in New Issue
Block a user