Compare commits
25 Commits
main
...
tau_showcl
Author | SHA1 | Date |
---|---|---|
pedro | 1b6767b703 | |
Cayo Puigdefabregas | 59f6ac705c | |
Cayo Puigdefabregas | 54ef0bb41c | |
pedro | bab540187c | |
pedro | b024dd1a11 | |
pedro | 665310651c | |
pedro | 765b017ac2 | |
Cayo Puigdefabregas | 2ed33270ed | |
Cayo Puigdefabregas | d277e6cbaa | |
Cayo Puigdefabregas | 756570c7b4 | |
Cayo Puigdefabregas | ebe80ffea6 | |
Cayo Puigdefabregas | bdaaa2740d | |
Cayo Puigdefabregas | b36636d28e | |
Cayo Puigdefabregas | d144cef7b8 | |
pedro | b534d69750 | |
pedro | ecb0877b36 | |
pedro | d491fdd47e | |
pedro | 1834aab36e | |
pedro | fa59feaf77 | |
Cayo Puigdefabregas | f5f0ea3c35 | |
Cayo Puigdefabregas | 2f4a85f79b | |
pedro | a1ffdb5ed6 | |
Cayo Puigdefabregas | ad59484b03 | |
Cayo Puigdefabregas | 13c78fb3c1 | |
Cayo Puigdefabregas | 029e694958 |
|
@ -0,0 +1,2 @@
|
||||||
|
DOMAIN=localhost
|
||||||
|
DEMO=false
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
|
@ -0,0 +1,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ApiConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'api'
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
# Generated by Django 5.0.6 on 2024-09-19 15:09
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Token",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("token", models.UUIDField()),
|
||||||
|
(
|
||||||
|
"owner",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,9 @@
|
||||||
|
from django.db import models
|
||||||
|
from user.models import User
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
|
|
||||||
|
|
||||||
|
class Token(models.Model):
|
||||||
|
token = models.UUIDField()
|
||||||
|
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
@ -0,0 +1,67 @@
|
||||||
|
import django_tables2 as tables
|
||||||
|
from django.utils.html import format_html
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from api.models import Token
|
||||||
|
|
||||||
|
|
||||||
|
class ButtonColumn(tables.Column):
|
||||||
|
attrs = {
|
||||||
|
"a": {
|
||||||
|
"type": "button",
|
||||||
|
"class": "text-danger",
|
||||||
|
"title": "Remove",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# it makes no sense to order a column of buttons
|
||||||
|
orderable = False
|
||||||
|
# django_tables will only call the render function if it doesn't find
|
||||||
|
# any empty values in the data, so we stop it from matching the data
|
||||||
|
# to any value considered empty
|
||||||
|
empty_values = ()
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
return format_html('<i class="bi bi-trash"></i>')
|
||||||
|
|
||||||
|
|
||||||
|
class TokensTable(tables.Table):
|
||||||
|
delete = ButtonColumn(
|
||||||
|
verbose_name=_("Delete"),
|
||||||
|
linkify={
|
||||||
|
"viewname": "api:delete_token",
|
||||||
|
"args": [tables.A("pk")]
|
||||||
|
},
|
||||||
|
orderable=False
|
||||||
|
)
|
||||||
|
|
||||||
|
token = tables.Column(verbose_name=_("Token"), empty_values=())
|
||||||
|
|
||||||
|
def render_view_user(self):
|
||||||
|
return format_html('<i class="bi bi-eye"></i>')
|
||||||
|
|
||||||
|
# def render_token(self, record):
|
||||||
|
# return record.get_memberships()
|
||||||
|
|
||||||
|
# def order_membership(self, queryset, is_descending):
|
||||||
|
# # TODO: Test that this doesn't return more rows than it should
|
||||||
|
# queryset = queryset.order_by(
|
||||||
|
# ("-" if is_descending else "") + "memberships__type"
|
||||||
|
# )
|
||||||
|
|
||||||
|
# return (queryset, True)
|
||||||
|
|
||||||
|
# def render_role(self, record):
|
||||||
|
# return record.get_roles()
|
||||||
|
|
||||||
|
# def order_role(self, queryset, is_descending):
|
||||||
|
# queryset = queryset.order_by(
|
||||||
|
# ("-" if is_descending else "") + "roles"
|
||||||
|
# )
|
||||||
|
|
||||||
|
# return (queryset, True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Token
|
||||||
|
template_name = "custom_table.html"
|
||||||
|
fields = ("token", "view_user")
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
{% load django_tables2 %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block table-wrapper %}
|
||||||
|
<div class="table-container">
|
||||||
|
{% block table %}
|
||||||
|
<table class= "table table-striped table-sm">
|
||||||
|
{% block table.thead %}
|
||||||
|
{% if table.show_header %}
|
||||||
|
<thead {{ table.attrs.thead.as_html }}>
|
||||||
|
<tr>
|
||||||
|
{% for column in table.columns %}
|
||||||
|
<th scope="col" {{ column.attrs.th.as_html }}>
|
||||||
|
{% if column.orderable %}
|
||||||
|
<a type="button" class="btn btn-grey border border-dark"
|
||||||
|
href="{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}">{{ column.header }}</a>
|
||||||
|
{% else %}
|
||||||
|
{{ column.header }}
|
||||||
|
{% endif %}
|
||||||
|
</th>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock table.thead %}
|
||||||
|
{% block table.tbody %}
|
||||||
|
<tbody {{ table.attrs.tbody.as_html }}>
|
||||||
|
{% for row in table.paginated_rows %}
|
||||||
|
{% block table.tbody.row %}
|
||||||
|
<tr {{ row.attrs.as_html }}>
|
||||||
|
{% for column, cell in row.items %}
|
||||||
|
<td {{ column.attrs.td.as_html }}>{% if column.localize == None %}{{ cell }}{% else %}{% if column.localize %}{{ cell|localize }}{% else %}{{ cell|unlocalize }}{% endif %}{% endif %}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endblock table.tbody.row %}
|
||||||
|
{% empty %}
|
||||||
|
{% if table.empty_text %}
|
||||||
|
{% block table.tbody.empty_text %}
|
||||||
|
<tr><td colspan="{{ table.columns|length }}">{{ table.empty_text }}</td></tr>
|
||||||
|
{% endblock table.tbody.empty_text %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
{% endblock table.tbody %}
|
||||||
|
{% block table.tfoot %}
|
||||||
|
{% if table.has_footer %}
|
||||||
|
<tfoot {{ table.attrs.tfoot.as_html }}>
|
||||||
|
<tr>
|
||||||
|
{% for column in table.columns %}
|
||||||
|
<td {{ column.attrs.tf.as_html }}>{{ column.footer }}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock table.tfoot %}
|
||||||
|
</table>
|
||||||
|
{% endblock table %}
|
||||||
|
|
||||||
|
{% block pagination %}
|
||||||
|
{% if table.page and table.paginator.num_pages > 1 %}
|
||||||
|
<ul class="pagination">
|
||||||
|
{% if table.page.has_previous %}
|
||||||
|
{% block pagination.previous %}
|
||||||
|
<li class="previous">
|
||||||
|
<a type="button" class="btn btn-grey border border-dark" href="{% querystring table.prefixed_page_field=table.page.previous_page_number %}">
|
||||||
|
{% trans 'Previous' %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endblock pagination.previous %}
|
||||||
|
{% endif %}
|
||||||
|
{% if table.page.has_previous or table.page.has_next %}
|
||||||
|
{% block pagination.range %}
|
||||||
|
{% for p in table.page|table_page_range:table.paginator %}
|
||||||
|
<li {% if p == table.page.number %}class="active"{% endif %}>
|
||||||
|
<a type="button" class="btn btn-grey{% if p == table.page.number %}-selected{% endif %}
|
||||||
|
border border-dark"
|
||||||
|
{% if p == '...' %}
|
||||||
|
href="#">
|
||||||
|
{% else %}
|
||||||
|
href="{% querystring table.prefixed_page_field=p %}">
|
||||||
|
{% endif %}
|
||||||
|
{{ p }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock pagination.range %}
|
||||||
|
{% endif %}
|
||||||
|
{% if table.page.has_next %}
|
||||||
|
{% block pagination.next %}
|
||||||
|
<li class="next">
|
||||||
|
<a type="button" class="btn btn-grey border border-dark" href="{% querystring table.prefixed_page_field=table.page.next_page_number %}">
|
||||||
|
{% trans 'Next' %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endblock pagination.next %}
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock pagination %}
|
||||||
|
</div>
|
||||||
|
{% endblock table-wrapper %}
|
|
@ -0,0 +1,14 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h3>
|
||||||
|
<i class="{{ icon }}"></i>
|
||||||
|
{{ subtitle }}
|
||||||
|
</h3>
|
||||||
|
{% render_table table %}
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,13 @@
|
||||||
|
from api import views
|
||||||
|
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
|
||||||
|
app_name = 'api'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('snapshot/', views.NewSnapshot, name='new_snapshot'),
|
||||||
|
path('tokens/', views.TokenView.as_view(), name='tokens'),
|
||||||
|
path('tokens/new', views.TokenNewView.as_view(), name='new_token'),
|
||||||
|
path('tokens/<int:pk>/del', views.TokenDeleteView.as_view(), name='delete_token'),
|
||||||
|
]
|
|
@ -0,0 +1,114 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
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 uuid import uuid4
|
||||||
|
|
||||||
|
from dashboard.mixins import DashboardView
|
||||||
|
from evidence.models import Annotation
|
||||||
|
from evidence.parse import Build
|
||||||
|
from user.models import User
|
||||||
|
from api.models import Token
|
||||||
|
from api.tables import TokensTable
|
||||||
|
|
||||||
|
|
||||||
|
def save_in_disk(data, user):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
def NewSnapshot(request):
|
||||||
|
# Accept only posts
|
||||||
|
if request.method != 'POST':
|
||||||
|
return JsonResponse({'error': 'Invalid request method'}, status=400)
|
||||||
|
|
||||||
|
# Authentication
|
||||||
|
# auth_header = request.headers.get('Authorization')
|
||||||
|
# if not auth_header or not auth_header.startswith('Bearer '):
|
||||||
|
# return JsonResponse({'error': 'Invalid or missing token'}, status=401)
|
||||||
|
|
||||||
|
# token = auth_header.split(' ')[1]
|
||||||
|
# tk = Token.objects.filter(token=token).first()
|
||||||
|
# if not tk:
|
||||||
|
# return JsonResponse({'error': 'Invalid or missing token'}, status=401)
|
||||||
|
|
||||||
|
# Validation snapshot
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return JsonResponse({'error': 'Invalid JSON'}, status=400)
|
||||||
|
|
||||||
|
# try:
|
||||||
|
# Build(data, None, check=True)
|
||||||
|
# except Exception:
|
||||||
|
# return JsonResponse({'error': 'Invalid Snapshot'}, status=400)
|
||||||
|
|
||||||
|
exist_annotation = Annotation.objects.filter(
|
||||||
|
uuid=data['uuid']
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if exist_annotation:
|
||||||
|
raise ValidationError("error: the snapshot {} exist".format(data['uuid']))
|
||||||
|
|
||||||
|
# Process snapshot
|
||||||
|
# save_in_disk(data, tk.user)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Build(data, tk.user)
|
||||||
|
user = User.objects.get(email="user@example.org")
|
||||||
|
Build(data, user)
|
||||||
|
except Exception:
|
||||||
|
return JsonResponse({'status': 'fail'}, status=200)
|
||||||
|
|
||||||
|
return JsonResponse({'status': 'success'}, status=200)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TokenView(DashboardView, SingleTableView):
|
||||||
|
template_name = "token.html"
|
||||||
|
title = _("Credential management")
|
||||||
|
section = "Credential"
|
||||||
|
subtitle = _('Managament Tokens')
|
||||||
|
icon = 'bi bi-key'
|
||||||
|
model = Token
|
||||||
|
table_class = TokensTable
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""
|
||||||
|
Override the get_queryset method to filter events based on the user type.
|
||||||
|
"""
|
||||||
|
return Token.objects.filter().order_by("-id")
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context.update({
|
||||||
|
'tokens': Token.objects,
|
||||||
|
})
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class TokenDeleteView(DashboardView, DeleteView):
|
||||||
|
model = Token
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
self.pk = kwargs['pk']
|
||||||
|
self.object = get_object_or_404(self.model, pk=self.pk, owner=self.request.user)
|
||||||
|
self.object.delete()
|
||||||
|
|
||||||
|
return redirect('api:tokens')
|
||||||
|
|
||||||
|
|
||||||
|
class TokenNewView(DashboardView, View):
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
Token.objects.create(token=uuid4(), owner=self.request.user)
|
||||||
|
|
||||||
|
return redirect('api:tokens')
|
||||||
|
|
|
@ -39,16 +39,16 @@ class DashboardView(LoginRequiredMixin):
|
||||||
'section': self.section,
|
'section': self.section,
|
||||||
'path': resolve(self.request.path).url_name,
|
'path': resolve(self.request.path).url_name,
|
||||||
'user': self.request.user,
|
'user': self.request.user,
|
||||||
'lot_tags': LotTag.objects.filter(owner=self.request.user)
|
'lot_tags': LotTag.objects.filter(owner=self.request.user.institution)
|
||||||
})
|
})
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_session_devices(self):
|
def get_session_devices(self):
|
||||||
# import pdb; pdb.set_trace()
|
|
||||||
dev_ids = self.request.session.pop("devices", [])
|
dev_ids = self.request.session.pop("devices", [])
|
||||||
|
|
||||||
self._devices = []
|
self._devices = []
|
||||||
for x in Annotation.objects.filter(value__in=dev_ids).filter(owner=self.request.user).distinct():
|
annotation = Annotation.objects.filter(value__in=dev_ids)
|
||||||
|
for x in annotation.filter(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 +57,7 @@ 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)
|
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):
|
||||||
|
|
|
@ -143,6 +143,12 @@
|
||||||
</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>
|
||||||
|
|
|
@ -12,7 +12,7 @@ class UnassignedDevicesView(InventaryMixin):
|
||||||
|
|
||||||
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 = Device.get_unassigned(self.request.user)
|
devices = Device.get_unassigned(self.request.user.institution)
|
||||||
|
|
||||||
context.update({
|
context.update({
|
||||||
'devices': devices,
|
'devices': devices,
|
||||||
|
|
|
@ -102,8 +102,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,10 +114,10 @@ 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, user):
|
def get_unassigned(cls, institution):
|
||||||
chids = DeviceLot.objects.filter(lot__owner=user).values_list("device_id", flat=True).distinct()
|
chids = DeviceLot.objects.filter(lot__owner=institution).values_list("device_id", flat=True).distinct()
|
||||||
annotations = Annotation.objects.filter(
|
annotations = Annotation.objects.filter(
|
||||||
owner=user,
|
owner=institution,
|
||||||
type=Annotation.Type.SYSTEM,
|
type=Annotation.Type.SYSTEM,
|
||||||
).exclude(value__in=chids).values_list("value", flat=True).distinct()
|
).exclude(value__in=chids).values_list("value", flat=True).distinct()
|
||||||
return [cls(id=x) for x in annotations]
|
return [cls(id=x) for x in annotations]
|
||||||
|
@ -141,22 +142,24 @@ class Device:
|
||||||
def manufacturer(self):
|
def manufacturer(self):
|
||||||
if not self.last_evidence:
|
if not self.last_evidence:
|
||||||
self.get_last_evidence()
|
self.get_last_evidence()
|
||||||
return self.last_evidence.doc['device']['manufacturer']
|
return self.last_evidence.get_manufacturer()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self):
|
def type(self):
|
||||||
if not self.last_evidence:
|
if not self.last_evidence:
|
||||||
self.get_last_evidence()
|
self.get_last_evidence()
|
||||||
return self.last_evidence.doc['device']['type']
|
return self.last_evidence.get_chassis()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def model(self):
|
def model(self):
|
||||||
if not self.last_evidence:
|
if not self.last_evidence:
|
||||||
self.get_last_evidence()
|
self.get_last_evidence()
|
||||||
return self.last_evidence.doc['device']['model']
|
return self.last_evidence.get_model()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self):
|
def components(self):
|
||||||
if not self.last_evidence:
|
if not self.last_evidence:
|
||||||
self.get_last_evidence()
|
self.get_last_evidence()
|
||||||
return self.last_evidence.doc['device']['type']
|
return self.last_evidence.get_components()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -173,16 +173,19 @@
|
||||||
<div class="tab-pane fade profile-overview" id="components">
|
<div class="tab-pane fade profile-overview" id="components">
|
||||||
<h5 class="card-title">Components last evidence</h5>
|
<h5 class="card-title">Components last evidence</h5>
|
||||||
<div class="list-group col-6">
|
<div class="list-group col-6">
|
||||||
{% for c in object.last_evidence.doc.components %}
|
{% for c in object.components %}
|
||||||
<div class="list-group-item">
|
<div class="list-group-item">
|
||||||
<div class="d-flex w-100 justify-content-between">
|
<div class="d-flex w-100 justify-content-between">
|
||||||
<h5 class="mb-1">{{ c.type }}</h5>
|
<h5 class="mb-1">{{ c.type }}</h5>
|
||||||
<small class="text-muted">{{ evidence.created }}</small>
|
<small class="text-muted">{{ evidence.created }}</small>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-1">
|
<p class="mb-1">
|
||||||
{{ c.manufacturer }}<br />
|
{% for k, v in c.items %}
|
||||||
{{ c.model }}<br />
|
{% if k not in "actions,type" %}
|
||||||
{{ c.serialNumber }}<br />
|
{{ k }}: {{ v }}<br />
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
<br />
|
||||||
</p>
|
</p>
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
</small>
|
</small>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
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,
|
||||||
|
@ -87,12 +87,15 @@ 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
|
||||||
|
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
self.object.initial()
|
self.object.initial()
|
||||||
lot_tags = LotTag.objects.filter(owner=self.request.user)
|
lot_tags = LotTag.objects.filter(owner=self.request.user.institution)
|
||||||
context.update({
|
context.update({
|
||||||
'object': self.object,
|
'object': self.object,
|
||||||
'snapshot': self.object.get_last_evidence(),
|
'snapshot': self.object.get_last_evidence(),
|
||||||
|
@ -110,7 +113,7 @@ class AddAnnotationView(DashboardView, CreateView):
|
||||||
fields = ("key", "value")
|
fields = ("key", "value")
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.owner = self.request.user
|
form.instance.owner = self.request.user.institution
|
||||||
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)
|
||||||
|
@ -118,11 +121,12 @@ class AddAnnotationView(DashboardView, CreateView):
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
pk = self.kwargs.get('pk')
|
pk = self.kwargs.get('pk')
|
||||||
|
institution = self.request.user.institution
|
||||||
self.annotation = Annotation.objects.filter(
|
self.annotation = Annotation.objects.filter(
|
||||||
owner=self.request.user, 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=self.request.user)
|
get_object_or_404(Annotation, pk=0, owner=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
|
||||||
|
@ -137,7 +141,7 @@ class AddDocumentView(DashboardView, CreateView):
|
||||||
fields = ("key", "value")
|
fields = ("key", "value")
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.owner = self.request.user
|
form.instance.owner = self.request.user.institution
|
||||||
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)
|
||||||
|
@ -145,11 +149,12 @@ class AddDocumentView(DashboardView, CreateView):
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
pk = self.kwargs.get('pk')
|
pk = self.kwargs.get('pk')
|
||||||
|
institution = self.request.user.institution
|
||||||
self.annotation = Annotation.objects.filter(
|
self.annotation = Annotation.objects.filter(
|
||||||
owner=self.request.user, 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=self.request.user)
|
get_object_or_404(Annotation, pk=0, owner=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
|
||||||
|
|
|
@ -27,10 +27,17 @@ 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 = []
|
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())
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
|
@ -43,6 +50,7 @@ INSTALLED_APPS = [
|
||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
'django_extensions',
|
'django_extensions',
|
||||||
'django_bootstrap5',
|
'django_bootstrap5',
|
||||||
|
'django_tables2',
|
||||||
"rest_framework",
|
"rest_framework",
|
||||||
"login",
|
"login",
|
||||||
"user",
|
"user",
|
||||||
|
@ -53,6 +61,7 @@ INSTALLED_APPS = [
|
||||||
"lot",
|
"lot",
|
||||||
"documents",
|
"documents",
|
||||||
"dashboard",
|
"dashboard",
|
||||||
|
"api",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -24,4 +24,5 @@ urlpatterns = [
|
||||||
path("evidence/", include("evidence.urls")),
|
path("evidence/", include("evidence.urls")),
|
||||||
path("device/", include("device.urls")),
|
path("device/", include("device.urls")),
|
||||||
path("lot/", include("lot.urls")),
|
path("lot/", include("lot.urls")),
|
||||||
|
path('api/', include('api.urls')),
|
||||||
]
|
]
|
||||||
|
|
|
@ -4,7 +4,9 @@ services:
|
||||||
build:
|
build:
|
||||||
dockerfile: docker/devicehub-django.Dockerfile
|
dockerfile: docker/devicehub-django.Dockerfile
|
||||||
environment:
|
environment:
|
||||||
DEBUG: true
|
- DEBUG=true
|
||||||
|
- DOMAIN=${DOMAIN:-localhost}
|
||||||
|
- DEMO=${DEMO:-n}
|
||||||
volumes:
|
volumes:
|
||||||
- .:/opt/devicehub-django
|
- .:/opt/devicehub-django
|
||||||
ports:
|
ports:
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Copyright (c) 2024 Pedro <copyright@cas.cat>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set -u
|
||||||
|
# DEBUG
|
||||||
|
set -x
|
||||||
|
|
||||||
|
main() {
|
||||||
|
# remove old database
|
||||||
|
sudo rm -vf db/*
|
||||||
|
docker compose down
|
||||||
|
docker compose build
|
||||||
|
docker compose up
|
||||||
|
}
|
||||||
|
|
||||||
|
main "${@}"
|
||||||
|
|
||||||
|
# written in emacs
|
||||||
|
# -*- mode: shell-script; -*-
|
|
@ -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,19 +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_user user@example.org 1234
|
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
|
||||||
|
./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,25 @@
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from pint import UnitRegistry
|
||||||
|
|
||||||
|
# Sets up the unit handling
|
||||||
|
unit_registry = Path(__file__).parent / 'unit_registry'
|
||||||
|
|
||||||
|
unit = UnitRegistry()
|
||||||
|
unit.load_definitions(str(unit_registry / 'quantities.txt'))
|
||||||
|
TB = unit.TB
|
||||||
|
GB = unit.GB
|
||||||
|
MB = unit.MB
|
||||||
|
Mbs = unit.Mbit / unit.s
|
||||||
|
MBs = unit.MB / unit.s
|
||||||
|
Hz = unit.Hz
|
||||||
|
GHz = unit.GHz
|
||||||
|
MHz = unit.MHz
|
||||||
|
Inch = unit.inch
|
||||||
|
mAh = unit.hour * unit.mA
|
||||||
|
mV = unit.mV
|
||||||
|
|
||||||
|
base2 = UnitRegistry()
|
||||||
|
base2.load_definitions(str(unit_registry / 'base2.quantities.txt'))
|
||||||
|
|
||||||
|
GiB = base2.GiB
|
|
@ -56,6 +56,15 @@ class UserTagForm(forms.Form):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.uuid = kwargs.pop('uuid', None)
|
self.uuid = kwargs.pop('uuid', None)
|
||||||
|
annotation = Annotation.objects.filter(
|
||||||
|
uuid=self.uuid,
|
||||||
|
type=Annotation.Type.SYSTEM,
|
||||||
|
key='CUSTOM_ID',
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if annotation:
|
||||||
|
kwargs['initial'].update({'tag': annotation.value})
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
@ -71,7 +80,7 @@ class UserTagForm(forms.Form):
|
||||||
|
|
||||||
Annotation.objects.create(
|
Annotation.objects.create(
|
||||||
uuid=self.uuid,
|
uuid=self.uuid,
|
||||||
owner=user,
|
owner=user.institution,
|
||||||
type=Annotation.Type.SYSTEM,
|
type=Annotation.Type.SYSTEM,
|
||||||
key='CUSTOM_ID',
|
key='CUSTOM_ID',
|
||||||
value=self.tag
|
value=self.tag
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
# 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"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,10 +1,12 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from dmidecode import DMIParse
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from utils.constants import STR_SM_SIZE, STR_EXTEND_SIZE
|
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 User
|
from evidence.parse_details import ParseSnapshot
|
||||||
|
from user.models import Institution
|
||||||
|
|
||||||
|
|
||||||
class Annotation(models.Model):
|
class Annotation(models.Model):
|
||||||
|
@ -15,7 +17,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(User, on_delete=models.CASCADE)
|
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
|
||||||
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)
|
||||||
|
@ -32,7 +34,10 @@ class Evidence:
|
||||||
self.owner = None
|
self.owner = None
|
||||||
self.doc = None
|
self.doc = None
|
||||||
self.created = None
|
self.created = 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()
|
||||||
|
@ -59,6 +64,10 @@ class Evidence:
|
||||||
for xa in matches:
|
for xa in matches:
|
||||||
self.doc = json.loads(xa.document.get_data())
|
self.doc = json.loads(xa.document.get_data())
|
||||||
|
|
||||||
|
if self.doc.get("software") == "EreuseWorkbench":
|
||||||
|
dmidecode_raw = self.doc["data"]["dmidecode"]
|
||||||
|
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()
|
||||||
|
@ -67,12 +76,43 @@ 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):
|
||||||
|
if self.doc.get("software") != "EreuseWorkbench":
|
||||||
|
return self.doc['device']['manufacturer']
|
||||||
|
|
||||||
|
return self.dmi.manufacturer().strip()
|
||||||
|
|
||||||
|
def get_model(self):
|
||||||
|
if self.doc.get("software") != "EreuseWorkbench":
|
||||||
|
return self.doc['device']['model']
|
||||||
|
|
||||||
|
return self.dmi.model().strip()
|
||||||
|
|
||||||
|
def get_chassis(self):
|
||||||
|
if self.doc.get("software") != "EreuseWorkbench":
|
||||||
|
return self.doc['device']['model']
|
||||||
|
|
||||||
|
chassis = self.dmi.get("Chassis")[0].get("Type", '_virtual')
|
||||||
|
lower_type = chassis.lower()
|
||||||
|
|
||||||
|
for k, v in CHASSIS_DH.items():
|
||||||
|
if lower_type in v:
|
||||||
|
return k
|
||||||
|
return ""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_all(cls, user):
|
def get_all(cls, user):
|
||||||
return Annotation.objects.filter(
|
return Annotation.objects.filter(
|
||||||
owner=user,
|
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']
|
||||||
|
|
|
@ -4,9 +4,33 @@ import shutil
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from dmidecode import DMIParse
|
||||||
from evidence.xapian import search, index
|
from evidence.xapian import search, index
|
||||||
from evidence.models import Evidence, Annotation
|
from evidence.models import Annotation
|
||||||
from utils.constants import ALGOS
|
from utils.constants import ALGOS, CHASSIS_DH
|
||||||
|
|
||||||
|
|
||||||
|
def get_network_cards(child, nets):
|
||||||
|
if child['id'] == 'network':
|
||||||
|
nets.append(child)
|
||||||
|
if child.get('children'):
|
||||||
|
[get_network_cards(x, nets) for x in child['children']]
|
||||||
|
|
||||||
|
|
||||||
|
def get_mac(lshw):
|
||||||
|
nets = []
|
||||||
|
try:
|
||||||
|
get_network_cards(json.loads(lshw), nets)
|
||||||
|
except Exception as ss:
|
||||||
|
print("WARNING!! {}".format(ss))
|
||||||
|
return
|
||||||
|
|
||||||
|
nets_sorted = sorted(nets, key=lambda x: x['businfo'])
|
||||||
|
# This funcion get the network card integrated in motherboard
|
||||||
|
# integrate = [x for x in nets if "pci@0000:00:" in x.get('businfo', '')]
|
||||||
|
|
||||||
|
if nets_sorted:
|
||||||
|
return nets_sorted[0]['serial']
|
||||||
|
|
||||||
|
|
||||||
class Build:
|
class Build:
|
||||||
|
@ -33,13 +57,17 @@ class Build:
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_hid_14(self):
|
def get_hid_14(self):
|
||||||
device = self.json['device']
|
if self.json.get("software") == "EreuseWorkbench":
|
||||||
manufacturer = device.get("manufacturer", '')
|
hid = self.get_hid(self.json)
|
||||||
model = device.get("model", '')
|
else:
|
||||||
chassis = device.get("chassis", '')
|
device = self.json['device']
|
||||||
serial_number = device.get("serialNumber", '')
|
manufacturer = device.get("manufacturer", '')
|
||||||
sku = device.get("sku", '')
|
model = device.get("model", '')
|
||||||
hid = f"{manufacturer}{model}{chassis}{serial_number}{sku}"
|
chassis = device.get("chassis", '')
|
||||||
|
serial_number = device.get("serialNumber", '')
|
||||||
|
sku = device.get("sku", '')
|
||||||
|
hid = f"{manufacturer}{model}{chassis}{serial_number}{sku}"
|
||||||
|
|
||||||
return hashlib.sha3_256(hid.encode()).hexdigest()
|
return hashlib.sha3_256(hid.encode()).hexdigest()
|
||||||
|
|
||||||
def create_annotations(self):
|
def create_annotations(self):
|
||||||
|
@ -47,8 +75,45 @@ class Build:
|
||||||
for k, v in self.algorithms.items():
|
for k, v in self.algorithms.items():
|
||||||
Annotation.objects.create(
|
Annotation.objects.create(
|
||||||
uuid=self.uuid,
|
uuid=self.uuid,
|
||||||
owner=self.user,
|
owner=self.user.institution,
|
||||||
type=Annotation.Type.SYSTEM,
|
type=Annotation.Type.SYSTEM,
|
||||||
key=k,
|
key=k,
|
||||||
value=v
|
value=v
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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_sku(self):
|
||||||
|
return self.dmi.get("System")[0].get("SKU Number", "n/a").strip()
|
||||||
|
|
||||||
|
def get_chassis(self):
|
||||||
|
return self.dmi.get("Chassis")[0].get("Type", '_virtual')
|
||||||
|
|
||||||
|
def get_hid(self, snapshot):
|
||||||
|
dmidecode_raw = snapshot["data"]["dmidecode"]
|
||||||
|
self.dmi = DMIParse(dmidecode_raw)
|
||||||
|
|
||||||
|
manufacturer = self.dmi.manufacturer().strip()
|
||||||
|
model = self.dmi.model().strip()
|
||||||
|
chassis = self.get_chassis_dh()
|
||||||
|
serial_number = self.dmi.serial_number()
|
||||||
|
sku = self.get_sku()
|
||||||
|
|
||||||
|
if not snapshot["data"].get('lshw'):
|
||||||
|
return f"{manufacturer}{model}{chassis}{serial_number}{sku}"
|
||||||
|
|
||||||
|
lshw = snapshot["data"]["lshw"]
|
||||||
|
# mac = get_mac2(hwinfo_raw) or ""
|
||||||
|
mac = get_mac(lshw) or ""
|
||||||
|
if not mac:
|
||||||
|
print("WARNING!! No there are MAC address")
|
||||||
|
else:
|
||||||
|
print(f"{manufacturer}{model}{chassis}{serial_number}{sku}{mac}")
|
||||||
|
|
||||||
|
return f"{manufacturer}{model}{chassis}{serial_number}{sku}{mac}"
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
K = KiB = k = kb = KB
|
||||||
|
M = MiB = m = mb = MB
|
||||||
|
G = GiB = g = gb = GB
|
||||||
|
T = TiB = t = tb = TB
|
|
@ -0,0 +1,9 @@
|
||||||
|
HZ = hertz = hz
|
||||||
|
KHZ = kilohertz = khz
|
||||||
|
MHZ = megahertz = mhz
|
||||||
|
GHZ = gigahertz = ghz
|
||||||
|
B = byte = b = UNIT = unit
|
||||||
|
KB = kilobyte = kb = K = k
|
||||||
|
MB = megabyte = mb = M = m
|
||||||
|
GB = gigabyte = gb = G = g
|
||||||
|
T = terabyte = tb = T = t
|
|
@ -89,7 +89,7 @@ class EvidenceView(DashboardView, FormView):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
self.pk = kwargs['pk']
|
self.pk = kwargs['pk']
|
||||||
self.object = Evidence(self.pk)
|
self.object = Evidence(self.pk)
|
||||||
if self.object.owner != self.request.user:
|
if self.object.owner != self.request.user.institution:
|
||||||
raise Http403
|
raise Http403
|
||||||
|
|
||||||
self.object.get_annotations()
|
self.object.get_annotations()
|
||||||
|
@ -127,7 +127,7 @@ class DownloadEvidenceView(DashboardView, TemplateView):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
pk = kwargs['pk']
|
pk = kwargs['pk']
|
||||||
evidence = Evidence(pk)
|
evidence = Evidence(pk)
|
||||||
if evidence.owner != self.request.user:
|
if evidence.owner != self.request.user.institution:
|
||||||
raise Http403()
|
raise Http403()
|
||||||
|
|
||||||
evidence.get_doc()
|
evidence.get_doc()
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
# 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,14 +6,14 @@ from utils.constants import (
|
||||||
STR_EXTEND_SIZE,
|
STR_EXTEND_SIZE,
|
||||||
)
|
)
|
||||||
|
|
||||||
from user.models import User
|
from user.models import Institution
|
||||||
# from device.models import Device
|
# from device.models import Device
|
||||||
from evidence.models import Annotation
|
# from evidence.models import Annotation
|
||||||
|
|
||||||
|
|
||||||
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(User, on_delete=models.CASCADE)
|
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -30,8 +30,8 @@ class Lot(models.Model):
|
||||||
name = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
name = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
||||||
code = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
code = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
||||||
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=False)
|
||||||
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
|
||||||
type = models.ForeignKey(LotTag, on_delete=models.CASCADE)
|
type = models.ForeignKey(LotTag, on_delete=models.CASCADE)
|
||||||
|
|
||||||
def add(self, v):
|
def add(self, v):
|
||||||
|
@ -52,7 +52,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(User, on_delete=models.CASCADE)
|
owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
|
||||||
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)
|
||||||
|
|
|
@ -7,6 +7,16 @@
|
||||||
<h3>{{ subtitle }}</h3>
|
<h3>{{ subtitle }}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="col text-center">
|
<div class="col text-center">
|
||||||
|
{% if show_closed %}
|
||||||
|
<a href="?show_closed=false" class="btn btn-green-admin">
|
||||||
|
{% trans 'Hide closed lots' %}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="?show_closed=true" class="btn btn-green-admin">
|
||||||
|
{% trans 'Show closed lots' %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<a href="{% url 'lot:add' %}" type="button" class="btn btn-green-admin">
|
<a href="{% url 'lot:add' %}" type="button" class="btn btn-green-admin">
|
||||||
<i class="bi bi-plus"></i>
|
<i class="bi bi-plus"></i>
|
||||||
{% trans 'Add new lot' %}
|
{% trans 'Add new lot' %}
|
||||||
|
|
32
lot/views.py
32
lot/views.py
|
@ -28,7 +28,7 @@ class NewLotView(DashboardView, CreateView):
|
||||||
)
|
)
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.owner = self.request.user
|
form.instance.owner = self.request.user.institution
|
||||||
response = super().form_valid(form)
|
response = super().form_valid(form)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@ -83,8 +83,8 @@ class AddToLotView(DashboardView, FormView):
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
lots = Lot.objects.filter(owner=self.request.user)
|
lots = Lot.objects.filter(owner=self.request.user.institution)
|
||||||
lot_tags = LotTag.objects.filter(owner=self.request.user)
|
lot_tags = LotTag.objects.filter(owner=self.request.user.institution)
|
||||||
context.update({
|
context.update({
|
||||||
'lots': lots,
|
'lots': lots,
|
||||||
'lot_tags':lot_tags,
|
'lot_tags':lot_tags,
|
||||||
|
@ -93,7 +93,7 @@ class AddToLotView(DashboardView, FormView):
|
||||||
|
|
||||||
def get_form(self):
|
def get_form(self):
|
||||||
form = super().get_form()
|
form = super().get_form()
|
||||||
form.fields["lots"].queryset = Lot.objects.filter(owner=self.request.user)
|
form.fields["lots"].queryset = Lot.objects.filter(owner=self.request.user.institution)
|
||||||
return form
|
return form
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
|
@ -123,14 +123,16 @@ class LotsTagsView(DashboardView, TemplateView):
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
self.pk = kwargs.get('pk')
|
self.pk = kwargs.get('pk')
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
tag = get_object_or_404(LotTag, owner=self.request.user, id=self.pk)
|
tag = get_object_or_404(LotTag, owner=self.request.user.institution, id=self.pk)
|
||||||
self.title += " {}".format(tag.name)
|
self.title += " {}".format(tag.name)
|
||||||
self.breadcrumb += " {}".format(tag.name)
|
self.breadcrumb += " {}".format(tag.name)
|
||||||
lots = Lot.objects.filter(owner=self.request.user).filter(type=tag)
|
show_closed = self.request.GET.get('show_closed', 'false') == 'true'
|
||||||
|
lots = Lot.objects.filter(owner=self.request.user.institution).filter(type=tag, closed=show_closed)
|
||||||
context.update({
|
context.update({
|
||||||
'lots': lots,
|
'lots': lots,
|
||||||
'title': self.title,
|
'title': self.title,
|
||||||
'breadcrumb': self.breadcrumb
|
'breadcrumb': self.breadcrumb,
|
||||||
|
'show_closed': show_closed
|
||||||
})
|
})
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
@ -144,7 +146,7 @@ class LotAddDocumentView(DashboardView, CreateView):
|
||||||
fields = ("key", "value")
|
fields = ("key", "value")
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.owner = self.request.user
|
form.instance.owner = self.request.user.institution
|
||||||
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)
|
||||||
|
@ -152,7 +154,7 @@ class LotAddDocumentView(DashboardView, CreateView):
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
pk = self.kwargs.get('pk')
|
pk = self.kwargs.get('pk')
|
||||||
self.lot = get_object_or_404(Lot, pk=pk, owner=self.request.user)
|
self.lot = get_object_or_404(Lot, pk=pk, owner=self.request.user.institution)
|
||||||
self.success_url = reverse_lazy('lot:documents', args=[pk])
|
self.success_url = reverse_lazy('lot:documents', args=[pk])
|
||||||
kwargs = super().get_form_kwargs()
|
kwargs = super().get_form_kwargs()
|
||||||
return kwargs
|
return kwargs
|
||||||
|
@ -166,10 +168,10 @@ class LotDocumentsView(DashboardView, TemplateView):
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
self.pk = kwargs.get('pk')
|
self.pk = kwargs.get('pk')
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
lot = get_object_or_404(Lot, owner=self.request.user, id=self.pk)
|
lot = get_object_or_404(Lot, owner=self.request.user.institution, id=self.pk)
|
||||||
documents = LotAnnotation.objects.filter(
|
documents = LotAnnotation.objects.filter(
|
||||||
lot=lot,
|
lot=lot,
|
||||||
owner=self.request.user,
|
owner=self.request.user.institution,
|
||||||
type=LotAnnotation.Type.DOCUMENT,
|
type=LotAnnotation.Type.DOCUMENT,
|
||||||
)
|
)
|
||||||
context.update({
|
context.update({
|
||||||
|
@ -189,10 +191,10 @@ class LotAnnotationsView(DashboardView, TemplateView):
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
self.pk = kwargs.get('pk')
|
self.pk = kwargs.get('pk')
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
lot = get_object_or_404(Lot, owner=self.request.user, id=self.pk)
|
lot = get_object_or_404(Lot, owner=self.request.user.institution, id=self.pk)
|
||||||
annotations = LotAnnotation.objects.filter(
|
annotations = LotAnnotation.objects.filter(
|
||||||
lot=lot,
|
lot=lot,
|
||||||
owner=self.request.user,
|
owner=self.request.user.institution,
|
||||||
type=LotAnnotation.Type.USER,
|
type=LotAnnotation.Type.USER,
|
||||||
)
|
)
|
||||||
context.update({
|
context.update({
|
||||||
|
@ -213,7 +215,7 @@ class LotAddAnnotationView(DashboardView, CreateView):
|
||||||
fields = ("key", "value")
|
fields = ("key", "value")
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.owner = self.request.user
|
form.instance.owner = self.request.user.institution
|
||||||
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)
|
||||||
|
@ -221,7 +223,7 @@ class LotAddAnnotationView(DashboardView, CreateView):
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
pk = self.kwargs.get('pk')
|
pk = self.kwargs.get('pk')
|
||||||
self.lot = get_object_or_404(Lot, pk=pk, owner=self.request.user)
|
self.lot = get_object_or_404(Lot, pk=pk, owner=self.request.user.institution)
|
||||||
self.success_url = reverse_lazy('lot:annotations', args=[pk])
|
self.success_url = reverse_lazy('lot:annotations', args=[pk])
|
||||||
kwargs = super().get_form_kwargs()
|
kwargs = super().get_form_kwargs()
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
|
@ -3,9 +3,11 @@ Django==5.0.6
|
||||||
django-bootstrap5==24.2
|
django-bootstrap5==24.2
|
||||||
django-extensions==3.2.3
|
django-extensions==3.2.3
|
||||||
djangorestframework==3.15.1
|
djangorestframework==3.15.1
|
||||||
|
django-tables2==2.6.0
|
||||||
python-decouple==3.3
|
python-decouple==3.3
|
||||||
py-dmidecode==0.1.3
|
py-dmidecode==0.1.3
|
||||||
pandas==2.2.2
|
pandas==2.2.2
|
||||||
xlrd==2.0.1
|
xlrd==2.0.1
|
||||||
odfpy==1.4.1
|
odfpy==1.4.1
|
||||||
|
pytz==2024.2
|
||||||
|
Pint==0.24.3
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from user.models import Institution
|
from user.models import Institution
|
||||||
|
from lot.models import LotTag
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Create a new Institution"
|
help = "Create a new Institution"
|
||||||
|
@ -9,4 +9,17 @@ class Command(BaseCommand):
|
||||||
parser.add_argument('name', type=str, help='institution')
|
parser.add_argument('name', type=str, help='institution')
|
||||||
|
|
||||||
def handle(self, *args, **kwargs):
|
def handle(self, *args, **kwargs):
|
||||||
Institution.objects.create(name=kwargs['name'])
|
self.institution = Institution.objects.create(name=kwargs['name'])
|
||||||
|
self.create_lot_tags()
|
||||||
|
|
||||||
|
def create_lot_tags(self):
|
||||||
|
tags = [
|
||||||
|
"Entrada",
|
||||||
|
"Salida",
|
||||||
|
"Temporal"
|
||||||
|
]
|
||||||
|
for tag in tags:
|
||||||
|
LotTag.objects.create(
|
||||||
|
name=tag,
|
||||||
|
owner=self.institution
|
||||||
|
)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
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 lot.models import LotTag
|
|
||||||
from user.models import Institution
|
from user.models import Institution
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,29 +15,16 @@ class Command(BaseCommand):
|
||||||
parser.add_argument('password', type=str, help='password')
|
parser.add_argument('password', type=str, help='password')
|
||||||
|
|
||||||
def handle(self, *args, **kwargs):
|
def handle(self, *args, **kwargs):
|
||||||
email = kwargs['email']
|
self.email = kwargs['email']
|
||||||
password = kwargs['password']
|
self.password = kwargs['password']
|
||||||
institution = Institution.objects.get(name=kwargs['institution'])
|
self.institution = Institution.objects.get(name=kwargs['institution'])
|
||||||
self.create_user(institution, email, password)
|
self.create_user()
|
||||||
self.create_lot_tags()
|
|
||||||
|
|
||||||
def create_user(self, institution, email, password):
|
def create_user(self):
|
||||||
self.u = User.objects.create(
|
self.u = User.objects.create(
|
||||||
institution=institution,
|
institution=self.institution,
|
||||||
email=email,
|
email=self.email,
|
||||||
password=password
|
password=self.password
|
||||||
)
|
)
|
||||||
self.u.set_password(password)
|
self.u.set_password(self.password)
|
||||||
self.u.save()
|
self.u.save()
|
||||||
|
|
||||||
def create_lot_tags(self):
|
|
||||||
tags = [
|
|
||||||
"Entrada",
|
|
||||||
"Salida",
|
|
||||||
"Temporal"
|
|
||||||
]
|
|
||||||
for tag in tags:
|
|
||||||
LotTag.objects.create(
|
|
||||||
name=tag,
|
|
||||||
owner=self.u
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,3 +1,37 @@
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from dashboard.mixins import DashboardView
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ProfileView(DashboardView):
|
||||||
|
template_name = "profile.html"
|
||||||
|
subtitle = _('My personal data')
|
||||||
|
icon = 'bi bi-person-gear'
|
||||||
|
fields = ('first_name', 'last_name', 'email')
|
||||||
|
success_url = reverse_lazy('idhub:user_profile')
|
||||||
|
model = User
|
||||||
|
|
||||||
|
def get_queryset(self, **kwargs):
|
||||||
|
queryset = Membership.objects.select_related('user').filter(
|
||||||
|
user=self.request.user)
|
||||||
|
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
return self.request.user
|
||||||
|
|
||||||
|
def get_form(self):
|
||||||
|
form = super().get_form()
|
||||||
|
return form
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context.update({
|
||||||
|
'lang': self.request.LANGUAGE_CODE,
|
||||||
|
})
|
||||||
|
return context
|
||||||
|
|
||||||
# Create your views here.
|
|
||||||
|
|
|
@ -20,3 +20,29 @@ HID_ALGO1 = [
|
||||||
ALGOS = {
|
ALGOS = {
|
||||||
"hidalgo1": HID_ALGO1,
|
"hidalgo1": HID_ALGO1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CHASSIS_DH = {
|
||||||
|
'Tower': {'desktop', 'low-profile', 'tower', 'server'},
|
||||||
|
'Docking': {'docking'},
|
||||||
|
'AllInOne': {'all-in-one'},
|
||||||
|
'Microtower': {'mini-tower', 'space-saving', 'mini'},
|
||||||
|
'PizzaBox': {'pizzabox'},
|
||||||
|
'Lunchbox': {'lunchbox'},
|
||||||
|
'Stick': {'stick'},
|
||||||
|
'Netbook': {'notebook', 'sub-notebook'},
|
||||||
|
'Handheld': {'handheld'},
|
||||||
|
'Laptop': {'portable', 'laptop'},
|
||||||
|
'Convertible': {'convertible'},
|
||||||
|
'Detachable': {'detachable'},
|
||||||
|
'Tablet': {'tablet'},
|
||||||
|
'Virtual': {'_virtual'},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DATASTORAGEINTERFACE = [
|
||||||
|
'ATA',
|
||||||
|
'USB',
|
||||||
|
'PCI',
|
||||||
|
'NVME',
|
||||||
|
]
|
||||||
|
|
|
@ -69,7 +69,7 @@ def create_annotation(doc, user, commit=False):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'uuid': doc['uuid'],
|
'uuid': doc['uuid'],
|
||||||
'owner': user,
|
'owner': user.institution,
|
||||||
'type': Annotation.Type.SYSTEM,
|
'type': Annotation.Type.SYSTEM,
|
||||||
'key': 'CUSTOMER_ID',
|
'key': 'CUSTOMER_ID',
|
||||||
'value': doc['CUSTOMER_ID'],
|
'value': doc['CUSTOMER_ID'],
|
||||||
|
|
Loading…
Reference in New Issue