Compare commits

..

3 Commits

Author SHA1 Message Date
R. Miles McCain
bca3faa597 Remove tests directory 2021-11-13 21:09:09 -08:00
R. Miles McCain
822f7fb74c Code cleanup 2021-11-13 21:08:08 -08:00
Sergio
a47edbfa03 Add factories and first dashboard tests 2021-09-19 16:26:05 +03:00
38 changed files with 405 additions and 1326 deletions

View File

@@ -9,11 +9,6 @@ jobs:
publish_to_docker_hub:
runs-on: ubuntu-latest
steps:
- name: Set swap space
uses: pierotofy/set-swap-space@master
with:
swap-size-gb: 5
- name: Checkout code
uses: actions/checkout@v2
@@ -43,6 +38,6 @@ jobs:
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: ${{ steps.prep.outputs.tags }}

View File

@@ -9,11 +9,6 @@ jobs:
publish_to_docker_hub:
runs-on: ubuntu-latest
steps:
- name: Set swap space
uses: pierotofy/set-swap-space@master
with:
swap-size-gb: 5
- name: Checkout code
uses: actions/checkout@v2
@@ -43,6 +38,6 @@ jobs:
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: ${{ steps.prep.outputs.tags }}

View File

@@ -9,11 +9,6 @@ jobs:
publish_to_docker_hub:
runs-on: ubuntu-latest
steps:
- name: Set swap space
uses: pierotofy/set-swap-space@master
with:
swap-size-gb: 5
- name: Checkout code
uses: actions/checkout@v2
@@ -44,6 +39,6 @@ jobs:
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: ${{ steps.prep.outputs.tags }}

View File

@@ -1,37 +0,0 @@
name: Run tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
db:
image: postgres:12.3-alpine
env:
POSTGRES_USER: shynet_db_user
POSTGRES_PASSWORD: shynet_db_user_password
POSTGRES_DB: shynet_db
ports:
- 5432:5432
strategy:
max-parallel: 4
matrix:
python-version: [3.9]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Run image
uses: abatilo/actions-poetry@v2.0.0
with:
poetry-version: 1.1.6
- name: Install dependencies
run: poetry install
- name: Django Testing project
run: |
cp TEMPLATE.env .env
poetry run ./shynet/manage.py test

View File

@@ -1,15 +0,0 @@
# Contributing
This document provides an overview of how to contribute to Shynet. Currently, it focuses on the more technical elements of contributing --- for example, setting up your development environment. Eventually, we will expand this guide to cover the social and governance oriented side of contributing as well.
## Setting up your development environment
To contribute to Shynet, you must have a reliable development environment. Because Shynet is intended to be run inside containers, we strongly encourage you to run Shynet in a container in development as well. The development setup described in this guide will use Docker and Docker Compose.
To begin, clone the Shynet repository to your computer, and ensure that you have Docker and Docker Compose installed.
Copy `TEMPLATE.env` to a new file called `.env`. This `.env` file will be used in your development environment. Paste `DEBUG=True` into the end of your new `.env` file so that Shynet will know to run in development mode.
Finally, follow the steps in [GUIDE.md](GUIDE.md) on setting up a Shynet instance with Docker Compose. This is where you'll setup an admin user.
_Did you have to perform additional steps to setup your environment? Document them here and submit a pull request!_

View File

@@ -47,5 +47,4 @@ RUN python manage.py collectstatic --noinput && \
# Launch
USER appuser
EXPOSE 8080
HEALTHCHECK CMD bash -c 'wget -o /dev/null -O /dev/null --header "Host: ${ALLOWED_HOSTS%%,*}" "http://127.0.0.1:$PORT/healthz/?format=json"'
CMD [ "./entrypoint.sh" ]

View File

