Merge branch 'main' into feature/90-implement-public-website-for-device
This commit is contained in:
commit
84b3579851
|
@ -0,0 +1,2 @@
|
||||||
|
DOMAIN=localhost
|
||||||
|
DEMO=false
|
19
README.md
19
README.md
|
@ -1,21 +1,20 @@
|
||||||
# INSTALACION:
|
# INSTALACIÓN:
|
||||||
|
|
||||||
la instalacion es muy estandar
|
La instalación es muy estándar
|
||||||
|
|
||||||
```
|
```
|
||||||
python -m venv env
|
python -m venv env
|
||||||
source env/bin/actevate
|
source env/bin/actevate
|
||||||
python install -r requirements.txt
|
python install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
## IMPORTANT EXTERNAL DEPENDETS
|
## IMPORTANT EXTERNAL DEPENDENCIES
|
||||||
|
|
||||||
Para arrancarlo es necesario tener el paquete xapian-bindings en tu ordenador. No se instala por pip asi que depende de cada sistema operativo:
|
Para arrancarlo es necesario tener el paquete `xapian-bindings` en tu ordenador. No se instala mediante `pip`, así que depende de cada [sistema operativo](https://xapian.org/download).
|
||||||
https://xapian.org/download
|
|
||||||
|
|
||||||
Luego solo necesitas:
|
Luego solo necesitas:
|
||||||
|
|
||||||
```
|
```
|
||||||
./manage.py migrate
|
./manage.py migrate
|
||||||
./manage.py runserver
|
./manage.py runserver
|
||||||
```
|
```
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
|
@ -0,0 +1,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class AdminConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "admin"
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
|
@ -0,0 +1,19 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h3>{{ subtitle }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<a href="{% url 'admin:institution' user.institution.pk %}" class="btn btn-green-admin">
|
||||||
|
{% translate "Institution" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,39 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h3>{{ subtitle }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<a href="{% url 'admin:new_user' %}" class="btn btn-green-admin">{% translate "Add new user" %}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Email</th>
|
||||||
|
<th>is Admin</th>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
{% for u in users %}
|
||||||
|
<td>{{ u.email }}</td>
|
||||||
|
<td>{{ u.is_admin }}</td>
|
||||||
|
<td><a href="{% url 'admin:edit_user' u.pk %}"><i class="bi bi-eye"></i></td>
|
||||||
|
<td><a href="{% url 'admin:delete_user' u.pk %}" class="text-danger" title="Remove"><i class="bi bi-trash"></i></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,38 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h3>{{ subtitle }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% load django_bootstrap5 %}
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col">
|
||||||
|
Are you sure than want remove the lot {{ object.name }} with {{ object.devices.count }} devices.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form role="form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% if form.errors %}
|
||||||
|
<div class="alert alert-danger alert-icon alert-icon-border alert-dismissible" role="alert">
|
||||||
|
<div class="icon"><span class="mdi mdi-close-circle-o"></span></div>
|
||||||
|
<div class="message">
|
||||||
|
{% for field, error in form.errors.items %}
|
||||||
|
{{ error }}<br />
|
||||||
|
{% endfor %}
|
||||||
|
<button class="btn-close" type="button" data-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
<div class="form-actions-no-box">
|
||||||
|
<a class="btn btn-grey" href="{% url 'admin:users' %}">{% translate "Cancel" %}</a>
|
||||||
|
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Delete' %}" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,32 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h3>{{ subtitle }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% load django_bootstrap5 %}
|
||||||
|
<form role="form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% if form.errors %}
|
||||||
|
<div class="alert alert-danger alert-icon alert-icon-border alert-dismissible" role="alert">
|
||||||
|
<div class="icon"><span class="mdi mdi-close-circle-o"></span></div>
|
||||||
|
<div class="message">
|
||||||
|
{% for field, error in form.errors.items %}
|
||||||
|
{{ error }}<br />
|
||||||
|
{% endfor %}
|
||||||
|
<button class="btn-close" type="button" data-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
<div class="form-actions-no-box">
|
||||||
|
<a class="btn btn-grey" href="{% url 'admin:panel' %}">{% translate "Cancel" %}</a>
|
||||||
|
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,32 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h3>{{ subtitle }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% load django_bootstrap5 %}
|
||||||
|
<form role="form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% if form.errors %}
|
||||||
|
<div class="alert alert-danger alert-icon alert-icon-border alert-dismissible" role="alert">
|
||||||
|
<div class="icon"><span class="mdi mdi-close-circle-o"></span></div>
|
||||||
|
<div class="message">
|
||||||
|
{% for field, error in form.errors.items %}
|
||||||
|
{{ error }}<br />
|
||||||
|
{% endfor %}
|
||||||
|
<button class="btn-close" type="button" data-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
<div class="form-actions-no-box">
|
||||||
|
<a class="btn btn-grey" href="{% url 'admin:users' %}">{% translate "Cancel" %}</a>
|
||||||
|
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
|
@ -0,0 +1,13 @@
|
||||||
|
from django.urls import path
|
||||||
|
from admin import views
|
||||||
|
|
||||||
|
app_name = 'admin'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("panel/", views.PanelView.as_view(), name="panel"),
|
||||||
|
path("users/", views.UsersView.as_view(), name="users"),
|
||||||
|
path("users/new", views.CreateUserView.as_view(), name="new_user"),
|
||||||
|
path("users/edit/<int:pk>", views.EditUserView.as_view(), name="edit_user"),
|
||||||
|
path("users/delete/<int:pk>", views.DeleteUserView.as_view(), name="delete_user"),
|
||||||
|
path("institution/<int:pk>", views.InstitutionView.as_view(), name="institution"),
|
||||||
|
]
|
|
@ -0,0 +1,126 @@
|
||||||
|
from smtplib import SMTPException
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.views.generic.base import TemplateView
|
||||||
|
from django.views.generic.edit import (
|
||||||
|
CreateView,
|
||||||
|
UpdateView,
|
||||||
|
DeleteView,
|
||||||
|
)
|
||||||
|
from dashboard.mixins import DashboardView, Http403
|
||||||
|
from user.models import User, Institution
|
||||||
|
from admin.email import NotifyActivateUserByEmail
|
||||||
|
|
||||||
|
|
||||||
|
class AdminView(DashboardView):
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
response = super().get(*args, **kwargs)
|
||||||
|
if not self.request.user.is_admin:
|
||||||
|
raise Http403
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
class PanelView(AdminView, TemplateView):
|
||||||
|
template_name = "admin_panel.html"
|
||||||
|
title = _("Admin")
|
||||||
|
breadcrumb = _("admin") + " /"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class UsersView(AdminView, TemplateView):
|
||||||
|
template_name = "admin_users.html"
|
||||||
|
title = _("Users")
|
||||||
|
breadcrumb = _("admin / Users") + " /"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context.update({
|
||||||
|
"users": User.objects.filter()
|
||||||
|
})
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class CreateUserView(AdminView, NotifyActivateUserByEmail, CreateView):
|
||||||
|
template_name = "user.html"
|
||||||
|
title = _("User")
|
||||||
|
breadcrumb = _("admin / User") + " /"
|
||||||
|
success_url = reverse_lazy('admin:users')
|
||||||
|
model = User
|
||||||
|
fields = (
|
||||||
|
"email",
|
||||||
|
"password",
|
||||||
|
"is_admin",
|
||||||
|
)
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
form.instance.institution = self.request.user.institution
|
||||||
|
form.instance.set_password(form.instance.password)
|
||||||
|
response = super().form_valid(form)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.send_email(form.instance)
|
||||||
|
except SMTPException as e:
|
||||||
|
messages.error(self.request, e)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteUserView(AdminView, DeleteView):
|
||||||
|
template_name = "delete_user.html"
|
||||||
|
title = _("Delete user")
|
||||||
|
breadcrumb = "admin / Delete user"
|
||||||
|
success_url = reverse_lazy('admin:users')
|
||||||
|
model = User
|
||||||
|
fields = (
|
||||||
|
"email",
|
||||||
|
"password",
|
||||||
|
"is_admin",
|
||||||
|
)
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
response = super().form_valid(form)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class EditUserView(AdminView, UpdateView):
|
||||||
|
template_name = "user.html"
|
||||||
|
title = _("Edit user")
|
||||||
|
breadcrumb = "admin / Edit user"
|
||||||
|
success_url = reverse_lazy('admin:users')
|
||||||
|
model = User
|
||||||
|
fields = (
|
||||||
|
"email",
|
||||||
|
"is_admin",
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
pk = self.kwargs.get('pk')
|
||||||
|
self.object = get_object_or_404(self.model, pk=pk)
|
||||||
|
#self.object.set_password(self.object.password)
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
|
class InstitutionView(AdminView, UpdateView):
|
||||||
|
template_name = "institution.html"
|
||||||
|
title = _("Edit institution")
|
||||||
|
section = "admin"
|
||||||
|
subtitle = _('Edit institution')
|
||||||
|
model = Institution
|
||||||
|
success_url = reverse_lazy('admin:panel')
|
||||||
|
fields = (
|
||||||
|
"name",
|
||||||
|
"logo",
|
||||||
|
"location",
|
||||||
|
"responsable_person",
|
||||||
|
"supervisor_person"
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
self.object = self.request.user.institution
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
return kwargs
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 5.0.6 on 2024-09-19 15:09
|
# Generated by Django 5.0.6 on 2024-10-10 11:34
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -26,6 +26,7 @@ class Migration(migrations.Migration):
|
||||||
verbose_name="ID",
|
verbose_name="ID",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
("tag", models.CharField(max_length=50)),
|
||||||
("token", models.UUIDField()),
|
("token", models.UUIDField()),
|
||||||
(
|
(
|
||||||
"owner",
|
"owner",
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from user.models import User
|
from user.models import User
|
||||||
|
|
||||||
# Create your models here.
|
|
||||||
|
|
||||||
|
|
||||||
class Token(models.Model):
|
class Token(models.Model):
|
||||||
|
tag = models.CharField(max_length=50)
|
||||||
token = models.UUIDField()
|
token = models.UUIDField()
|
||||||
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
|
|
@ -33,12 +33,30 @@ class TokensTable(tables.Table):
|
||||||
},
|
},
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
|
edit_token = ButtonColumn(
|
||||||
|
linkify={
|
||||||
|
"viewname": "api:edit_token",
|
||||||
|
"args": [tables.A("pk")]
|
||||||
|
},
|
||||||
|
attrs = {
|
||||||
|
"a": {
|
||||||
|
"type": "button",
|
||||||
|
"class": "text-primary",
|
||||||
|
"title": "Remove",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
orderable=False,
|
||||||
|
verbose_name="Edit"
|
||||||
|
)
|
||||||
token = tables.Column(verbose_name=_("Token"), empty_values=())
|
token = tables.Column(verbose_name=_("Token"), empty_values=())
|
||||||
|
tag = tables.Column(verbose_name=_("Tag"), empty_values=())
|
||||||
|
|
||||||
def render_view_user(self):
|
def render_view_user(self):
|
||||||
return format_html('<i class="bi bi-eye"></i>')
|
return format_html('<i class="bi bi-eye"></i>')
|
||||||
|
|
||||||
|
def render_edit_token(self):
|
||||||
|
return format_html('<i class="bi bi-pencil-square"></i>')
|
||||||
|
|
||||||
# def render_token(self, record):
|
# def render_token(self, record):
|
||||||
# return record.get_memberships()
|
# return record.get_memberships()
|
||||||
|
|
||||||
|
@ -63,5 +81,5 @@ class TokensTable(tables.Table):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Token
|
model = Token
|
||||||
template_name = "custom_table.html"
|
template_name = "custom_table.html"
|
||||||
fields = ("token", "view_user")
|
fields = ("token", "tag", "edit_token")
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h3>{{ subtitle }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% load django_bootstrap5 %}
|
||||||
|
<form role="form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% if form.errors %}
|
||||||
|
<div class="alert alert-danger alert-icon alert-icon-border alert-dismissible" role="alert">
|
||||||
|
<div class="icon"><span class="mdi mdi-close-circle-o"></span></div>
|
||||||
|
<div class="message">
|
||||||
|
{% for field, error in form.errors.items %}
|
||||||
|
{{ error }}<br />
|
||||||
|
{% endfor %}
|
||||||
|
<button class="btn-close" type="button" data-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
<div class="form-actions-no-box">
|
||||||
|
<a class="btn btn-grey" href="{% url 'api:tokens' %}">{% translate "Cancel" %}</a>
|
||||||
|
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
|
@ -8,6 +8,7 @@
|
||||||
{{ subtitle }}
|
{{ subtitle }}
|
||||||
</h3>
|
</h3>
|
||||||
{% render_table table %}
|
{% render_table table %}
|
||||||
|
|
||||||
<div class="form-actions-no-box">
|
<div class="form-actions-no-box">
|
||||||
<a class="btn btn-green-admin" href="{% url 'api:new_token' %}">{% translate "Generate a new token" %} <i class="bi bi-plus"></i></a>
|
<a class="btn btn-green-admin" href="{% url 'api:new_token' %}">{% translate "Generate a new token" %} <i class="bi bi-plus"></i></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,5 +9,6 @@ urlpatterns = [
|
||||||
path('snapshot/', views.NewSnapshot, name='new_snapshot'),
|
path('snapshot/', views.NewSnapshot, name='new_snapshot'),
|
||||||
path('tokens/', views.TokenView.as_view(), name='tokens'),
|
path('tokens/', views.TokenView.as_view(), name='tokens'),
|
||||||
path('tokens/new', views.TokenNewView.as_view(), name='new_token'),
|
path('tokens/new', views.TokenNewView.as_view(), name='new_token'),
|
||||||
|
path("tokens/<int:pk>/edit", views.EditTokenView.as_view(), name="edit_token"),
|
||||||
path('tokens/<int:pk>/del', views.TokenDeleteView.as_view(), name='delete_token'),
|
path('tokens/<int:pk>/del', views.TokenDeleteView.as_view(), name='delete_token'),
|
||||||
]
|
]
|
||||||
|
|
95
api/views.py
95
api/views.py
|
@ -1,13 +1,19 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.urls import reverse_lazy
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.views.generic.edit import DeleteView
|
|
||||||
from django.views.generic.base import View
|
|
||||||
from django.http import JsonResponse
|
|
||||||
from django_tables2 import SingleTableView
|
from django_tables2 import SingleTableView
|
||||||
|
from django.views.generic.base import View
|
||||||
|
from django.views.generic.edit import (
|
||||||
|
CreateView,
|
||||||
|
DeleteView,
|
||||||
|
UpdateView,
|
||||||
|
)
|
||||||
|
from django.http import JsonResponse
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from dashboard.mixins import DashboardView
|
from dashboard.mixins import DashboardView
|
||||||
|
@ -29,14 +35,14 @@ def NewSnapshot(request):
|
||||||
return JsonResponse({'error': 'Invalid request method'}, status=400)
|
return JsonResponse({'error': 'Invalid request method'}, status=400)
|
||||||
|
|
||||||
# Authentication
|
# Authentication
|
||||||
# auth_header = request.headers.get('Authorization')
|
auth_header = request.headers.get('Authorization')
|
||||||
# if not auth_header or not auth_header.startswith('Bearer '):
|
if not auth_header or not auth_header.startswith('Bearer '):
|
||||||
# return JsonResponse({'error': 'Invalid or missing token'}, status=401)
|
return JsonResponse({'error': 'Invalid or missing token'}, status=401)
|
||||||
|
|
||||||
# token = auth_header.split(' ')[1]
|
token = auth_header.split(' ')[1]
|
||||||
# tk = Token.objects.filter(token=token).first()
|
tk = Token.objects.filter(token=token).first()
|
||||||
# if not tk:
|
if not tk:
|
||||||
# return JsonResponse({'error': 'Invalid or missing token'}, status=401)
|
return JsonResponse({'error': 'Invalid or missing token'}, status=401)
|
||||||
|
|
||||||
# Validation snapshot
|
# Validation snapshot
|
||||||
try:
|
try:
|
||||||
|
@ -60,15 +66,34 @@ def NewSnapshot(request):
|
||||||
# save_in_disk(data, tk.user)
|
# save_in_disk(data, tk.user)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Build(data, tk.user)
|
Build(data, tk.owner)
|
||||||
user = User.objects.get(email="user@example.org")
|
|
||||||
Build(data, user)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return JsonResponse({'status': 'fail'}, status=200)
|
return JsonResponse({'status': 'fail'}, status=200)
|
||||||
|
|
||||||
return JsonResponse({'status': 'success'}, status=200)
|
annotation = Annotation.objects.filter(
|
||||||
|
uuid=data['uuid'],
|
||||||
|
type=Annotation.Type.SYSTEM,
|
||||||
|
# TODO this is hardcoded, it should select the user preferred algorithm
|
||||||
|
key="hidalgo1",
|
||||||
|
owner=tk.owner.institution
|
||||||
|
).first()
|
||||||
|
|
||||||
|
|
||||||
|
if not annotation:
|
||||||
|
return JsonResponse({'status': 'fail'}, status=200)
|
||||||
|
|
||||||
|
url = "{}://{}{}".format(
|
||||||
|
request.scheme,
|
||||||
|
settings.DOMAIN,
|
||||||
|
reverse_lazy("device:details", args=(annotation.value,))
|
||||||
|
)
|
||||||
|
response = {
|
||||||
|
"status": "success",
|
||||||
|
"dhid": annotation.value[:6].upper(),
|
||||||
|
"url": url,
|
||||||
|
"public_url": url
|
||||||
|
}
|
||||||
|
return JsonResponse(response, status=200)
|
||||||
|
|
||||||
|
|
||||||
class TokenView(DashboardView, SingleTableView):
|
class TokenView(DashboardView, SingleTableView):
|
||||||
|
@ -89,7 +114,7 @@ class TokenView(DashboardView, SingleTableView):
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context.update({
|
context.update({
|
||||||
'tokens': Token.objects,
|
'tokens': Token.objects.all(),
|
||||||
})
|
})
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
@ -105,10 +130,42 @@ class TokenDeleteView(DashboardView, DeleteView):
|
||||||
return redirect('api:tokens')
|
return redirect('api:tokens')
|
||||||
|
|
||||||
|
|
||||||
class TokenNewView(DashboardView, View):
|
class TokenNewView(DashboardView, CreateView):
|
||||||
|
template_name = "new_token.html"
|
||||||
|
title = _("Credential management")
|
||||||
|
section = "Credential"
|
||||||
|
subtitle = _('New Tokens')
|
||||||
|
icon = 'bi bi-key'
|
||||||
|
model = Token
|
||||||
|
success_url = reverse_lazy('api:tokens')
|
||||||
|
fields = (
|
||||||
|
"tag",
|
||||||
|
)
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def form_valid(self, form):
|
||||||
Token.objects.create(token=uuid4(), owner=self.request.user)
|
form.instance.owner = self.request.user
|
||||||
|
form.instance.token = uuid4()
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
return redirect('api:tokens')
|
|
||||||
|
|
||||||
|
class EditTokenView(DashboardView, UpdateView):
|
||||||
|
template_name = "new_token.html"
|
||||||
|
title = _("Credential management")
|
||||||
|
section = "Credential"
|
||||||
|
subtitle = _('New Tokens')
|
||||||
|
icon = 'bi bi-key'
|
||||||
|
model = Token
|
||||||
|
success_url = reverse_lazy('api:tokens')
|
||||||
|
fields = (
|
||||||
|
"tag",
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
pk = self.kwargs.get('pk')
|
||||||
|
self.object = get_object_or_404(
|
||||||
|
self.model,
|
||||||
|
owner=self.request.user,
|
||||||
|
pk=pk,
|
||||||
|
)
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
return kwargs
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from django.urls import resolve
|
from django.urls import resolve
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect, Http404
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
@ -47,8 +47,9 @@ class DashboardView(LoginRequiredMixin):
|
||||||
dev_ids = self.request.session.pop("devices", [])
|
dev_ids = self.request.session.pop("devices", [])
|
||||||
|
|
||||||
self._devices = []
|
self._devices = []
|
||||||
annotation = Annotation.objects.filter(value__in=dev_ids)
|
for x in Annotation.objects.filter(value__in=dev_ids).filter(
|
||||||
for x in annotation.filter(owner=self.request.user.institution).distinct():
|
owner=self.request.user.institution
|
||||||
|
).distinct():
|
||||||
self._devices.append(Device(id=x.value))
|
self._devices.append(Device(id=x.value))
|
||||||
return self._devices
|
return self._devices
|
||||||
|
|
||||||
|
@ -57,7 +58,11 @@ class DetailsMixin(DashboardView, TemplateView):
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
self.pk = kwargs['pk']
|
self.pk = kwargs['pk']
|
||||||
self.object = get_object_or_404(self.model, pk=self.pk, owner=self.request.user.institution)
|
self.object = get_object_or_404(
|
||||||
|
self.model,
|
||||||
|
pk=self.pk,
|
||||||
|
owner=self.request.user.institution
|
||||||
|
)
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
@ -71,14 +76,46 @@ class DetailsMixin(DashboardView, TemplateView):
|
||||||
class InventaryMixin(DashboardView, TemplateView):
|
class InventaryMixin(DashboardView, TemplateView):
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
dev_ids = dict(self.request.POST).get("devices", [])
|
post = dict(self.request.POST)
|
||||||
self.request.session["devices"] = dev_ids
|
url = post.get("url")
|
||||||
url = self.request.POST.get("url")
|
|
||||||
if url:
|
if url:
|
||||||
|
dev_ids = post.get("devices", [])
|
||||||
|
self.request.session["devices"] = dev_ids
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resource = resolve(url)
|
resource = resolve(url[0])
|
||||||
if resource and dev_ids:
|
if resource and dev_ids:
|
||||||
return redirect(url)
|
return redirect(url[0])
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
limit = self.request.GET.get("limit")
|
||||||
|
page = self.request.GET.get("page")
|
||||||
|
try:
|
||||||
|
limit = int(limit)
|
||||||
|
page = int(page)
|
||||||
|
if page < 1:
|
||||||
|
page = 1
|
||||||
|
if limit < 1:
|
||||||
|
limit = 10
|
||||||
|
except:
|
||||||
|
limit = 10
|
||||||
|
page = 1
|
||||||
|
|
||||||
|
offset = (page - 1) * limit
|
||||||
|
devices, count = self.get_devices(self.request.user, offset, limit)
|
||||||
|
total_pages = (count + limit - 1) // limit
|
||||||
|
|
||||||
|
context.update({
|
||||||
|
'devices': devices,
|
||||||
|
'count': count,
|
||||||
|
"limit": limit,
|
||||||
|
"offset": offset,
|
||||||
|
"page": page,
|
||||||
|
"total_pages": total_pages,
|
||||||
|
})
|
||||||
|
return context
|
||||||
|
|
|
@ -61,12 +61,12 @@
|
||||||
<header class="navbar navbar-dark sticky-top admin bg-green flex-md-nowrap p-0 shadow">
|
<header class="navbar navbar-dark sticky-top admin bg-green flex-md-nowrap p-0 shadow">
|
||||||
<a class="navbar-brand col-md-3 col-lg-2 me-0 px-3" href="#">DEVICEHUB</a>
|
<a class="navbar-brand col-md-3 col-lg-2 me-0 px-3" href="#">DEVICEHUB</a>
|
||||||
<div class="navbar-nav navbar-sub-brand">
|
<div class="navbar-nav navbar-sub-brand">
|
||||||
PANGEA
|
{{ user.institution.name|upper }}
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-nav">
|
<div class="navbar-nav">
|
||||||
<div class="nav-item text-nowrap">
|
<div class="nav-item text-nowrap">
|
||||||
<i id="user-avatar" class="bi bi-person-circle"></i>
|
<i id="user-avatar" class="bi bi-person-circle"></i>
|
||||||
<a class="navbar-sub-brand px-3" href="#">{{ user.email }}</a>
|
<a class="navbar-sub-brand px-3" href="{% url 'user:panel' %}">{{ user.email }}</a>
|
||||||
<a class="logout" href="{% url 'login:logout' %}">
|
<a class="logout" href="{% url 'login:logout' %}">
|
||||||
<i class="fa-solid fa-arrow-right-from-bracket"></i>
|
<i class="fa-solid fa-arrow-right-from-bracket"></i>
|
||||||
</a>
|
</a>
|
||||||
|
@ -79,6 +79,26 @@
|
||||||
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
|
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
|
||||||
<div class="position-sticky pt-5">
|
<div class="position-sticky pt-5">
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column">
|
||||||
|
{% if user.is_admin %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="admin {% if path in 'panel users' %}active {% endif %}nav-link fw-bold" data-bs-toggle="collapse" data-bs-target="#ul_admin" aria-expanded="false" aria-controls="ul_admin" href="javascript:void()">
|
||||||
|
<i class="bi bi-person-fill-gear icon_sidebar"></i>
|
||||||
|
{% trans 'Admin' %}
|
||||||
|
</a>
|
||||||
|
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if path in 'panel users' %}expanded{% else %}collapse{% endif %}" id="ul_admin" data-bs-parent="#sidebarMenu">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link{% if path == 'panel' %} active2{% endif %}" href="{% url 'admin:panel' %}">
|
||||||
|
{% trans 'Panel' %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link{% if path == 'users' %} active2{% endif %}" href="{% url 'admin:users' %}">
|
||||||
|
{% trans 'Users' %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="admin {% if path == 'unassigned_devices' %}active {% endif %}nav-link fw-bold" data-bs-toggle="collapse" data-bs-target="#ul_devices" aria-expanded="false" aria-controls="ul_devices" href="javascript:void()">
|
<a class="admin {% if path == 'unassigned_devices' %}active {% endif %}nav-link fw-bold" data-bs-toggle="collapse" data-bs-target="#ul_devices" aria-expanded="false" aria-controls="ul_devices" href="javascript:void()">
|
||||||
<i class="bi bi-laptop icon_sidebar"></i>
|
<i class="bi bi-laptop icon_sidebar"></i>
|
||||||
|
@ -143,12 +163,6 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link fw-bold" href="{% url 'api:tokens' %}">
|
|
||||||
<i class="bi-menu-button-wide icon_sidebar"></i>
|
|
||||||
{% trans 'Tokens' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -165,7 +179,19 @@
|
||||||
{% endblock messages %}
|
{% endblock messages %}
|
||||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2">
|
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2">
|
||||||
<h1 class="h2">{{ title }}</h1>
|
<h1 class="h2">{{ title }}</h1>
|
||||||
|
|
||||||
|
<form method="post" action="{% url 'dashboard:search' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="input-group rounded">
|
||||||
|
<input type="search" name="search" class="form-control rounded" placeholder="Search your device..." aria-label="Search" aria-describedby="search-addon" />
|
||||||
|
<span class="input-group-text border-0" id="search-addon">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row border-bottom mb-3">
|
<div class="row border-bottom mb-3">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<small style="color:#899bbd"><i>{{ breadcrumb }}</i></small>
|
<small style="color:#899bbd"><i>{{ breadcrumb }}</i></small>
|
||||||
|
|
|
@ -1,213 +0,0 @@
|
||||||
{% load i18n static %}
|
|
||||||
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
{% block head %}
|
|
||||||
{% block meta %}
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
|
||||||
<meta name="robots" content="NONE,NOARCHIVE" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<meta name="description" content="">
|
|
||||||
<meta name="author" content="Pangea">
|
|
||||||
{% endblock %}
|
|
||||||
<title>{% block title %}{% if title %}{{ title }} – {% endif %}DeviceHub{% endblock %}</title>
|
|
||||||
|
|
||||||
<!-- Bootstrap core CSS -->
|
|
||||||
{% block style %}
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
|
||||||
<link rel="stylesheet" href= "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
|
|
||||||
<link href="{% static "/css/bootstrap.min.css" %}" rel="stylesheet">
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.bd-placeholder-img {
|
|
||||||
font-size: 1.125rem;
|
|
||||||
text-anchor: middle;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.bd-placeholder-img-lg {
|
|
||||||
font-size: 3.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
html, body {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-content {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Custom styles for this template -->
|
|
||||||
<link href="{% static "/css/dashboard.css" %}" rel="stylesheet">
|
|
||||||
{% endblock %}
|
|
||||||
{% endblock %}
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header class="navbar navbar-dark sticky-top admin bg-green flex-md-nowrap p-0 shadow">
|
|
||||||
<a class="navbar-brand col-md-3 col-lg-2 me-0 px-3" href="#">DEVICE HUB</a>
|
|
||||||
<div class="navbar-nav navbar-sub-brand">
|
|
||||||
PANGEA
|
|
||||||
</div>
|
|
||||||
<div class="navbar-nav">
|
|
||||||
<div class="nav-item text-nowrap">
|
|
||||||
<i id="user-avatar" class="bi bi-person-circle"></i>
|
|
||||||
<a class="navbar-sub-brand px-3" href="#">{{ user.email }}</a>
|
|
||||||
<a class="logout" href="{% url 'login:logout' %}">
|
|
||||||
<i class="fa-solid fa-arrow-right-from-bracket"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="row">
|
|
||||||
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
|
|
||||||
<div class="position-sticky pt-5">
|
|
||||||
<ul class="nav flex-column">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="admin nav-link {% if section == 'Home' %}active {% endif %}fw-bold" href="{% url 'dashboard:dashboard' %}">
|
|
||||||
<i class="bi bi-house-door icon_sidebar"></i>
|
|
||||||
{% trans 'Dashboard' %}
|
|
||||||
</a>
|
|
||||||
<hr />
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="admin {% if section == 'People' %}active {% endif %}nav-link fw-bold" data-bs-toggle="collapse" data-bs-target="#people" aria-expanded="false" aria-controls="people" href="javascript:void()">
|
|
||||||
<i class="bi bi-people icon_sidebar"></i>
|
|
||||||
{% trans 'Users' %}
|
|
||||||
</a>
|
|
||||||
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if section == 'People' %}expanded{% else %}collapse{% endif %}" id="people" data-bs-parent="#sidebarMenu">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link{% if path == 'admin_people_list' %} active2{% endif %}" href="{# url 'idhub:admin_people_list' #}">
|
|
||||||
{% trans 'View users' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link{% if path == 'admin_people_new' %} active2{% endif %}" href="{# url 'idhub:admin_people_new' #}">
|
|
||||||
{% trans 'Add user' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="admin nav-link {% if section == 'AccessControl' %}active {% endif %}fw-bold" data-bs-toggle="collapse" data-bs-target="#control-access" aria-expanded="false" aria-controls="control-access" href="javascript:void()">
|
|
||||||
<i class="fa-solid fa-arrow-right-from-bracket icon_sidebar"></i>
|
|
||||||
{% trans 'Roles' %}
|
|
||||||
</a>
|
|
||||||
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if section == 'AccessControl' %}expanded{% else %}collapse{% endif %}" id="control-access" data-bs-parent="#sidebarMenu">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link{% if path == 'admin_roles' %} active2{% endif %}" href="{# url 'idhub:admin_roles' #}">
|
|
||||||
{% trans 'Manage roles' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link{% if path == 'admin_services' %} active2{% endif %}" href="{# url 'idhub:admin_services' #}">
|
|
||||||
{% trans 'Manage services' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="admin nav-link {% if section == 'Credential' %}active {% endif %}fw-bold" data-bs-toggle="collapse" data-bs-target="#credential" aria-expanded="false" aria-controls="credential" href="javascript:void()">
|
|
||||||
<i class="bi bi-patch-check icon_sidebar"></i>
|
|
||||||
{% trans 'Credentials' %}
|
|
||||||
</a>
|
|
||||||
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if section == 'Credential' %}expanded{% else %}collapse{% endif %}" id="credential" data-bs-parent="#sidebarMenu">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link{% if path == 'admin_credentials' %} active2{% endif %}" href="{# url 'idhub:admin_credentials' #}">
|
|
||||||
{% trans 'View credentials' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a id="wallet" class="nav-link" data-bs-toggle="collapse" data-bs-target="#lwallet" aria-expanded="false" aria-controls="lwallet" href="javascript:void()">
|
|
||||||
{% trans "Organization's wallet" %}
|
|
||||||
</a>
|
|
||||||
<ul class="flex-column mb-2 accordion-collapse {% if wallet %}expanded{% else %}collapse{% endif %}" id="lwallet" data-bs-parent="#wallet">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link{% if path == 'admin_dids' %} active2{% endif %}" href="{# url 'idhub:admin_dids' #}">
|
|
||||||
{% trans 'Manage Identities (DIDs)' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link{% if path == 'admin_wallet_credentials' %} active2{% endif %}" href="{# url 'idhub:admin_wallet_credentials' #}">
|
|
||||||
{% trans 'View org. credentials' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link{% if path == 'admin_wallet_config_issue' %} active2{% endif %}" href="{# url 'idhub:admin_wallet_config_issue' #}">
|
|
||||||
{% trans 'Configure credential issuance' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="admin nav-link {% if section == 'Templates' %}active {% endif %}fw-bold" href="{# url 'idhub:admin_schemas' #}">
|
|
||||||
<i class="bi bi-file-earmark-text icon_sidebar"></i>
|
|
||||||
{% trans 'Templates' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="admin nav-link {% if section == 'ImportExport' %}active {% endif %}fw-bold" href="{# url 'idhub:admin_import' #}">
|
|
||||||
<i class="bi bi-arrow-down-square icon_sidebar"></i>
|
|
||||||
{% trans 'Data' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
|
|
||||||
{% block messages %}
|
|
||||||
{% for message in messages %}
|
|
||||||
<div class="alert {{ message.tags|default:'info' }} alert-dismissible fade show mt-3" role="alert">
|
|
||||||
{{ message }}
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close">
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endblock messages %}
|
|
||||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
|
||||||
<h1 class="h2">{{ title }}</h1>
|
|
||||||
<div class="btn-toolbar mb-2 mb-md-0">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% endblock content %}
|
|
||||||
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Footer -->
|
|
||||||
<footer class="footer text-center mt-auto py-3">
|
|
||||||
<div class="container">
|
|
||||||
<span class="text-muted">{{ commit_id }}</span>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
{% block script %}
|
|
||||||
<script src="{% static "js/jquery-3.3.1.slim.min.js" %}"></script>
|
|
||||||
<script src="{% static "js/popper.min.js" %}"></script>
|
|
||||||
<script src="{% static "js/bootstrap.min.js" %}"></script>
|
|
||||||
{% block extrascript %}{% endblock %}
|
|
||||||
{% endblock %}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
{% load i18n %}
|
||||||
|
{% load range %}
|
||||||
|
|
||||||
|
<ul class="pagination">
|
||||||
|
{% if page_number > 1 %}
|
||||||
|
<li class="previous">
|
||||||
|
<a type="button" class="btn btn-grey border border-dark" href="?page=1&limit={{ limit }}">
|
||||||
|
«
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="previous">
|
||||||
|
<a type="button" class="btn btn-grey border border-dark" href="?page={{ page_number|add:-1 }}&limit={{ limit }}">
|
||||||
|
{% trans 'Previous' %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if total_pages > 1 %}
|
||||||
|
|
||||||
|
{% for p in total_pages|range_filter:page_number %}
|
||||||
|
<li {% if p == page_number %}class="active"{% endif %}>
|
||||||
|
<a type="button" class="btn btn-grey{% if p == page_number %}-selected{% endif %}
|
||||||
|
border border-dark"
|
||||||
|
{% if p == page_number or p == "..." %}
|
||||||
|
href="#">
|
||||||
|
{% else %}
|
||||||
|
href="?page={{ p }}&limit={{ limit }}">
|
||||||
|
{% endif %}
|
||||||
|
{{ p }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if page_number < total_pages %}
|
||||||
|
<li class="previous">
|
||||||
|
<a type="button" class="btn btn-grey border border-dark" href="?page={{ page_number|add:+1 }}&limit={{ limit }}">
|
||||||
|
{% trans 'Next' %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="previous">
|
||||||
|
<a type="button" class="btn btn-grey border border-dark" href="?page={{ total_pages }}&limit={{ limit }}">
|
||||||
|
»
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
|
@ -1,5 +1,7 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load paginacion %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -13,7 +15,7 @@
|
||||||
{% trans 'Documents' %}
|
{% trans 'Documents' %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{# url 'idhub:admin_people_activate' object.id #}" type="button" class="btn btn-green-admin">
|
<a href="{# url 'dashboard:exports' object.id #}" type="button" class="btn btn-green-admin">
|
||||||
<i class="bi bi-reply"></i>
|
<i class="bi bi-reply"></i>
|
||||||
{% trans 'Exports' %}
|
{% trans 'Exports' %}
|
||||||
</a>
|
</a>
|
||||||
|
@ -33,7 +35,19 @@
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" data-sortable="">
|
<th scope="col" data-sortable="">
|
||||||
<a class="dataTable-sorter" href="#">Title</a>
|
select
|
||||||
|
</th>
|
||||||
|
<th scope="col" data-sortable="">
|
||||||
|
shortid
|
||||||
|
</th>
|
||||||
|
<th scope="col" data-sortable="">
|
||||||
|
type
|
||||||
|
</th>
|
||||||
|
<th scope="col" data-sortable="">
|
||||||
|
manufacturer
|
||||||
|
</th>
|
||||||
|
<th scope="col" data-sortable="">
|
||||||
|
model
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -45,14 +59,28 @@
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'device:details' dev.id %}">
|
<a href="{% url 'device:details' dev.id %}">
|
||||||
{{ dev.type }} {{ dev.manufacturer }} {{ dev.model }}
|
{{ dev.shortid }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ dev.type }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ dev.manufacturer }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ dev.model }}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
<button type="submit" value="{% url 'lot:del_devices' %}" name="url">Remove</button> <button type="submit" name="url" value="{% url 'lot:add_devices' %}">add</button>
|
<button class="btn btn-green-admin" type="submit" value="{% url 'lot:del_devices' %}" name="url">Remove</button> <button class="btn btn-green-admin" type="submit" name="url" value="{% url 'lot:add_devices' %}">add</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col">
|
||||||
|
{% render_pagination page total_pages limit %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
from django import template
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
@register.inclusion_tag('pagination.html')
|
||||||
|
def render_pagination(page_number, total_pages, limit=10):
|
||||||
|
"""
|
||||||
|
Template tag for render pagination
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- page_number: number of actual page
|
||||||
|
- total_pages: total pages.
|
||||||
|
|
||||||
|
Use it template: {% render_pagination page_number total_pages %}
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
'page_number': page_number,
|
||||||
|
'total_pages': total_pages,
|
||||||
|
'limit': limit
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
from django import template
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def range_filter(value, page):
|
||||||
|
m = 11
|
||||||
|
mind = page -1 - m // 2
|
||||||
|
maxd = page + 1 + m // 2
|
||||||
|
if mind < 0:
|
||||||
|
maxd += abs(mind)
|
||||||
|
if maxd > value:
|
||||||
|
mind -= abs(maxd-value-1)
|
||||||
|
total_pages = [x for x in range(1, value + 1) if maxd > x > mind]
|
||||||
|
|
||||||
|
if value > m:
|
||||||
|
if total_pages[0] > 1:
|
||||||
|
total_pages = ["..."] + total_pages
|
||||||
|
if total_pages[-1] < value:
|
||||||
|
total_pages = total_pages + ["..."]
|
||||||
|
|
||||||
|
return total_pages
|
|
@ -6,4 +6,5 @@ app_name = 'dashboard'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", views.UnassignedDevicesView.as_view(), name="unassigned_devices"),
|
path("", views.UnassignedDevicesView.as_view(), name="unassigned_devices"),
|
||||||
path("<int:pk>/", views.LotDashboardView.as_view(), name="lot"),
|
path("<int:pk>/", views.LotDashboardView.as_view(), name="lot"),
|
||||||
|
path("search", views.SearchView.as_view(), name="search"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
|
import json
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.views.generic.edit import FormView
|
||||||
|
from django.shortcuts import Http404
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
from dashboard.mixins import InventaryMixin, DetailsMixin
|
from dashboard.mixins import InventaryMixin, DetailsMixin
|
||||||
|
from evidence.models import Annotation
|
||||||
|
from evidence.xapian import search
|
||||||
from device.models import Device
|
from device.models import Device
|
||||||
from lot.models import Lot
|
from lot.models import Lot
|
||||||
|
|
||||||
|
@ -10,15 +18,8 @@ class UnassignedDevicesView(InventaryMixin):
|
||||||
title = _("Unassigned Devices")
|
title = _("Unassigned Devices")
|
||||||
breadcrumb = "Devices / Unassigned Devices"
|
breadcrumb = "Devices / Unassigned Devices"
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_devices(self, user, offset, limit):
|
||||||
context = super().get_context_data(**kwargs)
|
return Device.get_unassigned(self.request.user.institution, offset, limit)
|
||||||
devices = Device.get_unassigned(self.request.user.institution)
|
|
||||||
|
|
||||||
context.update({
|
|
||||||
'devices': devices,
|
|
||||||
})
|
|
||||||
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class LotDashboardView(InventaryMixin, DetailsMixin):
|
class LotDashboardView(InventaryMixin, DetailsMixin):
|
||||||
|
@ -30,14 +31,75 @@ class LotDashboardView(InventaryMixin, DetailsMixin):
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
devices = self.get_devices()
|
|
||||||
lot = context.get('object')
|
lot = context.get('object')
|
||||||
context.update({
|
context.update({
|
||||||
'devices': devices,
|
|
||||||
'lot': lot,
|
'lot': lot,
|
||||||
})
|
})
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_devices(self):
|
def get_devices(self, user, offset, limit):
|
||||||
chids = self.object.devicelot_set.all().values_list("device_id", flat=True).distinct()
|
chids = self.object.devicelot_set.all().values_list(
|
||||||
return [Device(id=x) for x in chids]
|
"device_id", flat=True
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
chids_page = chids[offset:offset+limit]
|
||||||
|
return [Device(id=x) for x in chids_page], chids.count()
|
||||||
|
|
||||||
|
|
||||||
|
class SearchView(InventaryMixin):
|
||||||
|
template_name = "unassigned_devices.html"
|
||||||
|
section = "Search"
|
||||||
|
title = _("Search Devices")
|
||||||
|
breadcrumb = "Devices / Search Devices"
|
||||||
|
|
||||||
|
def get_devices(self, user, offset, limit):
|
||||||
|
post = dict(self.request.POST)
|
||||||
|
query = post.get("search")
|
||||||
|
|
||||||
|
if not query:
|
||||||
|
return [], 0
|
||||||
|
|
||||||
|
matches = search(
|
||||||
|
self.request.user.institution,
|
||||||
|
query[0],
|
||||||
|
offset,
|
||||||
|
limit
|
||||||
|
)
|
||||||
|
|
||||||
|
if not matches.size():
|
||||||
|
return self.search_hids(query, offset, limit)
|
||||||
|
|
||||||
|
annotations = []
|
||||||
|
for x in matches:
|
||||||
|
annotations.extend(self.get_annotations(x))
|
||||||
|
|
||||||
|
devices = [Device(id=x) for x in set(annotations)]
|
||||||
|
count = matches.size()
|
||||||
|
return devices, count
|
||||||
|
|
||||||
|
def get_annotations(self, xp):
|
||||||
|
snap = xp.document.get_data()
|
||||||
|
uuid = json.loads(snap).get('uuid')
|
||||||
|
|
||||||
|
return Annotation.objects.filter(
|
||||||
|
type=Annotation.Type.SYSTEM,
|
||||||
|
owner=self.request.user.institution,
|
||||||
|
uuid=uuid
|
||||||
|
).values_list("value", flat=True).distinct()
|
||||||
|
|
||||||
|
def search_hids(self, query, offset, limit):
|
||||||
|
qry = Q()
|
||||||
|
|
||||||
|
for i in query[0].split(" "):
|
||||||
|
if i:
|
||||||
|
qry |= Q(value__startswith=i)
|
||||||
|
|
||||||
|
chids = Annotation.objects.filter(
|
||||||
|
type=Annotation.Type.SYSTEM,
|
||||||
|
owner=self.request.user.institution
|
||||||
|
).filter(
|
||||||
|
qry
|
||||||
|
).values_list("value", flat=True).distinct()
|
||||||
|
chids_page = chids[offset:offset+limit]
|
||||||
|
|
||||||
|
return [Device(id=x) for x in chids_page], chids.count()
|
||||||
|
|
|
@ -23,7 +23,7 @@ DEVICE_TYPES = [
|
||||||
class DeviceForm(forms.Form):
|
class DeviceForm(forms.Form):
|
||||||
type = forms.ChoiceField(choices = DEVICE_TYPES, required=False)
|
type = forms.ChoiceField(choices = DEVICE_TYPES, required=False)
|
||||||
amount = forms.IntegerField(required=False, initial=1)
|
amount = forms.IntegerField(required=False, initial=1)
|
||||||
customer_id = forms.CharField(required=False)
|
custom_id = forms.CharField(required=False)
|
||||||
name = forms.CharField(required=False)
|
name = forms.CharField(required=False)
|
||||||
value = forms.CharField(required=False)
|
value = forms.CharField(required=False)
|
||||||
|
|
||||||
|
@ -49,14 +49,14 @@ class BaseDeviceFormSet(forms.BaseFormSet):
|
||||||
row["amount"] = d["amount"]
|
row["amount"] = d["amount"]
|
||||||
if d.get("name"):
|
if d.get("name"):
|
||||||
row[d["name"]] = d.get("value", '')
|
row[d["name"]] = d.get("value", '')
|
||||||
if d.get("customer_id"):
|
if d.get("custom_id"):
|
||||||
row['CUSTOMER_ID']= d["customer_id"]
|
row['CUSTOM_ID']= d["custom_id"]
|
||||||
|
|
||||||
doc = create_doc(row)
|
doc = create_doc(row)
|
||||||
if not commit:
|
if not commit:
|
||||||
return doc
|
return doc
|
||||||
|
|
||||||
create_index(doc)
|
create_index(doc, self.user)
|
||||||
create_annotation(doc, user, commit=commit)
|
create_annotation(doc, user, commit=commit)
|
||||||
return doc
|
return doc
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from django.db import models
|
from django.db import models, connection
|
||||||
|
|
||||||
from utils.constants import STR_SM_SIZE, STR_SIZE, STR_EXTEND_SIZE, ALGOS
|
from utils.constants import STR_SM_SIZE, STR_SIZE, STR_EXTEND_SIZE, ALGOS
|
||||||
from evidence.models import Annotation, Evidence
|
from evidence.models import Annotation, Evidence
|
||||||
|
@ -27,6 +27,7 @@ class Device:
|
||||||
# the id is the chid of the device
|
# the id is the chid of the device
|
||||||
self.id = kwargs["id"]
|
self.id = kwargs["id"]
|
||||||
self.pk = self.id
|
self.pk = self.id
|
||||||
|
self.shortid = self.pk[:6].upper()
|
||||||
self.algorithm = None
|
self.algorithm = None
|
||||||
self.owner = None
|
self.owner = None
|
||||||
self.annotations = []
|
self.annotations = []
|
||||||
|
@ -89,10 +90,10 @@ class Device:
|
||||||
def get_hids(self):
|
def get_hids(self):
|
||||||
annotations = self.get_annotations()
|
annotations = self.get_annotations()
|
||||||
|
|
||||||
self.hids = annotations.filter(
|
self.hids = list(set(annotations.filter(
|
||||||
type=Annotation.Type.SYSTEM,
|
type=Annotation.Type.SYSTEM,
|
||||||
key__in=ALGOS.keys(),
|
key__in=ALGOS.keys(),
|
||||||
).values_list("value", flat=True)
|
).values_list("value", flat=True)))
|
||||||
|
|
||||||
def get_evidences(self):
|
def get_evidences(self):
|
||||||
if not self.uuids:
|
if not self.uuids:
|
||||||
|
@ -102,8 +103,9 @@ class Device:
|
||||||
|
|
||||||
def get_last_evidence(self):
|
def get_last_evidence(self):
|
||||||
annotations = self.get_annotations()
|
annotations = self.get_annotations()
|
||||||
if annotations:
|
if not annotations.count():
|
||||||
annotation = annotations.first()
|
return
|
||||||
|
annotation = annotations.first()
|
||||||
self.last_evidence = Evidence(annotation.uuid)
|
self.last_evidence = Evidence(annotation.uuid)
|
||||||
|
|
||||||
def last_uuid(self):
|
def last_uuid(self):
|
||||||
|
@ -113,17 +115,46 @@ class Device:
|
||||||
self.lots = [x.lot for x in DeviceLot.objects.filter(device_id=self.id)]
|
self.lots = [x.lot for x in DeviceLot.objects.filter(device_id=self.id)]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_unassigned(cls, institution):
|
def get_unassigned(cls, institution, offset=0, limit=None):
|
||||||
chids = DeviceLot.objects.filter(lot__owner=institution).values_list("device_id", flat=True).distinct()
|
|
||||||
annotations = Annotation.objects.filter(
|
|
||||||
owner=institution,
|
|
||||||
type=Annotation.Type.SYSTEM,
|
|
||||||
).exclude(value__in=chids).values_list("value", flat=True).distinct()
|
|
||||||
return [cls(id=x) for x in annotations]
|
|
||||||
|
|
||||||
# return cls.objects.filter(
|
sql = """
|
||||||
# owner=user
|
SELECT DISTINCT t1.value from evidence_annotation as t1
|
||||||
# ).annotate(num_lots=models.Count('lot')).filter(num_lots=0)
|
left join lot_devicelot as t2 on t1.value = t2.device_id
|
||||||
|
where t2.device_id is null and owner_id=={institution} and type=={type}
|
||||||
|
""".format(
|
||||||
|
institution=institution.id,
|
||||||
|
type=Annotation.Type.SYSTEM,
|
||||||
|
)
|
||||||
|
if limit:
|
||||||
|
sql += " limit {} offset {}".format(int(limit), int(offset))
|
||||||
|
|
||||||
|
sql += ";"
|
||||||
|
|
||||||
|
annotations = []
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute(sql)
|
||||||
|
annotations = cursor.fetchall()
|
||||||
|
|
||||||
|
devices = [cls(id=x[0]) for x in annotations]
|
||||||
|
count = cls.get_unassigned_count(institution)
|
||||||
|
return devices, count
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_unassigned_count(cls, institution):
|
||||||
|
|
||||||
|
sql = """
|
||||||
|
SELECT count(DISTINCT t1.value) from evidence_annotation as t1
|
||||||
|
left join lot_devicelot as t2 on t1.value = t2.device_id
|
||||||
|
where t2.device_id is null and owner_id=={institution} and type=={type};
|
||||||
|
""".format(
|
||||||
|
institution=institution.id,
|
||||||
|
type=Annotation.Type.SYSTEM,
|
||||||
|
)
|
||||||
|
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute(sql)
|
||||||
|
return cursor.fetchall()[0][0]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_websnapshot(self):
|
def is_websnapshot(self):
|
||||||
|
@ -145,6 +176,9 @@ class Device:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self):
|
def type(self):
|
||||||
|
if self.last_evidence.doc['type'] == "WebSnapshot":
|
||||||
|
return self.last_evidence.doc.get("device", {}).get("type", "")
|
||||||
|
|
||||||
if not self.last_evidence:
|
if not self.last_evidence:
|
||||||
self.get_last_evidence()
|
self.get_last_evidence()
|
||||||
return self.last_evidence.get_chassis()
|
return self.last_evidence.get_chassis()
|
||||||
|
@ -155,3 +189,8 @@ class Device:
|
||||||
self.get_last_evidence()
|
self.get_last_evidence()
|
||||||
return self.last_evidence.get_model()
|
return self.last_evidence.get_model()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def components(self):
|
||||||
|
if not self.last_evidence:
|
||||||
|
self.get_last_evidence()
|
||||||
|
return self.last_evidence.get_components()
|
||||||
|
|
|
@ -2,9 +2,46 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h3>{{ object.id }}</h3>
|
<h3>{{ object.shortid }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<ul class="nav nav-tabs nav-tabs-bordered">
|
||||||
|
<li class="nav-items">
|
||||||
|
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#details">General details</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-items">
|
||||||
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#annotations">User annotations</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-items">
|
||||||
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#documents">Documents</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-items">
|
||||||
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#lots">Lots</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-items">
|
||||||
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#components">Components</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-items">
|
||||||
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#evidences">Evidences</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-items">
|
||||||
|
<a class="nav-link" href="">Web</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-content pt-2">
|
||||||
|
|
||||||
|
<div class="tab-pane fade show active" id="details">
|
||||||
|
<h5 class="card-title">Details</h5>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-lg-3 col-md-4 label ">Phid</div>
|
||||||
|
<div class="col-lg-9 col-md-8">{{ object.id }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -48,18 +85,61 @@
|
||||||
<div class="col-lg-9 col-md-8">{{ object.type }}</div>
|
<div class="col-lg-9 col-md-8">{{ object.type }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if object.is_websnapshot %}
|
<i class="bi bi-plus"></i>
|
||||||
{% for k, v in object.last_user_evidence %}
|
Add new document
|
||||||
<div class="row">
|
<span class="caret"></span>
|
||||||
<div class="col-lg-3 col-md-4 label">{{ k }}</div>
|
</a>
|
||||||
<div class="col-lg-9 col-md-8">{{ v|default:'' }}</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endfor %}
|
<h5 class="card-title mt-2">Documents</h5>
|
||||||
{% else %}
|
<table class="table table-striped">
|
||||||
<div class="row">
|
<thead>
|
||||||
<div class="col-lg-3 col-md-4 label">Manufacturer</div>
|
<tr>
|
||||||
<div class="col-lg-9 col-md-8">{{ object.manufacturer|default:'' }}</div>
|
<th scope="col">Key</th>
|
||||||
|
<th scope="col">Value</th>
|
||||||
|
<th scope="col" data-type="date" data-format="YYYY-MM-DD hh:mm">Created on</th>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for a in object.get_user_documents %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ a.key }}</td>
|
||||||
|
<td>{{ a.value }}</td>
|
||||||
|
<td>{{ a.created }}</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-pane fade profile-overview" id="components">
|
||||||
|
<h5 class="card-title">Components last evidence</h5>
|
||||||
|
<div class="list-group col-6">
|
||||||
|
{% for c in object.components %}
|
||||||
|
<div class="list-group-item">
|
||||||
|
<div class="d-flex w-100 justify-content-between">
|
||||||
|
<h5 class="mb-1">{{ c.type }}</h5>
|
||||||
|
<small class="text-muted">{{ evidence.created }}</small>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="mb-1">
|
||||||
|
{% for k, v in c.items %}
|
||||||
|
{% if k not in "actions,type" %}
|
||||||
|
{{ k }}: {{ v }}<br />
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
<br />
|
||||||
|
</p>
|
||||||
|
<small class="text-muted">
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-3 col-md-4 label">Model</div>
|
<div class="col-lg-3 col-md-4 label">Model</div>
|
||||||
|
|
|
@ -40,15 +40,6 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ form.management_form }}
|
{{ form.management_form }}
|
||||||
<div class="container" id="formset-container">
|
<div class="container" id="formset-container">
|
||||||
<div class="row mb-2">
|
|
||||||
<div class="col"></div>
|
|
||||||
<div class="col-2 text-center">
|
|
||||||
<a href="javascript:void()" onclick="addForm(this);" type="button" class="btn btn-green-admin">
|
|
||||||
<i class="bi bi-plus"></i>
|
|
||||||
{% trans 'Add' %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
{% bootstrap_field form.0.type %}
|
{% bootstrap_field form.0.type %}
|
||||||
|
@ -61,7 +52,18 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
{% bootstrap_field form.0.customer_id %}
|
{% bootstrap_field form.0.custom_id %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-10">
|
||||||
|
<span class="fw-bold">{% trans 'Component details' %}</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-2 text-center">
|
||||||
|
<a href="javascript:void()" onclick="addForm(this);" type="button" class="btn btn-green-admin text-nowrap">
|
||||||
|
<i class="bi bi-plus"></i>
|
||||||
|
{% trans 'Add component' %}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% for f in form %}
|
{% for f in form %}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from django.http import Http404
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404, Http404
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic.edit import (
|
from django.views.generic.edit import (
|
||||||
CreateView,
|
CreateView,
|
||||||
|
@ -9,9 +10,8 @@ from django.views.generic.edit import (
|
||||||
FormView,
|
FormView,
|
||||||
)
|
)
|
||||||
from django.views.generic.base import TemplateView
|
from django.views.generic.base import TemplateView
|
||||||
from dashboard.mixins import DashboardView
|
from dashboard.mixins import DashboardView, Http403
|
||||||
from evidence.models import Annotation
|
from evidence.models import Annotation
|
||||||
from evidence.xapian import search
|
|
||||||
from lot.models import LotTag
|
from lot.models import LotTag
|
||||||
from device.models import Device
|
from device.models import Device
|
||||||
from device.forms import DeviceFormSet
|
from device.forms import DeviceFormSet
|
||||||
|
@ -21,7 +21,7 @@ class NewDeviceView(DashboardView, FormView):
|
||||||
template_name = "new_device.html"
|
template_name = "new_device.html"
|
||||||
title = _("New Device")
|
title = _("New Device")
|
||||||
breadcrumb = "Device / New Device"
|
breadcrumb = "Device / New Device"
|
||||||
success_url = reverse_lazy('device:add')
|
success_url = reverse_lazy('dashboard:unassigned_devices')
|
||||||
form_class = DeviceFormSet
|
form_class = DeviceFormSet
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
|
@ -72,7 +72,11 @@ class EditDeviceView(DashboardView, UpdateView):
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
pk = self.kwargs.get('pk')
|
pk = self.kwargs.get('pk')
|
||||||
self.object = get_object_or_404(self.model, pk=pk)
|
self.object = get_object_or_404(
|
||||||
|
self.model,
|
||||||
|
pk=pk,
|
||||||
|
owner=self.request.user.institution
|
||||||
|
)
|
||||||
self.success_url = reverse_lazy('device:details', args=[pk])
|
self.success_url = reverse_lazy('device:details', args=[pk])
|
||||||
kwargs = super().get_form_kwargs()
|
kwargs = super().get_form_kwargs()
|
||||||
return kwargs
|
return kwargs
|
||||||
|
@ -87,6 +91,11 @@ class DetailsView(DashboardView, TemplateView):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
self.pk = kwargs['pk']
|
self.pk = kwargs['pk']
|
||||||
self.object = Device(id=self.pk)
|
self.object = Device(id=self.pk)
|
||||||
|
if not self.object.last_evidence:
|
||||||
|
raise Http404
|
||||||
|
if self.object.owner != self.request.user.institution:
|
||||||
|
raise Http403
|
||||||
|
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
@ -134,6 +143,7 @@ class AddAnnotationView(DashboardView, CreateView):
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.owner = self.request.user.institution
|
form.instance.owner = self.request.user.institution
|
||||||
|
form.instance.user = self.request.user
|
||||||
form.instance.uuid = self.annotation.uuid
|
form.instance.uuid = self.annotation.uuid
|
||||||
form.instance.type = Annotation.Type.USER
|
form.instance.type = Annotation.Type.USER
|
||||||
response = super().form_valid(form)
|
response = super().form_valid(form)
|
||||||
|
@ -143,10 +153,14 @@ class AddAnnotationView(DashboardView, CreateView):
|
||||||
pk = self.kwargs.get('pk')
|
pk = self.kwargs.get('pk')
|
||||||
institution = self.request.user.institution
|
institution = self.request.user.institution
|
||||||
self.annotation = Annotation.objects.filter(
|
self.annotation = Annotation.objects.filter(
|
||||||
owner=institution, value=pk, type=Annotation.Type.SYSTEM
|
owner=institution,
|
||||||
|
value=pk,
|
||||||
|
type=Annotation.Type.SYSTEM
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if not self.annotation:
|
if not self.annotation:
|
||||||
get_object_or_404(Annotation, pk=0, owner=institution)
|
raise Http404
|
||||||
|
|
||||||
self.success_url = reverse_lazy('device:details', args=[pk])
|
self.success_url = reverse_lazy('device:details', args=[pk])
|
||||||
kwargs = super().get_form_kwargs()
|
kwargs = super().get_form_kwargs()
|
||||||
return kwargs
|
return kwargs
|
||||||
|
@ -162,6 +176,7 @@ class AddDocumentView(DashboardView, CreateView):
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.owner = self.request.user.institution
|
form.instance.owner = self.request.user.institution
|
||||||
|
form.instance.user = self.request.user
|
||||||
form.instance.uuid = self.annotation.uuid
|
form.instance.uuid = self.annotation.uuid
|
||||||
form.instance.type = Annotation.Type.DOCUMENT
|
form.instance.type = Annotation.Type.DOCUMENT
|
||||||
response = super().form_valid(form)
|
response = super().form_valid(form)
|
||||||
|
@ -171,10 +186,14 @@ class AddDocumentView(DashboardView, CreateView):
|
||||||
pk = self.kwargs.get('pk')
|
pk = self.kwargs.get('pk')
|
||||||
institution = self.request.user.institution
|
institution = self.request.user.institution
|
||||||
self.annotation = Annotation.objects.filter(
|
self.annotation = Annotation.objects.filter(
|
||||||
owner=institution, value=pk, type=Annotation.Type.SYSTEM
|
owner=institution,
|
||||||
|
value=pk,
|
||||||
|
type=Annotation.Type.SYSTEM
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if not self.annotation:
|
if not self.annotation:
|
||||||
get_object_or_404(Annotation, pk=0, owner=institution)
|
raise Http404
|
||||||
|
|
||||||
self.success_url = reverse_lazy('device:details', args=[pk])
|
self.success_url = reverse_lazy('device:details', args=[pk])
|
||||||
kwargs = super().get_form_kwargs()
|
kwargs = super().get_form_kwargs()
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
|
@ -27,15 +27,46 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
SECRET_KEY = "django-insecure-1p8rs@qf$$l^!vsbetagojw23kw@1ez(qi8^(s0t!wyh!l3"
|
SECRET_KEY = "django-insecure-1p8rs@qf$$l^!vsbetagojw23kw@1ez(qi8^(s0t!wyh!l3"
|
||||||
|
|
||||||
# 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 = config('DEBUG', default=False, cast=bool)
|
||||||
|
|
||||||
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='[]', cast=Csv())
|
DOMAIN = config("DOMAIN")
|
||||||
|
assert DOMAIN not in [None, ''], "DOMAIN var is MANDATORY"
|
||||||
|
# this var is very important, we print it
|
||||||
|
print("DOMAIN: " + DOMAIN)
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=DOMAIN, cast=Csv())
|
||||||
|
assert DOMAIN in ALLOWED_HOSTS, "DOMAIN is not ALLOWED_HOST"
|
||||||
|
|
||||||
|
CSRF_TRUSTED_ORIGINS = config('CSRF_TRUSTED_ORIGINS', default=f'https://{DOMAIN}', cast=Csv())
|
||||||
|
|
||||||
|
|
||||||
|
INITIAL_ADMIN_EMAIL = config("INITIAL_ADMIN_EMAIL", default='admin@example.org')
|
||||||
|
INITIAL_ADMIN_PASSWORD = config("INITIAL_ADMIN_PASSWORD", default='1234')
|
||||||
|
|
||||||
|
DEFAULT_FROM_EMAIL = config(
|
||||||
|
'DEFAULT_FROM_EMAIL', default='webmaster@localhost')
|
||||||
|
|
||||||
|
EMAIL_HOST = config('EMAIL_HOST', default='localhost')
|
||||||
|
|
||||||
|
EMAIL_HOST_USER = config('EMAIL_HOST_USER', default='')
|
||||||
|
|
||||||
|
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD', default='')
|
||||||
|
|
||||||
|
EMAIL_PORT = config('EMAIL_PORT', default=25, cast=int)
|
||||||
|
|
||||||
|
EMAIL_USE_TLS = config('EMAIL_USE_TLS', default=False, cast=bool)
|
||||||
|
|
||||||
|
EMAIL_BACKEND = config('EMAIL_BACKEND', default='django.core.mail.backends.smtp.EmailBackend')
|
||||||
|
|
||||||
|
EMAIL_FILE_PATH = config('EMAIL_FILE_PATH', default='/tmp/app-messages')
|
||||||
|
|
||||||
|
ENABLE_EMAIL = config("ENABLE_EMAIL", default=True, cast=bool)
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
"django.contrib.admin",
|
# "django.contrib.admin",
|
||||||
"django.contrib.auth",
|
"django.contrib.auth",
|
||||||
"django.contrib.contenttypes",
|
"django.contrib.contenttypes",
|
||||||
"django.contrib.sessions",
|
"django.contrib.sessions",
|
||||||
|
@ -54,6 +85,7 @@ INSTALLED_APPS = [
|
||||||
"lot",
|
"lot",
|
||||||
"documents",
|
"documents",
|
||||||
"dashboard",
|
"dashboard",
|
||||||
|
"admin",
|
||||||
"api",
|
"api",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,8 @@ urlpatterns = [
|
||||||
path("dashboard/", include("dashboard.urls")),
|
path("dashboard/", include("dashboard.urls")),
|
||||||
path("evidence/", include("evidence.urls")),
|
path("evidence/", include("evidence.urls")),
|
||||||
path("device/", include("device.urls")),
|
path("device/", include("device.urls")),
|
||||||
|
path("admin/", include("admin.urls")),
|
||||||
|
path("user/", include("user.urls")),
|
||||||
path("lot/", include("lot.urls")),
|
path("lot/", include("lot.urls")),
|
||||||
path('api/', include('api.urls')),
|
path('api/', include('api.urls')),
|
||||||
]
|
]
|
||||||
|
|
|
@ -5,7 +5,8 @@ services:
|
||||||
dockerfile: docker/devicehub-django.Dockerfile
|
dockerfile: docker/devicehub-django.Dockerfile
|
||||||
environment:
|
environment:
|
||||||
- DEBUG=true
|
- DEBUG=true
|
||||||
- ALLOWED_HOSTS=*
|
- DOMAIN=${DOMAIN:-localhost}
|
||||||
|
- DEMO=${DEMO:-n}
|
||||||
volumes:
|
volumes:
|
||||||
- .:/opt/devicehub-django
|
- .:/opt/devicehub-django
|
||||||
ports:
|
ports:
|
||||||
|
|
|
@ -9,11 +9,14 @@ set -u
|
||||||
set -x
|
set -x
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
|
if [ "${DETACH:-}" ]; then
|
||||||
|
detach_arg='-d'
|
||||||
|
fi
|
||||||
# remove old database
|
# remove old database
|
||||||
sudo rm -vf db/*
|
sudo rm -vf db/*
|
||||||
docker compose down
|
docker compose down -v
|
||||||
docker compose build
|
docker compose build
|
||||||
docker compose up
|
docker compose up ${detach_arg:-}
|
||||||
}
|
}
|
||||||
|
|
||||||
main "${@}"
|
main "${@}"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM python:3.11.7-slim-bookworm
|
FROM python:3.11.10-slim-bookworm
|
||||||
|
|
||||||
# last line is dependencies for weasyprint (for generating pdfs in lafede pilot) https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#debian-11
|
# last line is dependencies for weasyprint (for generating pdfs in lafede pilot) https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#debian-11
|
||||||
RUN apt update && \
|
RUN apt update && \
|
||||||
|
@ -22,7 +22,8 @@ compile = no
|
||||||
no-cache-dir = True
|
no-cache-dir = True
|
||||||
END
|
END
|
||||||
|
|
||||||
RUN pip install --upgrade pip
|
# upgrade pip, which might fail on lxc, then remove the "corrupted file"
|
||||||
|
RUN python -m pip install --upgrade pip || (rm -rf /usr/local/lib/python3.11/site-packages/pip-*.dist-info && python -m pip install --upgrade pip)
|
||||||
|
|
||||||
COPY ./requirements.txt /opt/devicehub-django
|
COPY ./requirements.txt /opt/devicehub-django
|
||||||
RUN pip install -r requirements.txt
|
RUN pip install -r requirements.txt
|
||||||
|
|
|
@ -21,21 +21,28 @@ deploy() {
|
||||||
# inspired by https://medium.com/analytics-vidhya/django-with-docker-and-docker-compose-python-part-2-8415976470cc
|
# inspired by https://medium.com/analytics-vidhya/django-with-docker-and-docker-compose-python-part-2-8415976470cc
|
||||||
echo "INFO detected NEW deployment"
|
echo "INFO detected NEW deployment"
|
||||||
./manage.py migrate
|
./manage.py migrate
|
||||||
./manage.py add_institution example-org
|
INIT_ORG="${INIT_ORG:-example-org}"
|
||||||
|
INIT_USER="${INIT_USER:-user@example.org}"
|
||||||
|
INIT_PASSWD="${INIT_PASSWD:-1234}"
|
||||||
|
./manage.py add_institution "${INIT_ORG}"
|
||||||
# TODO: one error on add_user, and you don't add user anymore
|
# TODO: one error on add_user, and you don't add user anymore
|
||||||
./manage.py add_user example-org user@example.org 1234
|
./manage.py add_user "${INIT_ORG}" "${INIT_USER}" "${INIT_PASSWD}"
|
||||||
|
|
||||||
|
if [ "${DEMO:-}" ]; then
|
||||||
|
./manage.py up_snapshots example/snapshots/ "${INIT_USER}"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
runserver() {
|
runserver() {
|
||||||
PORT="${PORT:-8000}"
|
PORT="${PORT:-8000}"
|
||||||
if [ "${DEBUG:-}" = "true" ]; then
|
if [ "${DEBUG:-}" ]; then
|
||||||
./manage.py runserver 0.0.0.0:${PORT}
|
./manage.py runserver 0.0.0.0:${PORT}
|
||||||
else
|
else
|
||||||
# TODO
|
# TODO
|
||||||
#./manage.py collectstatic
|
#./manage.py collectstatic
|
||||||
true
|
true
|
||||||
if [ "${EXPERIMENTAL:-}" = "true" ]; then
|
if [ "${EXPERIMENTAL:-}" ]; then
|
||||||
# TODO
|
# TODO
|
||||||
# reloading on source code changing is a debugging future, maybe better then use debug
|
# reloading on source code changing is a debugging future, maybe better then use debug
|
||||||
# src https://stackoverflow.com/questions/12773763/gunicorn-autoreload-on-source-change/24893069#24893069
|
# src https://stackoverflow.com/questions/12773763/gunicorn-autoreload-on-source-change/24893069#24893069
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Evidencias
|
||||||
|
|
||||||
|
## Placeholder
|
||||||
|
Introducción de evidencias a través de formulario web.
|
||||||
|
|
||||||
|
La *clave* representa el tipo de detalle que se quiere registrar y el *valor* representa lo que se quiere registrar. Véase la siguiente tabla con ejemplos.
|
||||||
|
|
||||||
|
| Clave | Valor |
|
||||||
|
|-------------------------------|---------------------|
|
||||||
|
| marca | Lenovo |
|
||||||
|
| Tarjeta de red | RealTech 1000 |
|
||||||
|
| Pantalla | Samsung |
|
||||||
|
| Dimensiones de pantalla | 15' |
|
||||||
|
| Detalles visuales de pantalla | Rota por un lateral |
|
||||||
|
|
||||||
|
## Snapshots
|
||||||
|
|
||||||
|
Introducción de evidencias a través de programa [workbench-script](https://farga.pangea.org/ereuse/workbench-script)
|
|
@ -0,0 +1,80 @@
|
||||||
|
Modelo de datos *abstracto* de devicehub que ayuda a tener una idea de cómo funciona
|
||||||
|
|
||||||
|
Recordad que por ser este un proyecto de django, se puede obtener de forma automatizada un diagrama de datos con el comando `graph_models` (más adelante vemos de documentar mejor cómo generarlo)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
|
||||||
|
%% los snapshots/placeholders son ficheros de FS inmutables, se insertan en xapian
|
||||||
|
%% y via su uuid se anotan
|
||||||
|
%% placeholders también se pueden firmar (como un spnashot, otra fuente)
|
||||||
|
EVIDENCE {
|
||||||
|
json obj "its uuid is the PK"
|
||||||
|
}
|
||||||
|
|
||||||
|
USER {
|
||||||
|
int id PK
|
||||||
|
string personal-data-etc
|
||||||
|
}
|
||||||
|
|
||||||
|
%% includes the relevant CHID with algorithm for the device build
|
||||||
|
EVIDENCE_ANNOTATION {
|
||||||
|
int id PK
|
||||||
|
uuid uuid "ref evidence (snapshot,placeholder)"
|
||||||
|
string key
|
||||||
|
string value
|
||||||
|
int type "0: sys_deviceid, 1: usr_deviceid, 2: user"
|
||||||
|
ts created
|
||||||
|
int owner FK
|
||||||
|
}
|
||||||
|
|
||||||
|
ALGORITHM {
|
||||||
|
string algorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
%% todas las anotaciones que tienen CHID
|
||||||
|
%% y su key es un algoritmo de los que tenemos
|
||||||
|
|
||||||
|
%% un device es una evaluación
|
||||||
|
|
||||||
|
DEVICE {
|
||||||
|
string CHID
|
||||||
|
}
|
||||||
|
|
||||||
|
DEVICE_ANNOTATION {
|
||||||
|
string CHID FK
|
||||||
|
string key
|
||||||
|
string value
|
||||||
|
uuid uuid "from last snapshot"
|
||||||
|
}
|
||||||
|
|
||||||
|
LOT {
|
||||||
|
int id PK
|
||||||
|
string name
|
||||||
|
string code "id alt legacy"
|
||||||
|
string description
|
||||||
|
bool closed
|
||||||
|
int owner FK
|
||||||
|
ts created
|
||||||
|
ts updated
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
LOT_ANNOTATION {
|
||||||
|
string id FK
|
||||||
|
string key
|
||||||
|
string value
|
||||||
|
}
|
||||||
|
|
||||||
|
SNAPSHOT ||--|| EVIDENCE: "via workbench"
|
||||||
|
PLACEHOLDER ||--|| EVIDENCE: "via webform"
|
||||||
|
|
||||||
|
EVIDENCE ||--|{ EVIDENCE_ANNOTATION: "are interpreted"
|
||||||
|
USER ||--|{ EVIDENCE_ANNOTATION: "manually entered"
|
||||||
|
ALGORITHM ||--|{ EVIDENCE_ANNOTATION: "automatically entered"
|
||||||
|
EVIDENCE_ANNOTATION }|--|{ DEVICE: "aggregates"
|
||||||
|
DEVICE }|--|{ LOT: "aggregates"
|
||||||
|
|
||||||
|
DEVICE ||--|| DEVICE_ANNOTATION: "enriches data"
|
||||||
|
LOT ||--|| LOT_ANNOTATION: "enriches data"
|
||||||
|
```
|
|
@ -55,15 +55,19 @@ class UserTagForm(forms.Form):
|
||||||
tag = forms.CharField(label=_("Tag"))
|
tag = forms.CharField(label=_("Tag"))
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.pk = None
|
||||||
self.uuid = kwargs.pop('uuid', None)
|
self.uuid = kwargs.pop('uuid', None)
|
||||||
annotation = Annotation.objects.filter(
|
self.user = kwargs.pop('user')
|
||||||
|
instance = Annotation.objects.filter(
|
||||||
uuid=self.uuid,
|
uuid=self.uuid,
|
||||||
type=Annotation.Type.SYSTEM,
|
type=Annotation.Type.SYSTEM,
|
||||||
key='CUSTOM_ID',
|
key='CUSTOM_ID',
|
||||||
|
owner=self.user.institution
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if annotation:
|
if instance:
|
||||||
kwargs['initial'].update({'tag': annotation.value})
|
kwargs["initial"]["tag"] = instance.value
|
||||||
|
self.pk = instance.pk
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
@ -72,18 +76,33 @@ class UserTagForm(forms.Form):
|
||||||
if not data:
|
if not data:
|
||||||
return False
|
return False
|
||||||
self.tag = data
|
self.tag = data
|
||||||
|
self.instance = Annotation.objects.filter(
|
||||||
|
uuid=self.uuid,
|
||||||
|
type=Annotation.Type.SYSTEM,
|
||||||
|
key='CUSTOM_ID',
|
||||||
|
owner=self.user.institution
|
||||||
|
).first()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def save(self, user, commit=True):
|
def save(self, user, commit=True):
|
||||||
if not commit:
|
if not commit:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if self.instance:
|
||||||
|
if not self.tag:
|
||||||
|
self.instance.delete()
|
||||||
|
self.instance.value = self.tag
|
||||||
|
self.instance.save()
|
||||||
|
return
|
||||||
|
|
||||||
Annotation.objects.create(
|
Annotation.objects.create(
|
||||||
uuid=self.uuid,
|
uuid=self.uuid,
|
||||||
owner=user.institution,
|
|
||||||
type=Annotation.Type.SYSTEM,
|
type=Annotation.Type.SYSTEM,
|
||||||
key='CUSTOM_ID',
|
key='CUSTOM_ID',
|
||||||
value=self.tag
|
value=self.tag,
|
||||||
|
owner=self.user.institution,
|
||||||
|
user=self.user
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -133,7 +152,7 @@ class ImportForm(forms.Form):
|
||||||
if commit:
|
if commit:
|
||||||
for doc, cred in table:
|
for doc, cred in table:
|
||||||
cred.save()
|
cred.save()
|
||||||
create_index(doc)
|
create_index(doc, self.user)
|
||||||
return table
|
return table
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 5.0.6 on 2024-07-27 16:23
|
# Generated by Django 5.0.6 on 2024-10-10 09:46
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -10,6 +10,7 @@ class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
("user", "0001_initial"),
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -30,7 +31,9 @@ class Migration(migrations.Migration):
|
||||||
("uuid", models.UUIDField()),
|
("uuid", models.UUIDField()),
|
||||||
(
|
(
|
||||||
"type",
|
"type",
|
||||||
models.SmallIntegerField(choices=[(0, "System"), (1, "User")]),
|
models.SmallIntegerField(
|
||||||
|
choices=[(0, "System"), (1, "User"), (2, "Document")]
|
||||||
|
),
|
||||||
),
|
),
|
||||||
("key", models.CharField(max_length=256)),
|
("key", models.CharField(max_length=256)),
|
||||||
("value", models.CharField(max_length=256)),
|
("value", models.CharField(max_length=256)),
|
||||||
|
@ -38,6 +41,15 @@ class Migration(migrations.Migration):
|
||||||
"owner",
|
"owner",
|
||||||
models.ForeignKey(
|
models.ForeignKey(
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="user.institution",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
to=settings.AUTH_USER_MODEL,
|
to=settings.AUTH_USER_MODEL,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
# Generated by Django 5.0.6 on 2024-07-29 14:58
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("evidence", "0001_initial"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="annotation",
|
|
||||||
name="type",
|
|
||||||
field=models.SmallIntegerField(
|
|
||||||
choices=[(0, "System"), (1, "User"), (2, "Document"), (3, "Action")]
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,20 +0,0 @@
|
||||||
# Generated by Django 5.0.6 on 2024-07-29 15:37
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("evidence", "0002_alter_annotation_type"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="annotation",
|
|
||||||
name="type",
|
|
||||||
field=models.SmallIntegerField(
|
|
||||||
choices=[(0, "System"), (1, "User"), (2, "Document")]
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,22 +0,0 @@
|
||||||
# Generated by Django 5.0.6 on 2024-09-18 10:55
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("evidence", "0003_alter_annotation_type"),
|
|
||||||
("user", "0001_initial"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="annotation",
|
|
||||||
name="owner",
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE, to="user.institution"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -5,7 +5,8 @@ from django.db import models
|
||||||
|
|
||||||
from utils.constants import STR_SM_SIZE, STR_EXTEND_SIZE, CHASSIS_DH
|
from utils.constants import STR_SM_SIZE, STR_EXTEND_SIZE, CHASSIS_DH
|
||||||
from evidence.xapian import search
|
from evidence.xapian import search
|
||||||
from user.models import Institution
|
from evidence.parse_details import ParseSnapshot
|
||||||
|
from user.models import User, Institution
|
||||||
|
|
||||||
|
|
||||||
class Annotation(models.Model):
|
class Annotation(models.Model):
|
||||||
|
@ -17,6 +18,7 @@ class Annotation(models.Model):
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
uuid = models.UUIDField()
|
uuid = models.UUIDField()
|
||||||
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
|
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
|
||||||
|
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
type = models.SmallIntegerField(choices=Type)
|
type = models.SmallIntegerField(choices=Type)
|
||||||
key = models.CharField(max_length=STR_EXTEND_SIZE)
|
key = models.CharField(max_length=STR_EXTEND_SIZE)
|
||||||
value = models.CharField(max_length=STR_EXTEND_SIZE)
|
value = models.CharField(max_length=STR_EXTEND_SIZE)
|
||||||
|
@ -35,6 +37,8 @@ class Evidence:
|
||||||
self.created = None
|
self.created = None
|
||||||
self.dmi = None
|
self.dmi = None
|
||||||
self.annotations = []
|
self.annotations = []
|
||||||
|
self.components = []
|
||||||
|
self.default = "n/a"
|
||||||
|
|
||||||
self.get_owner()
|
self.get_owner()
|
||||||
self.get_time()
|
self.get_time()
|
||||||
|
@ -53,8 +57,10 @@ class Evidence:
|
||||||
|
|
||||||
def get_doc(self):
|
def get_doc(self):
|
||||||
self.doc = {}
|
self.doc = {}
|
||||||
|
if not self.owner:
|
||||||
|
self.get_owner()
|
||||||
qry = 'uuid:"{}"'.format(self.uuid)
|
qry = 'uuid:"{}"'.format(self.uuid)
|
||||||
matches = search(qry, limit=1)
|
matches = search(self.owner, qry, limit=1)
|
||||||
if matches.size() < 0:
|
if matches.size() < 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -65,7 +71,6 @@ class Evidence:
|
||||||
dmidecode_raw = self.doc["data"]["dmidecode"]
|
dmidecode_raw = self.doc["data"]["dmidecode"]
|
||||||
self.dmi = DMIParse(dmidecode_raw)
|
self.dmi = DMIParse(dmidecode_raw)
|
||||||
|
|
||||||
|
|
||||||
def get_time(self):
|
def get_time(self):
|
||||||
if not self.doc:
|
if not self.doc:
|
||||||
self.get_doc()
|
self.get_doc()
|
||||||
|
@ -74,16 +79,31 @@ class Evidence:
|
||||||
if not self.created:
|
if not self.created:
|
||||||
self.created = self.annotations.last().created
|
self.created = self.annotations.last().created
|
||||||
|
|
||||||
def components(self):
|
def get_components(self):
|
||||||
return self.doc.get('components', [])
|
if self.doc.get("software") != "EreuseWorkbench":
|
||||||
|
return self.doc.get('components', [])
|
||||||
|
self.set_components()
|
||||||
|
return self.components
|
||||||
|
|
||||||
def get_manufacturer(self):
|
def get_manufacturer(self):
|
||||||
|
if self.doc.get("type") == "WebSnapshot":
|
||||||
|
kv = self.doc.get('kv', {})
|
||||||
|
if len(kv) < 1:
|
||||||
|
return ""
|
||||||
|
return list(self.doc.get('kv').values())[0]
|
||||||
|
|
||||||
if self.doc.get("software") != "EreuseWorkbench":
|
if self.doc.get("software") != "EreuseWorkbench":
|
||||||
return self.doc['device']['manufacturer']
|
return self.doc['device']['manufacturer']
|
||||||
|
|
||||||
return self.dmi.manufacturer().strip()
|
return self.dmi.manufacturer().strip()
|
||||||
|
|
||||||
def get_model(self):
|
def get_model(self):
|
||||||
|
if self.doc.get("type") == "WebSnapshot":
|
||||||
|
kv = self.doc.get('kv', {})
|
||||||
|
if len(kv) < 2:
|
||||||
|
return ""
|
||||||
|
return list(self.doc.get('kv').values())[1]
|
||||||
|
|
||||||
if self.doc.get("software") != "EreuseWorkbench":
|
if self.doc.get("software") != "EreuseWorkbench":
|
||||||
return self.doc['device']['model']
|
return self.doc['device']['model']
|
||||||
|
|
||||||
|
@ -101,11 +121,13 @@ class Evidence:
|
||||||
return k
|
return k
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_all(cls, user):
|
def get_all(cls, user):
|
||||||
return Annotation.objects.filter(
|
return Annotation.objects.filter(
|
||||||
owner=user.institution,
|
owner=user.institution,
|
||||||
type=Annotation.Type.SYSTEM,
|
type=Annotation.Type.SYSTEM,
|
||||||
).order_by("-created").values_list("uuid", flat=True).distinct()
|
).order_by("-created").values_list("uuid", flat=True).distinct()
|
||||||
|
|
||||||
|
def set_components(self):
|
||||||
|
snapshot = ParseSnapshot(self.doc).snapshot_json
|
||||||
|
self.components = snapshot['components']
|
||||||
|
|
|
@ -5,13 +5,13 @@ import hashlib
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from dmidecode import DMIParse
|
from dmidecode import DMIParse
|
||||||
from evidence.xapian import search, index
|
from evidence.models import Annotation
|
||||||
from evidence.models import Evidence, Annotation
|
from evidence.xapian import index
|
||||||
from utils.constants import ALGOS, CHASSIS_DH
|
from utils.constants import ALGOS, CHASSIS_DH
|
||||||
|
|
||||||
|
|
||||||
def get_network_cards(child, nets):
|
def get_network_cards(child, nets):
|
||||||
if child['id'] == 'network':
|
if child['id'] == 'network' and "PCI:" in child.get("businfo"):
|
||||||
nets.append(child)
|
nets.append(child)
|
||||||
if child.get('children'):
|
if child.get('children'):
|
||||||
[get_network_cards(x, nets) for x in child['children']]
|
[get_network_cards(x, nets) for x in child['children']]
|
||||||
|
@ -19,8 +19,12 @@ def get_network_cards(child, nets):
|
||||||
|
|
||||||
def get_mac(lshw):
|
def get_mac(lshw):
|
||||||
nets = []
|
nets = []
|
||||||
|
try:
|
||||||
|
get_network_cards(json.loads(lshw), nets)
|
||||||
|
except Exception as ss:
|
||||||
|
print("WARNING!! {}".format(ss))
|
||||||
|
return
|
||||||
|
|
||||||
get_network_cards(json.loads(lshw), nets)
|
|
||||||
nets_sorted = sorted(nets, key=lambda x: x['businfo'])
|
nets_sorted = sorted(nets, key=lambda x: x['businfo'])
|
||||||
# This funcion get the network card integrated in motherboard
|
# This funcion get the network card integrated in motherboard
|
||||||
# integrate = [x for x in nets if "pci@0000:00:" in x.get('businfo', '')]
|
# integrate = [x for x in nets if "pci@0000:00:" in x.get('businfo', '')]
|
||||||
|
@ -45,7 +49,7 @@ class Build:
|
||||||
|
|
||||||
def index(self):
|
def index(self):
|
||||||
snap = json.dumps(self.json)
|
snap = json.dumps(self.json)
|
||||||
index(self.uuid, snap)
|
index(self.user.institution, self.uuid, snap)
|
||||||
|
|
||||||
def generate_chids(self):
|
def generate_chids(self):
|
||||||
self.algorithms = {
|
self.algorithms = {
|
||||||
|
@ -72,6 +76,7 @@ class Build:
|
||||||
Annotation.objects.create(
|
Annotation.objects.create(
|
||||||
uuid=self.uuid,
|
uuid=self.uuid,
|
||||||
owner=self.user.institution,
|
owner=self.user.institution,
|
||||||
|
user=self.user,
|
||||||
type=Annotation.Type.SYSTEM,
|
type=Annotation.Type.SYSTEM,
|
||||||
key=k,
|
key=k,
|
||||||
value=v
|
value=v
|
||||||
|
|
|
@ -0,0 +1,493 @@
|
||||||
|
import json
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from dmidecode import DMIParse
|
||||||
|
from utils.constants import CHASSIS_DH, DATASTORAGEINTERFACE
|
||||||
|
|
||||||
|
|
||||||
|
def get_lshw_child(child, nets, component):
|
||||||
|
if child.get('id') == component:
|
||||||
|
nets.append(child)
|
||||||
|
if child.get('children'):
|
||||||
|
[get_lshw_child(x, nets, component) for x in child['children']]
|
||||||
|
|
||||||
|
|
||||||
|
class ParseSnapshot:
|
||||||
|
def __init__(self, snapshot, default="n/a"):
|
||||||
|
self.default = default
|
||||||
|
self.dmidecode_raw = snapshot["data"].get("dmidecode", "{}")
|
||||||
|
self.smart_raw = snapshot["data"].get("disks", [])
|
||||||
|
self.hwinfo_raw = snapshot["data"].get("hwinfo", "")
|
||||||
|
self.lshw_raw = snapshot["data"].get("lshw", {}) or {}
|
||||||
|
self.lscpi_raw = snapshot["data"].get("lspci", "")
|
||||||
|
self.device = {"actions": []}
|
||||||
|
self.components = []
|
||||||
|
self.monitors = []
|
||||||
|
|
||||||
|
self.dmi = DMIParse(self.dmidecode_raw)
|
||||||
|
self.smart = self.loads(self.smart_raw)
|
||||||
|
self.lshw = self.loads(self.lshw_raw)
|
||||||
|
self.hwinfo = self.parse_hwinfo()
|
||||||
|
|
||||||
|
self.set_computer()
|
||||||
|
self.get_hwinfo_monitors()
|
||||||
|
self.set_components()
|
||||||
|
self.snapshot_json = {
|
||||||
|
"type": "Snapshot",
|
||||||
|
"device": self.device,
|
||||||
|
"software": snapshot["software"],
|
||||||
|
"components": self.components,
|
||||||
|
"uuid": snapshot['uuid'],
|
||||||
|
"version": snapshot['version'],
|
||||||
|
"endTime": snapshot["timestamp"],
|
||||||
|
"elapsed": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
def set_computer(self):
|
||||||
|
self.device['manufacturer'] = self.dmi.manufacturer().strip()
|
||||||
|
self.device['model'] = self.dmi.model().strip()
|
||||||
|
self.device['serialNumber'] = self.dmi.serial_number()
|
||||||
|
self.device['type'] = self.get_type()
|
||||||
|
self.device['sku'] = self.get_sku()
|
||||||
|
self.device['version'] = self.get_version()
|
||||||
|
self.device['system_uuid'] = self.get_uuid()
|
||||||
|
self.device['family'] = self.get_family()
|
||||||
|
self.device['chassis'] = self.get_chassis_dh()
|
||||||
|
|
||||||
|
def set_components(self):
|
||||||
|
self.get_cpu()
|
||||||
|
self.get_ram()
|
||||||
|
self.get_mother_board()
|
||||||
|
self.get_graphic()
|
||||||
|
self.get_data_storage()
|
||||||
|
self.get_display()
|
||||||
|
self.get_sound_card()
|
||||||
|
self.get_networks()
|
||||||
|
|
||||||
|
def get_cpu(self):
|
||||||
|
for cpu in self.dmi.get('Processor'):
|
||||||
|
serial = cpu.get('Serial Number')
|
||||||
|
if serial == 'Not Specified' or not serial:
|
||||||
|
serial = cpu.get('ID').replace(' ', '')
|
||||||
|
self.components.append(
|
||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"type": "Processor",
|
||||||
|
"speed": self.get_cpu_speed(cpu),
|
||||||
|
"cores": int(cpu.get('Core Count', 1)),
|
||||||
|
"model": cpu.get('Version'),
|
||||||
|
"threads": int(cpu.get('Thread Count', 1)),
|
||||||
|
"manufacturer": cpu.get('Manufacturer'),
|
||||||
|
"serialNumber": serial,
|
||||||
|
"brand": cpu.get('Family'),
|
||||||
|
"address": self.get_cpu_address(cpu),
|
||||||
|
"bogomips": self.get_bogomips(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_ram(self):
|
||||||
|
for ram in self.dmi.get("Memory Device"):
|
||||||
|
if ram.get('size') == 'No Module Installed':
|
||||||
|
continue
|
||||||
|
if not ram.get("Speed"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.components.append(
|
||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"type": "RamModule",
|
||||||
|
"size": self.get_ram_size(ram),
|
||||||
|
"speed": self.get_ram_speed(ram),
|
||||||
|
"manufacturer": ram.get("Manufacturer", self.default),
|
||||||
|
"serialNumber": ram.get("Serial Number", self.default),
|
||||||
|
"interface": ram.get("Type", "DDR"),
|
||||||
|
"format": ram.get("Form Factor", "DIMM"),
|
||||||
|
"model": ram.get("Part Number", self.default),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_mother_board(self):
|
||||||
|
for moder_board in self.dmi.get("Baseboard"):
|
||||||
|
self.components.append(
|
||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"type": "Motherboard",
|
||||||
|
"version": moder_board.get("Version"),
|
||||||
|
"serialNumber": moder_board.get("Serial Number", "").strip(),
|
||||||
|
"manufacturer": moder_board.get("Manufacturer", "").strip(),
|
||||||
|
"biosDate": self.get_bios_date(),
|
||||||
|
"ramMaxSize": self.get_max_ram_size(),
|
||||||
|
"ramSlots": len(self.dmi.get("Memory Device")),
|
||||||
|
"slots": self.get_ram_slots(),
|
||||||
|
"model": moder_board.get("Product Name", "").strip(),
|
||||||
|
"firewire": self.get_firmware_num(),
|
||||||
|
"pcmcia": self.get_pcmcia_num(),
|
||||||
|
"serial": self.get_serial_num(),
|
||||||
|
"usb": self.get_usb_num(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_graphic(self):
|
||||||
|
displays = []
|
||||||
|
get_lshw_child(self.lshw, displays, 'display')
|
||||||
|
|
||||||
|
for c in displays:
|
||||||
|
if not c['configuration'].get('driver', None):
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.components.append(
|
||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"type": "GraphicCard",
|
||||||
|
"memory": self.get_memory_video(c),
|
||||||
|
"manufacturer": c.get("vendor", self.default),
|
||||||
|
"model": c.get("product", self.default),
|
||||||
|
"serialNumber": c.get("serial", self.default),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_memory_video(self, c):
|
||||||
|
# get info of lspci
|
||||||
|
# pci_id = c['businfo'].split('@')[1]
|
||||||
|
# lspci.get(pci_id) | grep size
|
||||||
|
# lspci -v -s 00:02.0
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_data_storage(self):
|
||||||
|
for sm in self.smart:
|
||||||
|
if sm.get('smartctl', {}).get('exit_status') == 1:
|
||||||
|
continue
|
||||||
|
model = sm.get('model_name')
|
||||||
|
manufacturer = None
|
||||||
|
if model and len(model.split(" ")) > 1:
|
||||||
|
mm = model.split(" ")
|
||||||
|
model = mm[-1]
|
||||||
|
manufacturer = " ".join(mm[:-1])
|
||||||
|
|
||||||
|
self.components.append(
|
||||||
|
{
|
||||||
|
"actions": self.sanitize(sm),
|
||||||
|
"type": self.get_data_storage_type(sm),
|
||||||
|
"model": model,
|
||||||
|
"manufacturer": manufacturer,
|
||||||
|
"serialNumber": sm.get('serial_number'),
|
||||||
|
"size": self.get_data_storage_size(sm),
|
||||||
|
"variant": sm.get("firmware_version"),
|
||||||
|
"interface": self.get_data_storage_interface(sm),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def sanitize(self, action):
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_bogomips(self):
|
||||||
|
if not self.hwinfo:
|
||||||
|
return self.default
|
||||||
|
|
||||||
|
bogomips = 0
|
||||||
|
for row in self.hwinfo:
|
||||||
|
for cel in row:
|
||||||
|
if 'BogoMips' in cel:
|
||||||
|
try:
|
||||||
|
bogomips += float(cel.split(":")[-1])
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return bogomips
|
||||||
|
|
||||||
|
def get_networks(self):
|
||||||
|
networks = []
|
||||||
|
get_lshw_child(self.lshw, networks, 'network')
|
||||||
|
|
||||||
|
for c in networks:
|
||||||
|
capacity = c.get('capacity')
|
||||||
|
wireless = bool(c.get('configuration', {}).get('wireless', False))
|
||||||
|
self.components.append(
|
||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"type": "NetworkAdapter",
|
||||||
|
"model": c.get('product'),
|
||||||
|
"manufacturer": c.get('vendor'),
|
||||||
|
"serialNumber": c.get('serial'),
|
||||||
|
"speed": capacity,
|
||||||
|
"variant": c.get('version', 1),
|
||||||
|
"wireless": wireless or False,
|
||||||
|
"integrated": "PCI:0000:00" in c.get("businfo", ""),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_sound_card(self):
|
||||||
|
multimedias = []
|
||||||
|
get_lshw_child(self.lshw, multimedias, 'multimedia')
|
||||||
|
|
||||||
|
for c in multimedias:
|
||||||
|
self.components.append(
|
||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"type": "SoundCard",
|
||||||
|
"model": c.get('product'),
|
||||||
|
"manufacturer": c.get('vendor'),
|
||||||
|
"serialNumber": c.get('serial'),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_display(self): # noqa: C901
|
||||||
|
TECHS = 'CRT', 'TFT', 'LED', 'PDP', 'LCD', 'OLED', 'AMOLED'
|
||||||
|
|
||||||
|
for c in self.monitors:
|
||||||
|
resolution_width, resolution_height = (None,) * 2
|
||||||
|
refresh, serial, model, manufacturer, size = (None,) * 5
|
||||||
|
year, week, production_date = (None,) * 3
|
||||||
|
|
||||||
|
for x in c:
|
||||||
|
if "Vendor: " in x:
|
||||||
|
manufacturer = x.split('Vendor: ')[-1].strip()
|
||||||
|
if "Model: " in x:
|
||||||
|
model = x.split('Model: ')[-1].strip()
|
||||||
|
if "Serial ID: " in x:
|
||||||
|
serial = x.split('Serial ID: ')[-1].strip()
|
||||||
|
if " Resolution: " in x:
|
||||||
|
rs = x.split(' Resolution: ')[-1].strip()
|
||||||
|
if 'x' in rs:
|
||||||
|
resolution_width, resolution_height = [
|
||||||
|
int(r) for r in rs.split('x')
|
||||||
|
]
|
||||||
|
if "Frequencies: " in x:
|
||||||
|
try:
|
||||||
|
refresh = int(float(x.split(',')[-1].strip()[:-3]))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if 'Year of Manufacture' in x:
|
||||||
|
year = x.split(': ')[1]
|
||||||
|
|
||||||
|
if 'Week of Manufacture' in x:
|
||||||
|
week = x.split(': ')[1]
|
||||||
|
|
||||||
|
if "Size: " in x:
|
||||||
|
size = self.get_size_monitor(x)
|
||||||
|
technology = next((t for t in TECHS if t in c[0]), None)
|
||||||
|
|
||||||
|
if year and week:
|
||||||
|
d = '{} {} 0'.format(year, week)
|
||||||
|
production_date = datetime.strptime(d, '%Y %W %w').isoformat()
|
||||||
|
|
||||||
|
self.components.append(
|
||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"type": "Display",
|
||||||
|
"model": model,
|
||||||
|
"manufacturer": manufacturer,
|
||||||
|
"serialNumber": serial,
|
||||||
|
'size': size,
|
||||||
|
'resolutionWidth': resolution_width,
|
||||||
|
'resolutionHeight': resolution_height,
|
||||||
|
"productionDate": production_date,
|
||||||
|
'technology': technology,
|
||||||
|
'refreshRate': refresh,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_hwinfo_monitors(self):
|
||||||
|
for c in self.hwinfo:
|
||||||
|
monitor = None
|
||||||
|
external = None
|
||||||
|
for x in c:
|
||||||
|
if 'Hardware Class: monitor' in x:
|
||||||
|
monitor = c
|
||||||
|
if 'Driver Info' in x:
|
||||||
|
external = c
|
||||||
|
|
||||||
|
if monitor and not external:
|
||||||
|
self.monitors.append(c)
|
||||||
|
|
||||||
|
def get_size_monitor(self, x):
|
||||||
|
i = 1 / 25.4
|
||||||
|
t = x.split('Size: ')[-1].strip()
|
||||||
|
tt = t.split('mm')
|
||||||
|
if not tt:
|
||||||
|
return 0
|
||||||
|
sizes = tt[0].strip()
|
||||||
|
if 'x' not in sizes:
|
||||||
|
return 0
|
||||||
|
w, h = [int(x) for x in sizes.split('x')]
|
||||||
|
return "{:.2f}".format(np.sqrt(w**2 + h**2) * i)
|
||||||
|
|
||||||
|
def get_cpu_address(self, cpu):
|
||||||
|
default = 64
|
||||||
|
for ch in self.lshw.get('children', []):
|
||||||
|
for c in ch.get('children', []):
|
||||||
|
if c['class'] == 'processor':
|
||||||
|
return c.get('width', default)
|
||||||
|
return default
|
||||||
|
|
||||||
|
def get_usb_num(self):
|
||||||
|
return len(
|
||||||
|
[
|
||||||
|
u
|
||||||
|
for u in self.dmi.get("Port Connector")
|
||||||
|
if "USB" in u.get("Port Type", "").upper()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_serial_num(self):
|
||||||
|
return len(
|
||||||
|
[
|
||||||
|
u
|
||||||
|
for u in self.dmi.get("Port Connector")
|
||||||
|
if "SERIAL" in u.get("Port Type", "").upper()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_firmware_num(self):
|
||||||
|
return len(
|
||||||
|
[
|
||||||
|
u
|
||||||
|
for u in self.dmi.get("Port Connector")
|
||||||
|
if "FIRMWARE" in u.get("Port Type", "").upper()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_pcmcia_num(self):
|
||||||
|
return len(
|
||||||
|
[
|
||||||
|
u
|
||||||
|
for u in self.dmi.get("Port Connector")
|
||||||
|
if "PCMCIA" in u.get("Port Type", "").upper()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_bios_date(self):
|
||||||
|
return self.dmi.get("BIOS")[0].get("Release Date", self.default)
|
||||||
|
|
||||||
|
def get_firmware(self):
|
||||||
|
return self.dmi.get("BIOS")[0].get("Firmware Revision", '1')
|
||||||
|
|
||||||
|
def get_max_ram_size(self):
|
||||||
|
size = 0
|
||||||
|
for slot in self.dmi.get("Physical Memory Array"):
|
||||||
|
capacity = slot.get("Maximum Capacity", '0').split(" ")[0]
|
||||||
|
size += int(capacity)
|
||||||
|
|
||||||
|
return size
|
||||||
|
|
||||||
|
def get_ram_slots(self):
|
||||||
|
slots = 0
|
||||||
|
for x in self.dmi.get("Physical Memory Array"):
|
||||||
|
slots += int(x.get("Number Of Devices", 0))
|
||||||
|
return slots
|
||||||
|
|
||||||
|
def get_ram_size(self, ram):
|
||||||
|
memory = ram.get("Size", "0")
|
||||||
|
return memory
|
||||||
|
|
||||||
|
def get_ram_speed(self, ram):
|
||||||
|
size = ram.get("Speed", "0")
|
||||||
|
return size
|
||||||
|
|
||||||
|
def get_cpu_speed(self, cpu):
|
||||||
|
speed = cpu.get('Max Speed', "0")
|
||||||
|
return speed
|
||||||
|
|
||||||
|
def get_sku(self):
|
||||||
|
return self.dmi.get("System")[0].get("SKU Number", self.default).strip()
|
||||||
|
|
||||||
|
def get_version(self):
|
||||||
|
return self.dmi.get("System")[0].get("Version", self.default).strip()
|
||||||
|
|
||||||
|
def get_uuid(self):
|
||||||
|
return self.dmi.get("System")[0].get("UUID", '').strip()
|
||||||
|
|
||||||
|
def get_family(self):
|
||||||
|
return self.dmi.get("System")[0].get("Family", '')
|
||||||
|
|
||||||
|
def get_chassis(self):
|
||||||
|
return self.dmi.get("Chassis")[0].get("Type", '_virtual')
|
||||||
|
|
||||||
|
def get_type(self):
|
||||||
|
chassis_type = self.get_chassis()
|
||||||
|
return self.translation_to_devicehub(chassis_type)
|
||||||
|
|
||||||
|
def translation_to_devicehub(self, original_type):
|
||||||
|
lower_type = original_type.lower()
|
||||||
|
CHASSIS_TYPE = {
|
||||||
|
'Desktop': [
|
||||||
|
'desktop',
|
||||||
|
'low-profile',
|
||||||
|
'tower',
|
||||||
|
'docking',
|
||||||
|
'all-in-one',
|
||||||
|
'pizzabox',
|
||||||
|
'mini-tower',
|
||||||
|
'space-saving',
|
||||||
|
'lunchbox',
|
||||||
|
'mini',
|
||||||
|
'stick',
|
||||||
|
],
|
||||||
|
'Laptop': [
|
||||||
|
'portable',
|
||||||
|
'laptop',
|
||||||
|
'convertible',
|
||||||
|
'tablet',
|
||||||
|
'detachable',
|
||||||
|
'notebook',
|
||||||
|
'handheld',
|
||||||
|
'sub-notebook',
|
||||||
|
],
|
||||||
|
'Server': ['server'],
|
||||||
|
'Computer': ['_virtual'],
|
||||||
|
}
|
||||||
|
for k, v in CHASSIS_TYPE.items():
|
||||||
|
if lower_type in v:
|
||||||
|
return k
|
||||||
|
return self.default
|
||||||
|
|
||||||
|
def get_chassis_dh(self):
|
||||||
|
chassis = self.get_chassis()
|
||||||
|
lower_type = chassis.lower()
|
||||||
|
for k, v in CHASSIS_DH.items():
|
||||||
|
if lower_type in v:
|
||||||
|
return k
|
||||||
|
return self.default
|
||||||
|
|
||||||
|
def get_data_storage_type(self, x):
|
||||||
|
# TODO @cayop add more SSDS types
|
||||||
|
SSDS = ["nvme"]
|
||||||
|
SSD = 'SolidStateDrive'
|
||||||
|
HDD = 'HardDrive'
|
||||||
|
type_dev = x.get('device', {}).get('type')
|
||||||
|
trim = x.get('trim', {}).get("supported") in [True, "true"]
|
||||||
|
return SSD if type_dev in SSDS or trim else HDD
|
||||||
|
|
||||||
|
def get_data_storage_interface(self, x):
|
||||||
|
interface = x.get('device', {}).get('protocol', 'ATA')
|
||||||
|
if interface.upper() in DATASTORAGEINTERFACE:
|
||||||
|
return interface.upper()
|
||||||
|
|
||||||
|
txt = "Sid: {}, interface {} is not in DataStorageInterface Enum".format(
|
||||||
|
self.sid, interface
|
||||||
|
)
|
||||||
|
self.errors("{}".format(err))
|
||||||
|
|
||||||
|
def get_data_storage_size(self, x):
|
||||||
|
return x.get('user_capacity', {}).get('bytes')
|
||||||
|
|
||||||
|
def parse_hwinfo(self):
|
||||||
|
hw_blocks = self.hwinfo_raw.split("\n\n")
|
||||||
|
return [x.split("\n") for x in hw_blocks]
|
||||||
|
|
||||||
|
def loads(self, x):
|
||||||
|
if isinstance(x, str):
|
||||||
|
try:
|
||||||
|
return json.loads(x)
|
||||||
|
except Exception as ss:
|
||||||
|
print("WARNING!! {}".format(ss))
|
||||||
|
return {}
|
||||||
|
return x
|
||||||
|
|
||||||
|
def errors(self, txt=None):
|
||||||
|
if not txt:
|
||||||
|
return self._errors
|
||||||
|
|
||||||
|
logger.error(txt)
|
||||||
|
self._errors.append(txt)
|
||||||
|
|
|
@ -66,8 +66,17 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% bootstrap_form form %}
|
{% bootstrap_form form %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<a class="btn btn-grey" href="{% url 'dashboard:unassigned_devices' %}">{% translate "Cancel" %}</a>
|
<div class="row">
|
||||||
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
|
<div class="col">
|
||||||
|
<a class="btn btn-grey" href="">{% translate "Cancel" %}</a>
|
||||||
|
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
|
||||||
|
</div>
|
||||||
|
{% if form.tag.value %}
|
||||||
|
<div class="col-1">
|
||||||
|
<a class="btn btn-yellow" href="{% url 'evidence:delete_annotation' form.pk %}">{% translate "Delete" %}</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,4 +19,5 @@ urlpatterns = [
|
||||||
path("import", views.ImportView.as_view(), name="import"),
|
path("import", views.ImportView.as_view(), name="import"),
|
||||||
path("<uuid:pk>", views.EvidenceView.as_view(), name="details"),
|
path("<uuid:pk>", views.EvidenceView.as_view(), name="details"),
|
||||||
path("<uuid:pk>/download", views.DownloadEvidenceView.as_view(), name="download"),
|
path("<uuid:pk>/download", views.DownloadEvidenceView.as_view(), name="download"),
|
||||||
|
path('annotation/<int:pk>/del', views.AnnotationDeleteView.as_view(), name='delete_annotation'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from urllib.parse import urlparse
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.shortcuts import get_object_or_404, redirect, Http404
|
||||||
from django.views.generic.base import TemplateView
|
from django.views.generic.base import TemplateView
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy, resolve
|
||||||
from django.views.generic.edit import (
|
from django.views.generic.edit import (
|
||||||
|
DeleteView,
|
||||||
FormView,
|
FormView,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
from dashboard.mixins import DashboardView, Http403
|
from dashboard.mixins import DashboardView, Http403
|
||||||
from evidence.models import Evidence
|
from evidence.models import Evidence, Annotation
|
||||||
from evidence.forms import UploadForm, UserTagForm, ImportForm
|
from evidence.forms import UploadForm, UserTagForm, ImportForm
|
||||||
# from django.shortcuts import render
|
# from django.shortcuts import render
|
||||||
# from rest_framework import viewsets
|
# from rest_framework import viewsets
|
||||||
|
@ -106,6 +110,7 @@ class EvidenceView(DashboardView, FormView):
|
||||||
self.pk = self.kwargs.get('pk')
|
self.pk = self.kwargs.get('pk')
|
||||||
kwargs = super().get_form_kwargs()
|
kwargs = super().get_form_kwargs()
|
||||||
kwargs['uuid'] = self.pk
|
kwargs['uuid'] = self.pk
|
||||||
|
kwargs['user'] = self.request.user
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
|
@ -135,3 +140,30 @@ class DownloadEvidenceView(DashboardView, TemplateView):
|
||||||
response = HttpResponse(data, content_type="application/json")
|
response = HttpResponse(data, content_type="application/json")
|
||||||
response['Content-Disposition'] = 'attachment; filename={}'.format("credential.json")
|
response['Content-Disposition'] = 'attachment; filename={}'.format("credential.json")
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class AnnotationDeleteView(DashboardView, DeleteView):
|
||||||
|
model = Annotation
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
self.pk = kwargs['pk']
|
||||||
|
|
||||||
|
try:
|
||||||
|
referer = self.request.META["HTTP_REFERER"]
|
||||||
|
path_referer = urlparse(referer).path
|
||||||
|
resolver_match = resolve(path_referer)
|
||||||
|
url_name = resolver_match.view_name
|
||||||
|
kwargs_view = resolver_match.kwargs
|
||||||
|
except:
|
||||||
|
# if is not possible resolve the reference path return 404
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
self.object = get_object_or_404(
|
||||||
|
self.model,
|
||||||
|
pk=self.pk,
|
||||||
|
owner=self.request.user.institution
|
||||||
|
)
|
||||||
|
self.object.delete()
|
||||||
|
|
||||||
|
|
||||||
|
return redirect(url_name, **kwargs_view)
|
||||||
|
|
|
@ -10,7 +10,7 @@ import xapian
|
||||||
# indexer.set_stemmer(stemmer)
|
# indexer.set_stemmer(stemmer)
|
||||||
|
|
||||||
|
|
||||||
def search(qs, offset=0, limit=10):
|
def search(institution, qs, offset=0, limit=10):
|
||||||
database = xapian.Database("db")
|
database = xapian.Database("db")
|
||||||
|
|
||||||
qp = xapian.QueryParser()
|
qp = xapian.QueryParser()
|
||||||
|
@ -18,18 +18,21 @@ def search(qs, offset=0, limit=10):
|
||||||
qp.set_stemmer(xapian.Stem("english"))
|
qp.set_stemmer(xapian.Stem("english"))
|
||||||
qp.set_stemming_strategy(xapian.QueryParser.STEM_SOME)
|
qp.set_stemming_strategy(xapian.QueryParser.STEM_SOME)
|
||||||
qp.add_prefix("uuid", "uuid")
|
qp.add_prefix("uuid", "uuid")
|
||||||
# qp.add_prefix("snapshot", "snapshot")
|
|
||||||
query = qp.parse_query(qs)
|
query = qp.parse_query(qs)
|
||||||
|
institution_term = "U{}".format(institution.id)
|
||||||
|
final_query = xapian.Query(
|
||||||
|
xapian.Query.OP_AND, query, xapian.Query(institution_term)
|
||||||
|
)
|
||||||
enquire = xapian.Enquire(database)
|
enquire = xapian.Enquire(database)
|
||||||
enquire.set_query(query)
|
enquire.set_query(final_query)
|
||||||
matches = enquire.get_mset(offset, limit)
|
matches = enquire.get_mset(offset, limit)
|
||||||
return matches
|
return matches
|
||||||
|
|
||||||
|
|
||||||
def index(uuid, snap):
|
def index(institution, uuid, snap):
|
||||||
uuid = 'uuid:"{}"'.format(uuid)
|
uuid = 'uuid:"{}"'.format(uuid)
|
||||||
try:
|
try:
|
||||||
matches = search(uuid, limit=1)
|
matches = search(institution, uuid, limit=1)
|
||||||
if matches.size() > 0:
|
if matches.size() > 0:
|
||||||
return
|
return
|
||||||
except (xapian.DatabaseNotFoundError, xapian.DatabaseOpeningError):
|
except (xapian.DatabaseNotFoundError, xapian.DatabaseOpeningError):
|
||||||
|
@ -47,5 +50,7 @@ def index(uuid, snap):
|
||||||
indexer.index_text(snap)
|
indexer.index_text(snap)
|
||||||
indexer.index_text(uuid, 10, "uuid")
|
indexer.index_text(uuid, 10, "uuid")
|
||||||
# indexer.index_text(snap, 1, "snapshot")
|
# indexer.index_text(snap, 1, "snapshot")
|
||||||
|
institution_term = "U{}".format(institution.id)
|
||||||
|
doc.add_term(institution_term)
|
||||||
|
|
||||||
database.add_document(doc)
|
database.add_document(doc)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "auth/login_base.html" %}
|
{% extends "login_base.html" %}
|
||||||
{% load i18n django_bootstrap5 %}
|
{% load i18n django_bootstrap5 %}
|
||||||
|
|
||||||
{% block login_content %}
|
{% block login_content %}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
{% load i18n %}{% autoescape off %}
|
||||||
|
{% trans "DeviceHub" as site %}
|
||||||
|
<p>
|
||||||
|
{% blocktrans %}You're receiving this email because your user account at {{site}} has been activated.{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{% trans "Your username is:" %} {{ user.username }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{% trans "Please go to the following page and choose a password:" %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{% block reset_link %}
|
||||||
|
<a href="{{ protocol }}://{{ domain }}{% url 'login:password_reset_confirm' uidb64=uid token=token %}">
|
||||||
|
{{ protocol }}://{{ domain }}{% url 'login:password_reset_confirm' uidb64=uid token=token %}
|
||||||
|
</a>
|
||||||
|
{% endblock %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{% trans "Thanks for using our site!" %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{% blocktrans %}The {{site}} team{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{% endautoescape %}
|
|
@ -0,0 +1,19 @@
|
||||||
|
{% load i18n %}{% autoescape off %}
|
||||||
|
|
||||||
|
{% trans "DeviceHub" as site %}
|
||||||
|
|
||||||
|
{% blocktrans %}You're receiving this email because your user account at {{site}} has been activated.{% endblocktrans %}
|
||||||
|
|
||||||
|
{% trans "Your username is:" %} {{ user.username }}
|
||||||
|
|
||||||
|
{% trans "Please go to the following page and choose a password:" %}
|
||||||
|
{% block reset_link %}
|
||||||
|
{{ protocol }}://{{ domain }}{% url 'login:password_reset_confirm' uidb64=uid token=token %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% trans "Thanks for using our site!" %}
|
||||||
|
|
||||||
|
{% blocktrans %}The {{site}} team{% endblocktrans %}
|
||||||
|
|
||||||
|
{% endautoescape %}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{% load i18n %}{% autoescape off %}
|
||||||
|
{% trans "IdHub" as site %}
|
||||||
|
{% blocktrans %}User activation on {{site}}{% endblocktrans %}
|
||||||
|
{% endautoescape %}
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "auth/login_base.html" %}
|
{% extends "login_base.html" %}
|
||||||
{% load i18n django_bootstrap5 %}
|
{% load i18n django_bootstrap5 %}
|
||||||
|
|
||||||
{% block login_content %}
|
{% block login_content %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "auth/login_base.html" %}
|
{% extends "login_base.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block login_content %}
|
{% block login_content %}
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
{% block reset_link %}
|
{% block reset_link %}
|
||||||
<a href="{{ protocol }}://{{ domain }}{% url 'idhub:password_reset_confirm' uidb64=uid token=token %}">
|
<a href="{{ protocol }}://{{ domain }}{% url 'login:password_reset_confirm' uidb64=uid token=token %}">
|
||||||
{{ protocol }}://{{ domain }}{% url 'idhub:password_reset_confirm' uidb64=uid token=token %}
|
{{ protocol }}://{{ domain }}{% url 'login:password_reset_confirm' uidb64=uid token=token %}
|
||||||
</a>
|
</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
{% trans "Please go to the following page and choose a new password:" %}
|
{% trans "Please go to the following page and choose a new password:" %}
|
||||||
{% block reset_link %}
|
{% block reset_link %}
|
||||||
{{ protocol }}://{{ domain }}{% url 'idhub:password_reset_confirm' uidb64=uid token=token %}
|
{{ protocol }}://{{ domain }}{% url 'login:password_reset_confirm' uidb64=uid token=token %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% trans "Your username, in case you've forgotten:" %} {{ user.username }}
|
{% trans "Your username, in case you've forgotten:" %} {{ user.username }}
|
||||||
|
|
||||||
|
|
|
@ -65,8 +65,10 @@ class PasswordResetView(auth_views.PasswordResetView):
|
||||||
success_url = reverse_lazy('login:password_reset_done')
|
success_url = reverse_lazy('login:password_reset_done')
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
|
import pdb; pdb.set_trace()
|
||||||
try:
|
try:
|
||||||
return super().form_valid(form)
|
response = super().form_valid(form)
|
||||||
|
return response
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
return HttpResponseRedirect(self.success_url)
|
return HttpResponseRedirect(self.success_url)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 5.0.6 on 2024-07-27 16:23
|
# Generated by Django 5.0.6 on 2024-10-10 10:35
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -10,6 +10,7 @@ class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
("user", "0001_initial"),
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -36,6 +37,15 @@ class Migration(migrations.Migration):
|
||||||
"owner",
|
"owner",
|
||||||
models.ForeignKey(
|
models.ForeignKey(
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="user.institution",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
to=settings.AUTH_USER_MODEL,
|
to=settings.AUTH_USER_MODEL,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -62,6 +72,51 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="LotAnnotation",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("created", models.DateTimeField(auto_now_add=True)),
|
||||||
|
(
|
||||||
|
"type",
|
||||||
|
models.SmallIntegerField(
|
||||||
|
choices=[(0, "System"), (1, "User"), (2, "Document")]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("key", models.CharField(max_length=256)),
|
||||||
|
("value", models.CharField(max_length=256)),
|
||||||
|
(
|
||||||
|
"lot",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE, to="lot.lot"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"owner",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="user.institution",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name="LotTag",
|
name="LotTag",
|
||||||
fields=[
|
fields=[
|
||||||
|
@ -79,6 +134,15 @@ class Migration(migrations.Migration):
|
||||||
"owner",
|
"owner",
|
||||||
models.ForeignKey(
|
models.ForeignKey(
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="user.institution",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
to=settings.AUTH_USER_MODEL,
|
to=settings.AUTH_USER_MODEL,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
# Generated by Django 5.0.6 on 2024-07-29 15:37
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("lot", "0001_initial"),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="LotAnnotation",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.BigAutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("created", models.DateTimeField(auto_now_add=True)),
|
|
||||||
(
|
|
||||||
"type",
|
|
||||||
models.SmallIntegerField(
|
|
||||||
choices=[(0, "System"), (1, "User"), (2, "Document")]
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("key", models.CharField(max_length=256)),
|
|
||||||
("value", models.CharField(max_length=256)),
|
|
||||||
(
|
|
||||||
"lot",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE, to="lot.lot"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"owner",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to=settings.AUTH_USER_MODEL,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,36 +0,0 @@
|
||||||
# Generated by Django 5.0.6 on 2024-09-18 10:55
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("lot", "0002_lotannotation"),
|
|
||||||
("user", "0001_initial"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="lot",
|
|
||||||
name="owner",
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE, to="user.institution"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="lotannotation",
|
|
||||||
name="owner",
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE, to="user.institution"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="lottag",
|
|
||||||
name="owner",
|
|
||||||
field=models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE, to="user.institution"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -6,7 +6,7 @@ from utils.constants import (
|
||||||
STR_EXTEND_SIZE,
|
STR_EXTEND_SIZE,
|
||||||
)
|
)
|
||||||
|
|
||||||
from user.models import Institution
|
from user.models import User, Institution
|
||||||
# from device.models import Device
|
# from device.models import Device
|
||||||
# from evidence.models import Annotation
|
# from evidence.models import Annotation
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ from user.models import Institution
|
||||||
class LotTag(models.Model):
|
class LotTag(models.Model):
|
||||||
name = models.CharField(max_length=STR_SIZE, blank=False, null=False)
|
name = models.CharField(max_length=STR_SIZE, blank=False, null=False)
|
||||||
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
|
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
|
||||||
|
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -32,6 +33,7 @@ class Lot(models.Model):
|
||||||
description = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
description = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
||||||
closed = models.BooleanField(default=True)
|
closed = models.BooleanField(default=True)
|
||||||
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
|
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
|
||||||
|
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
type = models.ForeignKey(LotTag, on_delete=models.CASCADE)
|
type = models.ForeignKey(LotTag, on_delete=models.CASCADE)
|
||||||
|
|
||||||
def add(self, v):
|
def add(self, v):
|
||||||
|
@ -53,6 +55,7 @@ class LotAnnotation(models.Model):
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
lot = models.ForeignKey(Lot, on_delete=models.CASCADE)
|
lot = models.ForeignKey(Lot, on_delete=models.CASCADE)
|
||||||
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
|
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
|
||||||
|
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
type = models.SmallIntegerField(choices=Type)
|
type = models.SmallIntegerField(choices=Type)
|
||||||
key = models.CharField(max_length=STR_EXTEND_SIZE)
|
key = models.CharField(max_length=STR_EXTEND_SIZE)
|
||||||
value = models.CharField(max_length=STR_EXTEND_SIZE)
|
value = models.CharField(max_length=STR_EXTEND_SIZE)
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<button type="submit">Save</button>
|
<button class="btn btn-green-admin" type="submit">Save</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -29,6 +29,7 @@ class NewLotView(DashboardView, CreateView):
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.owner = self.request.user.institution
|
form.instance.owner = self.request.user.institution
|
||||||
|
form.instance.user = self.request.user
|
||||||
response = super().form_valid(form)
|
response = super().form_valid(form)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@ -68,7 +69,11 @@ class EditLotView(DashboardView, UpdateView):
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
pk = self.kwargs.get('pk')
|
pk = self.kwargs.get('pk')
|
||||||
self.object = get_object_or_404(self.model, pk=pk)
|
self.object = get_object_or_404(
|
||||||
|
self.model,
|
||||||
|
owner=self.request.user.institution,
|
||||||
|
pk=pk,
|
||||||
|
)
|
||||||
# self.success_url = reverse_lazy('dashbiard:lot', args=[pk])
|
# self.success_url = reverse_lazy('dashbiard:lot', args=[pk])
|
||||||
kwargs = super().get_form_kwargs()
|
kwargs = super().get_form_kwargs()
|
||||||
return kwargs
|
return kwargs
|
||||||
|
@ -145,6 +150,7 @@ class LotAddDocumentView(DashboardView, CreateView):
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.owner = self.request.user.institution
|
form.instance.owner = self.request.user.institution
|
||||||
|
form.instance.user = self.request.user
|
||||||
form.instance.lot = self.lot
|
form.instance.lot = self.lot
|
||||||
form.instance.type = LotAnnotation.Type.DOCUMENT
|
form.instance.type = LotAnnotation.Type.DOCUMENT
|
||||||
response = super().form_valid(form)
|
response = super().form_valid(form)
|
||||||
|
@ -214,6 +220,7 @@ class LotAddAnnotationView(DashboardView, CreateView):
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.owner = self.request.user.institution
|
form.instance.owner = self.request.user.institution
|
||||||
|
form.instance.user = self.request.user
|
||||||
form.instance.lot = self.lot
|
form.instance.lot = self.lot
|
||||||
form.instance.type = LotAnnotation.Type.USER
|
form.instance.type = LotAnnotation.Type.USER
|
||||||
response = super().form_valid(form)
|
response = super().form_valid(form)
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
4 login
|
|
||||||
8 device models
|
|
||||||
4 inventory template
|
|
||||||
4 upload snapshots
|
|
||||||
16 parse snapshots
|
|
||||||
16 build devices
|
|
||||||
8 action models
|
|
||||||
8 view actions
|
|
||||||
8 lot models
|
|
||||||
16 view lots
|
|
||||||
8 edit device
|
|
||||||
16 documents
|
|
||||||
8 tag
|
|
||||||
8 print label
|
|
||||||
4 server erase
|
|
||||||
4 profile
|
|
||||||
8 erase views
|
|
||||||
8 erase certificate
|
|
||||||
4 inventory snapshots
|
|
||||||
8 search
|
|
||||||
join split devices
|
|
||||||
|
|
||||||
168 horas => 21 dias
|
|
|
@ -10,3 +10,4 @@ pandas==2.2.2
|
||||||
xlrd==2.0.1
|
xlrd==2.0.1
|
||||||
odfpy==1.4.1
|
odfpy==1.4.1
|
||||||
pytz==2024.2
|
pytz==2024.2
|
||||||
|
|
||||||
|
|
2
reset.sh
2
reset.sh
|
@ -1,5 +1,5 @@
|
||||||
rm db/*
|
rm db/*
|
||||||
python3 manage.py migrate
|
python3 manage.py migrate
|
||||||
python3 manage.py add_institution Pangea
|
python3 manage.py add_institution Pangea
|
||||||
python3 manage.py add_user Pangea user@example.org 1234
|
python3 manage.py add_user Pangea user@example.org 1234 True
|
||||||
python3 manage.py up_snapshots example/snapshots/ user@example.org
|
python3 manage.py up_snapshots example/snapshots/ user@example.org
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsForm(forms.Form):
|
||||||
|
token = forms.ChoiceField(
|
||||||
|
choices = []
|
||||||
|
)
|
||||||
|
erasure = forms.ChoiceField(
|
||||||
|
choices = [(0, 'Not erasure'),
|
||||||
|
('basic', 'Erasure Basic'),
|
||||||
|
('baseline', 'Erasure Baseline'),
|
||||||
|
('enhanced', 'Erasure Enhanced'),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
tokens = kwargs.pop('tokens')
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
tk = [(str(x.token), x.tag) for x in tokens]
|
||||||
|
self.fields['token'].choices = tk
|
|
@ -1,6 +1,9 @@
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from user.models import Institution
|
from user.models import Institution
|
||||||
|
from api.models import Token
|
||||||
|
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
@ -13,18 +16,24 @@ class Command(BaseCommand):
|
||||||
parser.add_argument('institution', type=str, help='institution')
|
parser.add_argument('institution', type=str, help='institution')
|
||||||
parser.add_argument('email', type=str, help='email')
|
parser.add_argument('email', type=str, help='email')
|
||||||
parser.add_argument('password', type=str, help='password')
|
parser.add_argument('password', type=str, help='password')
|
||||||
|
parser.add_argument('is_admin', nargs='?', default=False, type=str, help='is admin')
|
||||||
|
|
||||||
def handle(self, *args, **kwargs):
|
def handle(self, *args, **kwargs):
|
||||||
self.email = kwargs['email']
|
email = kwargs['email']
|
||||||
self.password = kwargs['password']
|
password = kwargs['password']
|
||||||
self.institution = Institution.objects.get(name=kwargs['institution'])
|
is_admin = kwargs['is_admin']
|
||||||
self.create_user()
|
institution = Institution.objects.get(name=kwargs['institution'])
|
||||||
|
self.create_user(institution, email, password, is_admin)
|
||||||
|
|
||||||
def create_user(self):
|
def create_user(self, institution, email, password, is_admin):
|
||||||
self.u = User.objects.create(
|
self.u = User.objects.create(
|
||||||
institution=self.institution,
|
institution=institution,
|
||||||
email=self.email,
|
email=email,
|
||||||
password=self.password
|
password=password,
|
||||||
|
is_admin=is_admin,
|
||||||
)
|
)
|
||||||
self.u.set_password(self.password)
|
self.u.set_password(password)
|
||||||
self.u.save()
|
self.u.save()
|
||||||
|
token = uuid4()
|
||||||
|
Token.objects.create(token=token, owner=self.u)
|
||||||
|
print(f"TOKEN: {token}")
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col">
|
||||||
|
<h3>{{ subtitle }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="col text-center">
|
||||||
|
{{ user.email }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<a class="nav-link fw-bold" href="{% url 'api:tokens' %}">
|
||||||
|
{% translate 'Admin your Tokens' %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<a class="nav-link fw-bold" href="{% url 'user:settings' %}">
|
||||||
|
{% translate 'Download a settings file' %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,32 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h3>{{ subtitle }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% load django_bootstrap5 %}
|
||||||
|
<form role="form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% if form.errors %}
|
||||||
|
<div class="alert alert-danger alert-icon alert-icon-border alert-dismissible" role="alert">
|
||||||
|
<div class="icon"><span class="mdi mdi-close-circle-o"></span></div>
|
||||||
|
<div class="message">
|
||||||
|
{% for field, error in form.errors.items %}
|
||||||
|
{{ error }}<br />
|
||||||
|
{% endfor %}
|
||||||
|
<button class="btn-close" type="button" data-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
<div class="form-actions-no-box">
|
||||||
|
<a class="btn btn-grey" href="{% url 'user:panel' %}">{% translate "Cancel" %}</a>
|
||||||
|
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,6 @@
|
||||||
|
[settings]
|
||||||
|
url = {{ url }}
|
||||||
|
token = {{ token }}
|
||||||
|
erase = {{ erasure }}
|
||||||
|
legacy = false
|
||||||
|
# path = /path/to/save
|
|
@ -0,0 +1,6 @@
|
||||||
|
[settings]
|
||||||
|
url = {{ url }}
|
||||||
|
token = {{ token }}
|
||||||
|
legacy = true
|
||||||
|
# erase = {{ erasure }}
|
||||||
|
# path = /path/to/save
|
|
@ -0,0 +1,9 @@
|
||||||
|
from django.urls import path
|
||||||
|
from user import views
|
||||||
|
|
||||||
|
app_name = 'user'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("panel/", views.PanelView.as_view(), name="panel"),
|
||||||
|
path("settings/", views.SettingsView.as_view(), name="settings"),
|
||||||
|
]
|
|
@ -1,6 +1,50 @@
|
||||||
|
from decouple import config
|
||||||
|
from django.urls import reverse, reverse_lazy
|
||||||
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from dashboard.mixins import InventaryMixin, DetailsMixin
|
from django.views.generic.base import TemplateView
|
||||||
|
from dashboard.mixins import DashboardView
|
||||||
|
from django.views.generic.edit import (
|
||||||
|
FormView,
|
||||||
|
)
|
||||||
|
|
||||||
|
from user.forms import SettingsForm
|
||||||
|
from api.models import Token
|
||||||
|
|
||||||
|
|
||||||
|
class PanelView(DashboardView, TemplateView):
|
||||||
|
template_name = "panel.html"
|
||||||
|
title = _("User")
|
||||||
|
breadcrumb = "User / Panel"
|
||||||
|
subtitle = "User panel"
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsView(DashboardView, FormView):
|
||||||
|
template_name = "settings.html"
|
||||||
|
title = _("Download Settings")
|
||||||
|
breadcrumb = "user / workbench / settings"
|
||||||
|
form_class = SettingsForm
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
cleaned_data = form.cleaned_data.copy()
|
||||||
|
settings_tmpl = "settings.ini"
|
||||||
|
path = reverse("api:new_snapshot")
|
||||||
|
cleaned_data['url'] = self.request.build_absolute_uri(path)
|
||||||
|
|
||||||
|
if config("LEGACY", False):
|
||||||
|
cleaned_data['token'] = config.get('TOKEN_LEGACY', '')
|
||||||
|
cleaned_data['url'] = config.get('URL_LEGACY', '')
|
||||||
|
settings_tmpl = "settings_legacy.ini"
|
||||||
|
|
||||||
|
data = render(self.request, settings_tmpl, cleaned_data)
|
||||||
|
response = HttpResponse(data.content, content_type="application/text")
|
||||||
|
response['Content-Disposition'] = 'attachment; filename={}'.format("settings.ini")
|
||||||
|
return response
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
tokens = Token.objects.filter(owner=self.request.user)
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
kwargs['tokens'] = tokens
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
|
|
@ -38,3 +38,11 @@ CHASSIS_DH = {
|
||||||
'Tablet': {'tablet'},
|
'Tablet': {'tablet'},
|
||||||
'Virtual': {'_virtual'},
|
'Virtual': {'_virtual'},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DATASTORAGEINTERFACE = [
|
||||||
|
'ATA',
|
||||||
|
'USB',
|
||||||
|
'PCI',
|
||||||
|
'NVME',
|
||||||
|
]
|
||||||
|
|
|
@ -70,6 +70,7 @@ def create_annotation(doc, user, commit=False):
|
||||||
data = {
|
data = {
|
||||||
'uuid': doc['uuid'],
|
'uuid': doc['uuid'],
|
||||||
'owner': user.institution,
|
'owner': user.institution,
|
||||||
|
'user': user,
|
||||||
'type': Annotation.Type.SYSTEM,
|
'type': Annotation.Type.SYSTEM,
|
||||||
'key': 'CUSTOMER_ID',
|
'key': 'CUSTOMER_ID',
|
||||||
'value': doc['CUSTOMER_ID'],
|
'value': doc['CUSTOMER_ID'],
|
||||||
|
@ -80,10 +81,10 @@ def create_annotation(doc, user, commit=False):
|
||||||
return Annotation(**data)
|
return Annotation(**data)
|
||||||
|
|
||||||
|
|
||||||
def create_index(doc):
|
def create_index(doc, user):
|
||||||
if not doc or not doc.get('uuid'):
|
if not doc or not doc.get('uuid'):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
_uuid = doc['uuid']
|
_uuid = doc['uuid']
|
||||||
ev = json.dumps(doc)
|
ev = json.dumps(doc)
|
||||||
index(_uuid, ev)
|
index(user.institution, _uuid, ev)
|
||||||
|
|
Loading…
Reference in New Issue