@@ -7,6 +7,7 @@
- [Render](#render)
- [Updating Your Configuration](#updating-your-configuration)
- [Advanced Usage](#advanced-usage)
* [Installation with SSL](#installation-with-ssl)
* [Configuring a Reverse Proxy](#configuring-a-reverse-proxy)
+ [Cloudflare](#cloudflare)
+ [Nginx](#nginx)
@@ -22,7 +23,7 @@
## Installation
Installation of Shynet is easy! Follow the [Basic Installation](#basic-installation) guide or the [Basic Installation with Docker Compose](#basic-installation-with-docker-compose) below for a minimal installation, or if you are going to be running Shynet over HTTPS through a reverse proxy.
Installation of Shynet is easy! Follow the [Basic Installation](#basic-installation) guide or the [Basic Installation with Docker Compose](#basic-installation-with-docker-compose) below for a minimal installation, or if you are going to be running Shynet over HTTPS through a reverse proxy. If you'd like to run Shynet over HTTPS without a reverse proxy, skip ahead to [Installation with SSL](#installation-with-ssl) instead.
> **These commands assume Ubuntu.** If you're installing Shynet on a different platform, the process will be different.
@@ -34,7 +35,7 @@ Before continuing, please be sure to have the latest version of Docker installed
2. Have a PostgreSQL server ready to go. This can be on the same machine as the deployment, or elsewhere. You'll just need a username, password, host, and port. (For info on how to setup a PostgreSQL server on Ubuntu, follow [this guide](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-postgresql-on-ubuntu-18-04)).
3. Configure an environment file for Shynet, using [this file](/TEMPLATE.env) as a template. (This file is typically named `.env`.) Make sure you set the database settings, or Shynet won't be able to run. Also consider setting `ALLOWED_HOSTS` inside the environment file to your deployment's domain for better security.
3. Configure an environment file for Shynet, using [this file](/TEMPLATE.env) as a template. (This file is typically named `.env`.) Make sure you set the database settings, or Shynet won't be able to run.
4. Launch the Shynet server for the first time by running `docker run --env-file=<your env file> milesmcc/shynet:latest`. Provided you're using the default environment information (i.e., `PERFORM_CHECKS_AND_SETUP` is `True`), you'll see a few warnings about not having an admin user or host setup; these are normal. Don't worry — we'll do this in the next step. You only need to stop if you see a stacktrace about being unable to connect to the database.
@@ -55,7 +56,7 @@ Before continuing, please be sure to have the latest version of Docker installed
1. Clone the repository.
2. Using [TEMPLATE.env](/TEMPLATE.env) as a template, configure the environment for your Shynet instance and place the modified config in a file called `.env` in the root of the repository. Do _not_ change the port number at the end; you can set the public facing port in the next step.
2. Using [TEMPLATE.env](/TEMPLATE.env) as a template, confiure the environment for your Shynet instance and place the modified config in a file called `.env` in the root of the repository. Do _not_ change the port number at the end; you can set the public facing port in the next step.
3. On line 2 of the `nginx.conf` file located in the root of the repository, replace `example.com` with your hostname. Then, in the `docker-compose.yml` file, set the port number by replacing `8080` in line 38 ( `- 8080:80` ) with whatever local port you want to bind it to. For example, set the port number to `- 80:80` if you want your site will be available via HTTP (port 80) at `http://<your hostname>`.
@@ -95,6 +96,40 @@ See the [Render docs](https://render.com/docs/deploy-shynet) for more informatio
## Advanced Usage
### Installation with SSL
If you are going to be running Shynet through a reverse proxy, please see [Configuring a Reverse Proxy](#configuring-a-reverse-proxy) instead.
0. We'll be cloning this into the home directory to make this installation easier, so run `cd ~/` if you need to.
1. Instead of pulling from Docker, we will be pulling from GitHub and building using Docker in order to easily add SSL certificates. You will want to run `git clone https://github.com/milesmcc/shynet.git` to clone the GitHub repo to your current working directory.
2. To install `certbot` follow [the guide here](https://certbot.eff.org/instructions) or follow along below
* Ubuntu 18.04
* `sudo apt-get update`
* `sudo apt-get install software-properties-common`
* `sudo add-apt-repository universe`
* `sudo add-apt-repository ppa:certbot/certbot`
* `sudo apt-get update`
* `sudo apt-get install certbot`
3. Run `sudo certbot certonly --standalone` and follow the instructions to generate your SSL certificate.
* If you registering the certificate to a domain name like `example.com`, please be sure to point your DNS records to your current server before running `certbot`.
4. We are going to move the SSL certificates to the Shynet repo with with command below. Replace `<domain>` with the domain name you used in step 3.
* `cp /etc/letsencrypt/live/<domain>/{cert,privkey}.pem ~/shynet/shynet/`
5. With that, we are going to replace the `webserver.sh` with `ssl.webserver.sh` to enable the use of SSL certificates. The original `webserver.sh` will be backed up to `backup.webserver.sh`
* `mv ~/shynet/shynet/webserver.sh ~/shynet/shynet/backup.webserver.sh`
* `mv ~/shynet/shynet/ssl.webserver.sh ~/shynet/shynet/webserver.sh`
6. Now we build the image!
* `docker image build shynet -t shynet-ssl:latest`
7. Have a PostgreSQL server ready to go. This can be on the same machine as the deployment, or elsewhere. You'll just need a username, password, host, and port (default is `5432`). (For info on how to setup a PostgreSQL server on Ubuntu, follow [this guide](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-postgresql-on-ubuntu-18-04)).
8. Follow the [Basic Installation](#basic-installation) guide with just one modification: in step #4, change the local bind port from `80` to `443`, and use `shynet-ssl:latest` as your Docker image instead of `milesmcc/shynet:latest`.
### Configuring a Reverse Proxy
A reverse proxy has many benefits. It can be used for DDoS protection, caching files to reduce server load, routing HTTPS and/or HTTP connections, hosting multiple services on a single server, [and more](https://www.cloudflare.com/learning/cdn/glossary/reverse-proxy/)!
@@ -208,22 +243,6 @@ In a single-page application, the page never reloads. (That's the entire point o
Fortunately, Shynet offers a simple method you can call from anywhere within your JavaScript to indicate that a new page has been loaded: `Shynet.newPageLoad()`. Add this method call to the code that handles routing in your app, and you'll be ready to go.
### API
All the information displayed on the dashboard can be obtained via API on url ```//shynet.example.com/api/v1/dashboard/```. By default this endpoint will return the full data from all services over the last last 30 days. The `Authentication` header should be set to use user's parsonal API token (```'Authorization: Token <user API token>'```).
There are 3 optional query parameters:
* `uuid` - to get data only from one service
* `startDate` - to set start date in format YYYY-MM-DD
* `endDate` - to set end date in format YYYY-MM-DD
Example in HTTPie:
```http get '//shynet.example.com/api/v1/dashboard/?uuid={{service_uuid}}&startDate=2021-01-01&endDate=2050-01-01' 'Authorization:Token {{user_api_token}}'```
Example in cURL:
```curl -H 'Authorization:Token {{user_api_token}}' '//shynet.example.com/api/v1/dashboard/?uuid={{service_uuid}}&startDate=2021-01-01&endDate=2050-01-01'```
---
## Troubleshooting

View File

@@ -8,7 +8,7 @@
<br>
<strong><a href="#installation">Getting started »</a></strong>
</p>
<p align="center"><a href="#screenshots">Screenshots</a> &bull; <a href="#features">Features</a> &bull; <a href="https://miles.land/officehours/">Office Hours</a></p>
<p align="center"><a href="#screenshots">Screenshots</a> &bull; <a href="#features">Features</a> &bull; <a href="https://github.com/milesmcc/a17t">Design</a></p>
</p>
<br>

View File

@@ -17,7 +17,7 @@ spec:
spec:
containers:
- name: "shynet-webserver"
image: "milesmcc/shynet:edge" # Change to the version appropriate for you (e.g., :latest)
image: "milesmcc/shynet:latest"
imagePullPolicy: Always
envFrom:
- secretRef:
@@ -42,7 +42,7 @@ spec:
spec:
containers:
- name: "shynet-celeryworker"
image: "milesmcc/shynet:edge" # Change to the version appropriate for you (e.g., :latest)
image: "milesmcc/shynet:latest"
command: ["./celeryworker.sh"]
imagePullPolicy: Always
envFrom:
@@ -95,7 +95,7 @@ spec:
selector:
app: shynet-webserver
---
apiVersion: networking.k8s.io/v1
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: shynet-webserver-ingress

19
package-lock.json generated
View File

@@ -4,7 +4,6 @@
"requires": true,
"packages": {
"": {
"name": "shynet",
"license": "Apache-2.0",
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.1",
@@ -161,7 +160,8 @@
"esprima": "^3.1.3",
"estraverse": "^4.2.0",
"esutils": "^2.0.2",
"optionator": "^0.8.1"
"optionator": "^0.8.1",
"source-map": "~0.6.1"
},
"bin": {
"escodegen": "bin/escodegen.js",
@@ -367,9 +367,9 @@
}
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
},
"node_modules/prelude-ls": {
"version": "1.1.2",
@@ -480,7 +480,8 @@
"esprima": "^4.0.1",
"estraverse": "^4.2.0",
"esutils": "^2.0.2",
"optionator": "^0.8.1"
"optionator": "^0.8.1",
"source-map": "~0.6.1"
},
"bin": {
"escodegen": "bin/escodegen.js",
@@ -992,9 +993,9 @@
}
},
"path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
},
"prelude-ls": {
"version": "1.1.2",

1299
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -17,7 +17,7 @@ PyYAML = "^5.4.1"
user-agents = "^2.2.0"
rules = "^3.0"
gunicorn = "^20.1.0"
psycopg2-binary = "^2.9.2"
psycopg2-binary = "^2.9.1"
redis = "^3.5.3"
django-redis-cache = "^3.0.0"
pycountry = "^20.7.3"
@@ -26,15 +26,6 @@ django-health-check = "^3.16.4"
django-npm = "^1.0.0"
python-dotenv = "^0.18.0"
django-debug-toolbar = "^3.2.1"
django-cors-headers = "^3.11.0"
[tool.poetry.dev-dependencies]
pytest-sugar = "^0.9.4"
factory-boy = "^3.2.0"
pytest-django = "^4.4.0"
django-coverage-plugin = "^2.0.0"
django-stubs = "^1.8.0"
mypy = "^0.910"
[build-system]
requires = ["poetry-core>=1.0.0"]

View File

@@ -1,23 +1,23 @@
<nav class="flex w-full flex-wrap items-center justify-between" role="navigation" aria-label="pagination">
<div class="w-full md:w-auto mb-2">
{% if page.has_previous %}
<a href="?page={{ page.previous_page_number }}&{{url_parameters}}" class="button field !low bg-neutral-000 w-auto mr-1">Previous</a>
<a href="?page={{ page.previous_page_number }}&{{url_parameters}}" class="button field bg-neutral-000 w-auto mr-1">Previous</a>
{% else %}
<a class="button field !low bg-neutral-000 w-auto mr-1" disabled>Previous</a>
<a class="button field bg-neutral-000 w-auto mr-1" disabled>Previous</a>
{% endif %}
{% if page.has_next %}
<a href="?page={{ page.next_page_number }}&{{url_parameters}}" class="button field !low bg-neutral-000 w-auto">Next</a>
<a href="?page={{ page.next_page_number }}&{{url_parameters}}" class="button field bg-neutral-000 w-auto">Next</a>
{% else %}
<a class="button field !low bg-neutral-000 w-auto" disabled>Next</a>
<a class="button field bg-neutral-000 w-auto" disabled>Next</a>
{% endif %}
</div>
<ul class="pagination-list w-full md:w-auto mb-2 flex">
{% for pnum in begin %}
{% ifequal page.number pnum %}
<li><a class="button field !low w-auto mx-1 text-white bg-neutral-700">{{ pnum }}</a></li>
<li><a class="button field w-auto mx-1 text-white bg-neutral-700">{{ pnum }}</a></li>
{% else %}
<li><a class="button field !low bg-neutral-000 w-auto mx-1" href="?page={{ pnum }}&{{url_parameters}}">{{ pnum }}</a></li>
<li><a class="button field bg-neutral-000 w-auto mx-1" href="?page={{ pnum }}&{{url_parameters}}">{{ pnum }}</a></li>
{% endifequal %}
{% endfor %}
@@ -25,9 +25,9 @@
<li><span class="pagination-ellipsis">&hellip;</span></li>
{% for pnum in middle %}
{% ifequal page.number pnum %}
<li><a class="button field !low w-auto mx-1 text-white bg-neutral-700">{{ pnum }}</a></li>
<li><a class="button field w-auto mx-1 text-white bg-neutral-700">{{ pnum }}</a></li>
{% else %}
<li><a class="button field !low bg-neutral-000 w-auto mx-1" href="?page={{ pnum }}&{{url_parameters}}">{{ pnum }}</a></li>
<li><a class="button field bg-neutral-000 w-auto mx-1" href="?page={{ pnum }}&{{url_parameters}}">{{ pnum }}</a></li>
{% endifequal %}
{% endfor %}
{% endif %}
@@ -36,9 +36,9 @@
<li><span class="pagination-ellipsis">&hellip;</span></li>
{% for pnum in end %}
{% ifequal page.number pnum %}
<li><a class="button field !low w-auto mx-1 text-white bg-neutral-700">{{ pnum }}</a></li>
<li><a class="button field w-auto mx-1 text-white bg-neutral-700">{{ pnum }}</a></li>
{% else %}
<li><a class="button field !low bg-neutral-000 w-auto mx-1" href="?page={{ pnum }}&{{url_parameters}}">{{ pnum }}</a></li>
<li><a class="button field bg-neutral-000 w-auto mx-1" href="?page={{ pnum }}&{{url_parameters}}">{{ pnum }}</a></li>
{% endifequal %}
{% endfor %}
{% endif %}

View File

View File

@@ -1 +0,0 @@
# from django.contrib import admin

View File

@@ -1,6 +0,0 @@
from django.apps import AppConfig
class ApiConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'api'

View File

@@ -1,23 +0,0 @@
from django.http import JsonResponse
from django.contrib.auth.models import AnonymousUser
from core.models import User
class ApiTokenRequiredMixin:
def _get_user_by_token(self, request):
token = request.headers.get('Authorization')
if not token or not token.startswith('Token '):
return AnonymousUser()
token = token.split(' ')[1]
user = User.objects.filter(api_token=token).first()
return user if user else AnonymousUser()
def dispatch(self, request, *args, **kwargs):
request.user = self._get_user_by_token(request)
if not request.user.is_authenticated:
return JsonResponse(data={}, status=403)
return super().dispatch(request, *args, **kwargs)

View File

@@ -1 +0,0 @@
# from django.db import models

View File

@@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -1,7 +0,0 @@
from django.urls import path
from . import views
urlpatterns = [
path("dashboard/", views.DashboardApiView.as_view(), name="services"),
]

View File

@@ -1,60 +0,0 @@
import uuid
from django.http import JsonResponse
from django.db.models import Q
from django.db.models.query import QuerySet
from django.views.generic import View
from dashboard.mixins import DateRangeMixin
from core.models import Service
from .mixins import ApiTokenRequiredMixin
def is_valid_uuid(value):
try:
uuid.UUID(value)
return True
except ValueError:
return False
class DashboardApiView(ApiTokenRequiredMixin, DateRangeMixin, View):
def get(self, request, *args, **kwargs):
services = Service.objects.filter(
Q(owner=request.user) | Q(collaborators__in=[request.user])
).distinct()
uuid = request.GET.get('uuid')
if uuid and is_valid_uuid(uuid):
services = services.filter(uuid=uuid)
try:
start = self.get_start_date()
end = self.get_end_date()
except ValueError:
return JsonResponse(status=400, data={'error': 'Invalid date format'})
services_data = [
{
'name': s.name,
'uuid': s.uuid,
'link': s.link,
'stats': s.get_core_stats(start, end),
}
for s in services
]
services_data = self._convert_querysets_to_lists(services_data)
return JsonResponse(data={'services': services_data})
def _convert_querysets_to_lists(self, services_data):
for service_data in services_data:
for key, value in service_data['stats'].items():
if isinstance(value, QuerySet):
service_data['stats'][key] = list(value)
for key, value in service_data['stats']['compare'].items():
if isinstance(value, QuerySet):
service_data['stats']['compare'][key] = list(value)
return services_data

View File

@@ -1,24 +0,0 @@
# Generated by Django 3.2.5 on 2021-11-17 07:17
import core.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0008_auto_20200628_1403'),
]
operations = [
migrations.AddField(
model_name='user',
name='api_token',
field=models.TextField(default=core.models._default_api_token, unique=True),
),
migrations.AlterField(
model_name='user',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
]

View File

@@ -1,9 +1,8 @@
import ipaddress
import json
import re
import uuid
from secrets import token_urlsafe
from django.apps import apps
from django.conf import settings
from django.contrib.auth.models import AbstractUser
@@ -44,14 +43,9 @@ def _parse_network_list(networks: str):
return [ipaddress.ip_network(network.strip()) for network in networks.split(",")]
def _default_api_token():
return token_urlsafe(32)
class User(AbstractUser):
username = models.TextField(default=_default_uuid, unique=True)
email = models.EmailField(unique=True)
api_token = models.TextField(default=_default_api_token, unique=True)
def __str__(self):
return self.email
@@ -209,7 +203,6 @@ class Service(models.Model):
chart_data, chart_tooltip_format, chart_granularity = self._get_chart_data(
sessions, hits, start_time, end_time, tz_now
)
return {
"currently_online": currently_online,
"session_count": session_count,

View File

@@ -45,6 +45,11 @@ class DateRangeMixin:
"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),

View File

@@ -2,8 +2,8 @@
{% load i18n a17t_tags %}
{% block head_title %}{% trans "Change authentication info" %}{% endblock %}
{% block page_title %}{% trans "Change authentication info" %}{% endblock %}
{% block head_title %}{% trans "Change Password" %}{% endblock %}
{% block page_title %}{% trans "Change Password" %}{% endblock %}
{% block card %}
<form method="POST" action="{% url 'account_change_password' %}" class="password_change max-w-lg">
@@ -11,17 +11,4 @@
{{ form|a17t }}
<button type="submit" name="action" class="button ~urge !high">{% trans "Change Password" %}</button>
</form>
<hr class="sep">
<div>
<p class="label mb-1">Personal API token</p>
<div class="flex justify-between">
<span class='chip ~info !normal'>{{request.user.api_token}}</span>
<form method="POST" action="{% url 'dashboard:api_token_refresh' %}">
{% csrf_token %}
<button type="submit" name="action" class="button ~neutral @high">{% trans "Refresh token" %}</button>
</form>
</div>
<p class="support mt-1">To learn more about the API, see our <a href="https://github.com/milesmcc/shynet/blob/master/GUIDE.md#api">API guide</a>.</p>
</div>
</div>
{% endblock %}

View File

@@ -2,7 +2,7 @@
<input type="hidden" name="startDate" value="{{start_date.isoformat}}" id="startDate">
<input type="hidden" name="endDate" value="{{end_date.isoformat}}" id="endDate">
</form>
<input type="input" id="rangePicker" placeholder="Date range" class="input ~neutral !low bg-neutral-000 cursor-pointer" style="max-width: 200px;" readonly>
<input type="input" id="rangePicker" placeholder="Date range" class="input ~neutral bg-neutral-000 cursor-pointer" style="max-width: 200px;" readonly>
<style>
:root {
--litepicker-button-prev-month-color-hover: var(--color-urge);

View File

@@ -13,7 +13,7 @@
</div>
{% has_perm "core.create_service" user as can_create %}
{% if can_create %}
<a href="{% url 'dashboard:service_create' %}" class="button field !low bg-neutral-000 w-auto">+ New Service</a>
<a href="{% url 'dashboard:service_create' %}" class="button field bg-neutral-000 w-auto">+ New Service</a>
{% endif %}
</div>
</div>

View File

@@ -6,7 +6,7 @@
<div class="mr-2">{% include 'dashboard/includes/date_range.html' %}</div>
{% has_perm 'core.change_service' user object as can_update %}
{% if can_update %}
<a href="{% contextual_url 'dashboard:service_update' service.uuid %}" class="button field !low 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 %}
{% endblock %}

View File

@@ -5,7 +5,7 @@
{% block head_title %}{{object.name}} Session{% endblock %}
{% block service_actions %}
<a href="{% contextual_url 'dashboard:service' object.uuid %}" class="button field !low 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 %}
{% block service_content %}

View File

@@ -6,7 +6,7 @@
{% block service_actions %}
<div class="mr-2">{% include 'dashboard/includes/date_range.html' %}</div>
<a href="{% contextual_url 'dashboard:service' object.uuid %}" class="button field ~neutral !low 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 %}
{% block service_content %}

View File

@@ -5,7 +5,7 @@
{% block head_title %}{{object.name}} Management{% endblock %}
{% block service_actions %}
<a href="{% url 'dashboard:service' object.uuid %}" class="button field !low bg-neutral-000 w-auto">View &rarr;</a>
<a href="{% url 'dashboard:service' object.uuid %}" class="button field bg-neutral-000 w-auto">View &rarr;</a>
{% endblock %}
{% block service_content %}
@@ -30,20 +30,5 @@
</div>
</div>
</form>
<hr class="sep h-4">
<h5>API</h5>
<div class="card ~neutral !low content">
<p>Shynet provides a simple API that you can use to pull data programmatically. You can access this data via this URL:</p>
<code class="text-sm">{{script_protocol}}{{request.get_host}}{% url 'api:services' %}?uuid={{object.uuid}}</code>
<p>
There are 2 optional query parameters:
<ul>
<li><code class="text-sm">startDate</code> &mdash; to set the start date (in format YYYY-MM-DD)</li>
<li><code class="text-sm">endDate</code> &mdash; to set the end date (in format YYYY-MM-DD)</li>
</ul>
</p>
<p>Example using cURL:</p>
<code class="text-sm">curl -H 'Authorization: Token {{request.user.api_token}}' '{{script_protocol}}{{request.get_host}}{% url 'api:services' %}?uuid={{object.uuid}}&startDate=2021-01-01&endDate=2050-01-01'</code>
</div>
</div>
{% endblock %}

View File

@@ -1,4 +1,6 @@
from django.urls import path
from django.contrib import admin
from django.urls import include, path
from django.views.generic import RedirectView
from . import views
@@ -26,9 +28,4 @@ urlpatterns = [
views.ServiceSessionView.as_view(),
name="service_session",
),
path(
"api-token-refresh/",
views.RefreshApiTokenView.as_view(),
name="api_token_refresh",
),
]

View File

@@ -3,7 +3,8 @@ from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin
from django.core.cache import cache
from django.db.models import Q
from django.shortcuts import get_object_or_404, reverse, redirect
from django.shortcuts import get_object_or_404, reverse
from django.utils import timezone
from django.views.generic import (
CreateView,
DeleteView,
@@ -11,12 +12,11 @@ from django.views.generic import (
ListView,
TemplateView,
UpdateView,
View,
)
from rules.contrib.views import PermissionRequiredMixin
from analytics.models import Session
from core.models import Service, _default_api_token
from core.models import Service
from .forms import ServiceForm
from .mixins import DateRangeMixin
@@ -155,10 +155,3 @@ class ServiceSessionView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
data = super().get_context_data(**kwargs)
data["object"] = get_object_or_404(Service, pk=self.kwargs.get("pk"))
return data
class RefreshApiTokenView(LoginRequiredMixin, View):
def post(self, request):
request.user.api_token = _default_api_token()
request.user.save()
return redirect('account_change_password')

View File

@@ -22,7 +22,7 @@ from django.contrib.messages import constants as messages
load_dotenv()
# Increment on new releases
VERSION = "0.12.0"
VERSION = "0.11.0"
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -59,19 +59,16 @@ INSTALLED_APPS = [
"core",
"dashboard.apps.DashboardConfig",
"analytics",
"api",
"allauth",
"allauth.account",
"allauth.socialaccount",
"debug_toolbar",
"corsheaders",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"corsheaders.middleware.CorsMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
@@ -373,6 +370,3 @@ DASHBOARD_PAGE_SIZE = int(os.getenv("DASHBOARD_PAGE_SIZE", "5"))
USE_RELATIVE_MAX_IN_BAR_VISUALIZATION = (
os.getenv("USE_RELATIVE_MAX_IN_BAR_VISUALIZATION", "True") == "True"
)
CORS_ALLOW_ALL_ORIGINS = True
CORS_ALLOW_METHODS = ["GET", "OPTIONS"]

View File

@@ -25,5 +25,4 @@ urlpatterns = [
path("dashboard/", include(("dashboard.urls", "dashboard"), namespace="dashboard")),
path("healthz/", include("health_check.urls")),
path("", include(("core.urls", "core"), namespace="core")),
path("api/v1/", include(("api.urls", "api"), namespace="api")),
]

View File

@@ -1,16 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>JS test</title>
</head>
<body>
<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>
</html>

View File

@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Pixel test</title>
</head>
<body>
<img src="http://localhost:8000/ingress/9b2c4e2f-8d29-4418-82d4-b68e06795025/pixel.gif">
</body>
</html>