Merge pull request 'xapian' (#1) from xapian into master
Reviewed-on: https://gitea.pangea.org/ereuse/devicehub-django/pulls/1
This commit is contained in:
commit
e8743cd793
|
@ -5,6 +5,8 @@ from django.core.exceptions import PermissionDenied
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.views.generic.base import TemplateView
|
from django.views.generic.base import TemplateView
|
||||||
from device.models import Device
|
from device.models import Device
|
||||||
|
from evidence.models import Annotation
|
||||||
|
from lot.models import LotTag
|
||||||
|
|
||||||
|
|
||||||
class Http403(PermissionDenied):
|
class Http403(PermissionDenied):
|
||||||
|
@ -37,13 +39,17 @@ 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)
|
||||||
})
|
})
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_session_devices(self):
|
def get_session_devices(self):
|
||||||
# import pdb; pdb.set_trace()
|
# import pdb; pdb.set_trace()
|
||||||
dev_ids = self.request.session.pop("devices", [])
|
dev_ids = self.request.session.pop("devices", [])
|
||||||
self._devices = Device.objects.filter(id__in=dev_ids).filter(owner=self.request.user)
|
|
||||||
|
self._devices = []
|
||||||
|
for x in Annotation.objects.filter(value__in=dev_ids).filter(owner=self.request.user).distinct():
|
||||||
|
self._devices.append(Device(id=x.value))
|
||||||
return self._devices
|
return self._devices
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,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)
|
self.object = get_object_or_404(self.model, pk=self.pk, owner=self.request.user)
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
@ -76,5 +82,3 @@ class InventaryMixin(DashboardView, TemplateView):
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<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="#">DEVICE HUB</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
|
PANGEA
|
||||||
</div>
|
</div>
|
||||||
|
@ -90,11 +90,6 @@
|
||||||
{% trans 'Unassigned devices' %}
|
{% trans 'Unassigned devices' %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link{% if path == 'admin_people_new' %} active2{% endif %}" href="{% url 'dashboard:all_devices' %}">
|
|
||||||
{% trans 'All devices' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
|
@ -103,37 +98,29 @@
|
||||||
{% trans 'Lots' %}
|
{% trans 'Lots' %}
|
||||||
</a>
|
</a>
|
||||||
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if section == 'People' %}expanded{% else %}collapse{% endif %}" id="ul_lots" data-bs-parent="#sidebarMenu">
|
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if section == 'People' %}expanded{% else %}collapse{% endif %}" id="ul_lots" data-bs-parent="#sidebarMenu">
|
||||||
|
{% for tag in lot_tags %}
|
||||||
<li class="nav-items">
|
<li class="nav-items">
|
||||||
<a class="nav-link{% if path == 'admin_people_list' %} active2{% endif %}" href="{% url 'lot:lots_incoming' %}">
|
<a class="nav-link{% if path == 'admin_people_list' %} active2{% endif %}" href="{% url 'lot:tag' tag.id %}">
|
||||||
{% trans 'Incoming ' %}
|
{{ tag.name }}
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link{% if path == 'admin_people_new' %} active2{% endif %}" href="{% url 'lot:lots_outgoing' %}">
|
|
||||||
{% trans 'Outgoing ' %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link{% if path == 'admin_people_new' %} active2{% endif %}" href="{% url 'lot:lots_temporal' %}">
|
|
||||||
{% trans 'Temporal ' %}
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="admin {% if section == 'People' %}active {% endif %}nav-link fw-bold" data-bs-toggle="collapse" data-bs-target="#ul_snapshots" aria-expanded="false" aria-controls="ul_snapshots" href="javascript:void()">
|
<a class="admin {% if section == 'People' %}active {% endif %}nav-link fw-bold" data-bs-toggle="collapse" data-bs-target="#ul_evidences" aria-expanded="false" aria-controls="ul_evidences" href="javascript:void()">
|
||||||
<i class="bi bi-usb-drive icon_sidebar"></i>
|
<i class="bi bi-usb-drive icon_sidebar"></i>
|
||||||
{% trans 'Snapshots' %}
|
{% trans 'evidences' %}
|
||||||
</a>
|
</a>
|
||||||
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if section == 'People' %}expanded{% else %}collapse{% endif %}" id="ul_snapshots" data-bs-parent="#sidebarMenu">
|
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if section == 'People' %}expanded{% else %}collapse{% endif %}" id="ul_evidences" data-bs-parent="#sidebarMenu">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link{% if path == 'admin_people_list' %} active2{% endif %}" href="{# url 'idhub:admin_people_list' #}">
|
<a class="nav-link{% if path == 'admin_people_list' %} active2{% endif %}" href="{% url 'evidence:upload' %}">
|
||||||
{% trans 'Upload one' %}
|
{% trans 'Upload one' %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link{% if path == 'admin_people_list' %} active2{% endif %}" href="{% url 'snapshot:list' %}">
|
<a class="nav-link{% if path == 'admin_people_list' %} active2{% endif %}" href="{% url 'evidence:list' %}">
|
||||||
{% trans 'Old snapshots' %}
|
{% trans 'Old evidences' %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -145,7 +132,7 @@
|
||||||
</a>
|
</a>
|
||||||
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if section == 'People' %}expanded{% else %}collapse{% endif %}" id="ul_placeholders" data-bs-parent="#sidebarMenu">
|
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if section == 'People' %}expanded{% else %}collapse{% endif %}" id="ul_placeholders" data-bs-parent="#sidebarMenu">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link{% if path == 'admin_people_list' %} active2{% endif %}" href="{# url 'idhub:admin_people_list' #}">
|
<a class="nav-link{% if path == 'admin_people_list' %} active2{% endif %}" href="{% url 'evidence:import' %}">
|
||||||
{% trans 'Upload Spreadsheet' %}
|
{% trans 'Upload Spreadsheet' %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -151,12 +151,12 @@
|
||||||
</a>
|
</a>
|
||||||
</li><!-- End Dashboard Nav -->
|
</li><!-- End Dashboard Nav -->
|
||||||
|
|
||||||
<li class="nav-heading">Snapshots</li>
|
<li class="nav-heading">evidences</li>
|
||||||
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link collapsed" href="{# url_for('inventory.snapshotslist') #}">
|
<a class="nav-link collapsed" href="{# url_for('inventory.evidenceslist') #}">
|
||||||
<i class="bi-menu-button-wide"></i>
|
<i class="bi-menu-button-wide"></i>
|
||||||
<span>Uploaded Snapshots</span>
|
<span>Uploaded evidences</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
|
@ -7,22 +7,22 @@
|
||||||
<h3>{{ subtitle }}</h3>
|
<h3>{{ subtitle }}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="col text-center">
|
<div class="col text-center">
|
||||||
<a href="{# url 'idhub:admin_people_edit' object.id #}" type="button" class="btn btn-green-admin">
|
{% if lot %}
|
||||||
|
<a href="{% url 'lot:documents' object.id %}" type="button" class="btn btn-green-admin">
|
||||||
<i class="bi bi-folder2"></i>
|
<i class="bi bi-folder2"></i>
|
||||||
{% trans 'Lots' %}
|
{% trans 'Documents' %}
|
||||||
</a>
|
|
||||||
<a href="{# url 'idhub:admin_people_edit' object.id #}" type="button" class="btn btn-green-admin">
|
|
||||||
<i class="bi bi-plus"></i>
|
|
||||||
{% trans 'Actions' %}
|
|
||||||
</a>
|
</a>
|
||||||
|
{% endif %}
|
||||||
<a href="{# url 'idhub:admin_people_activate' object.id #}" type="button" class="btn btn-green-admin">
|
<a href="{# url 'idhub:admin_people_activate' 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>
|
||||||
<a href="#" type="button" class="btn btn-green-admin">
|
{% if lot %}
|
||||||
|
<a href="{% url 'lot:annotations' object.id %}" type="button" class="btn btn-green-admin">
|
||||||
<i class="bi bi-tag"></i>
|
<i class="bi bi-tag"></i>
|
||||||
{% trans 'Labels' %}
|
{% trans 'Annotations' %}
|
||||||
</a>
|
</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -44,7 +44,9 @@
|
||||||
<input type="checkbox" name="devices" value="{{ dev.id }}" />
|
<input type="checkbox" name="devices" value="{{ dev.id }}" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'device:details' dev.pk %}">{{ dev.type }} {{ dev.manufacturer }} {{ dev.model }}</a>
|
<a href="{% url 'device:details' dev.id %}">
|
||||||
|
{{ dev.type }} {{ dev.manufacturer }} {{ dev.model }}
|
||||||
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -5,6 +5,5 @@ app_name = 'dashboard'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", views.UnassignedDevicesView.as_view(), name="unassigned_devices"),
|
path("", views.UnassignedDevicesView.as_view(), name="unassigned_devices"),
|
||||||
path("all/", views.AllDevicesView.as_view(), name="all_devices"),
|
|
||||||
path("<int:pk>/", views.LotDashboardView.as_view(), name="lot"),
|
path("<int:pk>/", views.LotDashboardView.as_view(), name="lot"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.db.models import Count
|
|
||||||
from dashboard.mixins import InventaryMixin, DetailsMixin
|
from dashboard.mixins import InventaryMixin, DetailsMixin
|
||||||
from device.models import Device
|
from device.models import Device
|
||||||
from lot.models import Lot
|
from lot.models import Lot
|
||||||
|
@ -13,41 +12,32 @@ 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.objects.filter(
|
devices = Device.get_unassigned(self.request.user)
|
||||||
owner=self.request.user
|
|
||||||
).annotate(num_lots=Count('lot')).filter(num_lots=0)
|
|
||||||
context.update({
|
context.update({
|
||||||
'devices': devices,
|
'devices': devices,
|
||||||
})
|
})
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class AllDevicesView(InventaryMixin):
|
|
||||||
template_name = "unassigned_devices.html"
|
|
||||||
section = "All"
|
|
||||||
title = _("All Devices")
|
|
||||||
breadcrumb = "Devices / All Devices"
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super().get_context_data(**kwargs)
|
|
||||||
devices = Device.objects.filter(owner=self.request.user)
|
|
||||||
context.update({
|
|
||||||
'devices': devices,
|
|
||||||
})
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class LotDashboardView(InventaryMixin, DetailsMixin):
|
class LotDashboardView(InventaryMixin, DetailsMixin):
|
||||||
template_name = "unassigned_devices.html"
|
template_name = "unassigned_devices.html"
|
||||||
section = "Unassigned"
|
section = "dashboard_lot"
|
||||||
title = _("Lot Devices")
|
title = _("Lot Devices")
|
||||||
breadcrumb = "Devices / Lot Devices"
|
breadcrumb = "Lot / Devices"
|
||||||
model = Lot
|
model = Lot
|
||||||
|
|
||||||
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.object.devices.filter(owner=self.request.user)
|
devices = self.get_devices()
|
||||||
|
lot = context.get('object')
|
||||||
context.update({
|
context.update({
|
||||||
'devices': devices,
|
'devices': devices,
|
||||||
|
'lot': lot,
|
||||||
})
|
})
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
def get_devices(self):
|
||||||
|
chids = self.object.devicelot_set.all().values_list("device_id", flat=True).distinct()
|
||||||
|
return [Device(id=x) for x in chids]
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
# src https://stackoverflow.com/questions/115983/how-do-i-add-an-empty-directory-to-a-git-repository
|
||||||
|
|
||||||
|
# Ignore everything in this directory
|
||||||
|
*
|
||||||
|
# Except this file
|
||||||
|
!.gitignore
|
|
@ -1,3 +1,65 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from utils.device import create_annotation, create_doc, create_index
|
||||||
|
|
||||||
|
|
||||||
|
DEVICE_TYPES = [
|
||||||
|
("Desktop", "Desktop"),
|
||||||
|
("Laptop", "Laptop"),
|
||||||
|
("Server", "Server"),
|
||||||
|
("GraphicCard", "GraphicCard"),
|
||||||
|
("HardDrive", "HardDrive"),
|
||||||
|
("SolidStateDrive", "SolidStateDrive"),
|
||||||
|
("Motherboard", "Motherboard"),
|
||||||
|
("NetworkAdapter", "NetworkAdapter"),
|
||||||
|
("Processor", "Processor"),
|
||||||
|
("RamModule", "RamModule"),
|
||||||
|
("SoundCard", "SoundCard"),
|
||||||
|
("Display", "Display"),
|
||||||
|
("Battery", "Battery"),
|
||||||
|
("Camera", "Camera"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceForm(forms.Form):
|
||||||
|
type = forms.ChoiceField(choices = DEVICE_TYPES, required=False)
|
||||||
|
amount = forms.IntegerField(required=False, initial=1)
|
||||||
|
customer_id = forms.CharField(required=False)
|
||||||
|
name = forms.CharField(required=False)
|
||||||
|
value = forms.CharField(required=False)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseDeviceFormSet(forms.BaseFormSet):
|
||||||
|
def clean(self):
|
||||||
|
for x in self.cleaned_data:
|
||||||
|
if x.get("amount"):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def save(self, user, commit=True):
|
||||||
|
self.user = user
|
||||||
|
row = {}
|
||||||
|
for f in self.forms:
|
||||||
|
d = f.cleaned_data
|
||||||
|
if not d:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if d.get("type"):
|
||||||
|
row["type"] = d["type"]
|
||||||
|
if d.get("amount"):
|
||||||
|
row["amount"] = d["amount"]
|
||||||
|
if d.get("name"):
|
||||||
|
row[d["name"]] = d.get("value", '')
|
||||||
|
if d.get("customer_id"):
|
||||||
|
row['CUSTOMER_ID']= d["customer_id"]
|
||||||
|
|
||||||
|
doc = create_doc(row)
|
||||||
|
if not commit:
|
||||||
|
return doc
|
||||||
|
|
||||||
|
create_index(doc)
|
||||||
|
create_annotation(doc, user, commit=commit)
|
||||||
|
return doc
|
||||||
|
|
||||||
|
|
||||||
|
DeviceFormSet = forms.formset_factory(form=DeviceForm, formset=BaseDeviceFormSet, extra=1)
|
||||||
|
|
||||||
|
|
|
@ -1,404 +0,0 @@
|
||||||
# Generated by Django 5.0.6 on 2024-06-11 09:20
|
|
||||||
|
|
||||||
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="Device",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.BigAutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("created", models.DateTimeField(auto_now_add=True)),
|
|
||||||
("updated", models.DateTimeField(auto_now=True)),
|
|
||||||
("type", models.CharField(max_length=32)),
|
|
||||||
("model", models.CharField(blank=True, max_length=64, null=True)),
|
|
||||||
(
|
|
||||||
"manufacturer",
|
|
||||||
models.CharField(blank=True, max_length=64, null=True),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"serial_number",
|
|
||||||
models.CharField(blank=True, max_length=64, null=True),
|
|
||||||
),
|
|
||||||
("part_number", models.CharField(blank=True, max_length=64, null=True)),
|
|
||||||
("brand", models.TextField(blank=True, null=True)),
|
|
||||||
("generation", models.SmallIntegerField(blank=True, null=True)),
|
|
||||||
("version", models.TextField(blank=True, null=True)),
|
|
||||||
("production_date", models.DateTimeField(blank=True, null=True)),
|
|
||||||
("variant", models.TextField(blank=True, null=True)),
|
|
||||||
("devicehub_id", models.TextField(blank=True, null=True, unique=True)),
|
|
||||||
("dhid_bk", models.CharField(blank=True, max_length=64, null=True)),
|
|
||||||
("phid_bk", models.CharField(blank=True, max_length=64, null=True)),
|
|
||||||
("family", models.CharField(blank=True, max_length=64, null=True)),
|
|
||||||
("hid", models.CharField(blank=True, max_length=64, null=True)),
|
|
||||||
("chid", models.CharField(blank=True, max_length=64, null=True)),
|
|
||||||
("active", models.BooleanField(default=True)),
|
|
||||||
(
|
|
||||||
"owner",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to=settings.AUTH_USER_MODEL,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="Component",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"device",
|
|
||||||
models.OneToOneField(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
to="device.device",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"type",
|
|
||||||
models.CharField(
|
|
||||||
choices=[
|
|
||||||
("GraphicCard", "Graphiccard"),
|
|
||||||
("DataStorage", "Datastorage"),
|
|
||||||
("Motherboard", "Motherboard"),
|
|
||||||
("NetworkAdapter", "Networkadapter"),
|
|
||||||
("Processor", "Processor"),
|
|
||||||
("RamModule", "Rammodule"),
|
|
||||||
("SoundCard", "Soundcard"),
|
|
||||||
("Display", "Display"),
|
|
||||||
("Battery", "Battery"),
|
|
||||||
("Camera", "Camera"),
|
|
||||||
],
|
|
||||||
max_length=32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="Computer",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"device",
|
|
||||||
models.OneToOneField(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
to="device.device",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("chassis", models.TextField(blank=True, null=True)),
|
|
||||||
("system_uuid", models.UUIDField()),
|
|
||||||
("sku", models.TextField(blank=True, null=True)),
|
|
||||||
(
|
|
||||||
"type",
|
|
||||||
models.CharField(
|
|
||||||
choices=[
|
|
||||||
("Desktop", "Desktop"),
|
|
||||||
("Laptop", "Laptop"),
|
|
||||||
("Server", "Server"),
|
|
||||||
],
|
|
||||||
default="Laptop",
|
|
||||||
max_length=32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="PhysicalProperties",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"device",
|
|
||||||
models.OneToOneField(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
to="device.device",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("weight", models.FloatField(blank=True, null=True)),
|
|
||||||
("width", models.FloatField(blank=True, null=True)),
|
|
||||||
("height", models.FloatField(blank=True, null=True)),
|
|
||||||
("depth", models.FloatField(blank=True, null=True)),
|
|
||||||
("color", models.CharField(blank=True, max_length=20, null=True)),
|
|
||||||
("image", models.CharField(blank=True, max_length=64, null=True)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="SoundCard",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.BigAutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"component",
|
|
||||||
models.OneToOneField(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to="device.component",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="RamModule",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.BigAutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("size", models.IntegerField(blank=True, null=True)),
|
|
||||||
("speed", models.SmallIntegerField(blank=True, null=True)),
|
|
||||||
(
|
|
||||||
"interface",
|
|
||||||
models.CharField(
|
|
||||||
choices=[
|
|
||||||
("SDRAM", "Sdram"),
|
|
||||||
("DDR SDRAM", "Ddr"),
|
|
||||||
("DDR2 SDRAM", "Ddr2"),
|
|
||||||
("DDR3 SDRAM", "Ddr3"),
|
|
||||||
("DDR4 SDRAM", "Ddr4"),
|
|
||||||
("DDR5 SDRAM", "Ddr5"),
|
|
||||||
("DDR6 SDRAM", "Ddr6"),
|
|
||||||
("LPDDR3", "Lpddr3"),
|
|
||||||
],
|
|
||||||
max_length=32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"format",
|
|
||||||
models.CharField(
|
|
||||||
choices=[("DIMM", "Dimm"), ("SODIMM", "Sodimm")], max_length=32
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"component",
|
|
||||||
models.OneToOneField(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to="device.component",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="Processor",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.BigAutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("speed", models.FloatField(blank=True, null=True)),
|
|
||||||
("cores", models.SmallIntegerField(blank=True, null=True)),
|
|
||||||
("threads", models.SmallIntegerField(blank=True, null=True)),
|
|
||||||
("address", models.SmallIntegerField(blank=True, null=True)),
|
|
||||||
(
|
|
||||||
"component",
|
|
||||||
models.OneToOneField(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to="device.component",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="NetworkAdapter",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.BigAutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("speed", models.IntegerField(blank=True, null=True)),
|
|
||||||
("wireless", models.BooleanField(default=False)),
|
|
||||||
(
|
|
||||||
"component",
|
|
||||||
models.OneToOneField(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to="device.component",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="Motherboard",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.BigAutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("slots", models.SmallIntegerField(blank=True, null=True)),
|
|
||||||
("usb", models.SmallIntegerField(blank=True, null=True)),
|
|
||||||
("firewire", models.SmallIntegerField(blank=True, null=True)),
|
|
||||||
("serial", models.SmallIntegerField(blank=True, null=True)),
|
|
||||||
("pcmcia", models.SmallIntegerField(blank=True, null=True)),
|
|
||||||
("bios_date", models.DateTimeField()),
|
|
||||||
("ram_slots", models.SmallIntegerField(blank=True, null=True)),
|
|
||||||
("ram_max_size", models.IntegerField(blank=True, null=True)),
|
|
||||||
(
|
|
||||||
"component",
|
|
||||||
models.OneToOneField(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to="device.component",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="GraphicCard",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.BigAutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("memory", models.IntegerField(blank=True, null=True)),
|
|
||||||
(
|
|
||||||
"component",
|
|
||||||
models.OneToOneField(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to="device.component",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="Display",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.BigAutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"component",
|
|
||||||
models.OneToOneField(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to="device.component",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="DataStorage",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.BigAutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("size", models.IntegerField(blank=True, null=True)),
|
|
||||||
(
|
|
||||||
"interface",
|
|
||||||
models.CharField(
|
|
||||||
choices=[
|
|
||||||
("ATA", "Ata"),
|
|
||||||
("USB", "Usb"),
|
|
||||||
("PCI", "Pci"),
|
|
||||||
("NVME", "Nvme"),
|
|
||||||
],
|
|
||||||
max_length=32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"type",
|
|
||||||
models.CharField(
|
|
||||||
choices=[
|
|
||||||
("HardDrive", "Harddrive"),
|
|
||||||
("SolidStateDrive", "Solidstatedrive"),
|
|
||||||
],
|
|
||||||
max_length=32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"component",
|
|
||||||
models.OneToOneField(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to="device.component",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="Battery",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.BigAutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"component",
|
|
||||||
models.OneToOneField(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to="device.component",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="component",
|
|
||||||
name="computer",
|
|
||||||
field=models.OneToOneField(
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to="device.computer",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,33 +0,0 @@
|
||||||
# Generated by Django 5.0.6 on 2024-07-03 11:07
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("device", "0001_initial"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="device",
|
|
||||||
name="brand",
|
|
||||||
field=models.CharField(blank=True, max_length=64, null=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="device",
|
|
||||||
name="devicehub_id",
|
|
||||||
field=models.CharField(blank=True, max_length=64, null=True, unique=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="device",
|
|
||||||
name="variant",
|
|
||||||
field=models.CharField(blank=True, max_length=64, null=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="device",
|
|
||||||
name="version",
|
|
||||||
field=models.CharField(blank=True, max_length=64, null=True),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,73 +0,0 @@
|
||||||
# Generated by Django 5.0.6 on 2024-07-03 12:33
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("device", "0002_alter_device_brand_alter_device_devicehub_id_and_more"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="component",
|
|
||||||
name="type",
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="computer",
|
|
||||||
name="type",
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="datastorage",
|
|
||||||
name="type",
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="computer",
|
|
||||||
name="chassis",
|
|
||||||
field=models.CharField(
|
|
||||||
blank=True,
|
|
||||||
choices=[
|
|
||||||
("Tower", "Tower"),
|
|
||||||
("All in one", "Allinone"),
|
|
||||||
("Microtower", "Microtower"),
|
|
||||||
("Netbook", "Netbook"),
|
|
||||||
("Laptop", "Laptop"),
|
|
||||||
("Tablet", "Tabler"),
|
|
||||||
("Server", "Server"),
|
|
||||||
("Non-physical device", "Virtual"),
|
|
||||||
],
|
|
||||||
max_length=32,
|
|
||||||
null=True,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="computer",
|
|
||||||
name="sku",
|
|
||||||
field=models.CharField(blank=True, max_length=32, null=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="device",
|
|
||||||
name="type",
|
|
||||||
field=models.CharField(
|
|
||||||
choices=[
|
|
||||||
("Desktop", "Desktop"),
|
|
||||||
("Laptop", "Laptop"),
|
|
||||||
("Server", "Server"),
|
|
||||||
("GraphicCard", "Graphiccard"),
|
|
||||||
("HardDrive", "Harddrive"),
|
|
||||||
("SolidStateDrive", "Solidstatedrive"),
|
|
||||||
("Motherboard", "Motherboard"),
|
|
||||||
("NetworkAdapter", "Networkadapter"),
|
|
||||||
("Processor", "Processor"),
|
|
||||||
("RamModule", "Rammodule"),
|
|
||||||
("SoundCard", "Soundcard"),
|
|
||||||
("Display", "Display"),
|
|
||||||
("Battery", "Battery"),
|
|
||||||
("Camera", "Camera"),
|
|
||||||
],
|
|
||||||
default="Laptop",
|
|
||||||
max_length=32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,46 +0,0 @@
|
||||||
# Generated by Django 5.0.6 on 2024-07-11 13:58
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("device", "0003_remove_component_type_remove_computer_type_and_more"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="device",
|
|
||||||
name="dhid_bk",
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="device",
|
|
||||||
name="phid_bk",
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="computer",
|
|
||||||
name="erasure_server",
|
|
||||||
field=models.BooleanField(default=False),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="device",
|
|
||||||
name="reliable",
|
|
||||||
field=models.BooleanField(default=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="component",
|
|
||||||
name="computer",
|
|
||||||
field=models.ForeignKey(
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to="device.computer",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="computer",
|
|
||||||
name="system_uuid",
|
|
||||||
field=models.UUIDField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
]
|
|
246
device/models.py
246
device/models.py
|
@ -1,9 +1,12 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
from utils.constants import STR_SM_SIZE, STR_SIZE, STR_EXTEND_SIZE, ALGOS
|
||||||
|
from evidence.models import Annotation, Evidence
|
||||||
from user.models import User
|
from user.models import User
|
||||||
from utils.constants import STR_SM_SIZE, STR_SIZE
|
from lot.models import DeviceLot
|
||||||
|
|
||||||
|
|
||||||
class Device(models.Model):
|
class Device:
|
||||||
class Types(models.TextChoices):
|
class Types(models.TextChoices):
|
||||||
DESKTOP = "Desktop"
|
DESKTOP = "Desktop"
|
||||||
LAPTOP = "Laptop"
|
LAPTOP = "Laptop"
|
||||||
|
@ -20,155 +23,140 @@ class Device(models.Model):
|
||||||
BATTERY = "Battery"
|
BATTERY = "Battery"
|
||||||
CAMERA = "Camera"
|
CAMERA = "Camera"
|
||||||
|
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
def __init__(self, *args, **kwargs):
|
||||||
updated = models.DateTimeField(auto_now=True)
|
# the id is the chid of the device
|
||||||
type = models.CharField(max_length=STR_SM_SIZE, choices=Types, default=Types.LAPTOP)
|
self.id = kwargs["id"]
|
||||||
model = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
self.pk = self.id
|
||||||
manufacturer = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
self.algorithm = None
|
||||||
serial_number = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
self.owner = None
|
||||||
part_number = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
self.annotations = []
|
||||||
brand = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
self.hids = []
|
||||||
generation = models.SmallIntegerField(blank=True, null=True)
|
self.uuids = []
|
||||||
version = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
self.evidences = []
|
||||||
production_date = models.DateTimeField(blank=True, null=True)
|
self.lots = []
|
||||||
variant = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
self.last_evidence = None
|
||||||
devicehub_id = models.CharField(max_length=STR_SIZE, unique=True, blank=True, null=True)
|
self.get_last_evidence()
|
||||||
family = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
|
||||||
hid = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
|
||||||
chid = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
|
||||||
active = models.BooleanField(default=True)
|
|
||||||
reliable = models.BooleanField(default=True)
|
|
||||||
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
||||||
|
|
||||||
def has_physical_properties(self):
|
def initial(self):
|
||||||
try:
|
self.get_annotations()
|
||||||
if self.physicalproperties:
|
self.get_uuids()
|
||||||
return True
|
self.get_hids()
|
||||||
else:
|
self.get_evidences()
|
||||||
return False
|
self.get_lots()
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
def get_annotations(self):
|
||||||
|
if self.annotations:
|
||||||
|
return self.annotations
|
||||||
|
|
||||||
class PhysicalProperties(models.Model):
|
self.annotations = Annotation.objects.filter(
|
||||||
device = models.OneToOneField(Device, models.CASCADE, primary_key=True)
|
type=Annotation.Type.SYSTEM,
|
||||||
weight = models.FloatField(blank=True, null=True)
|
value=self.id
|
||||||
width = models.FloatField(blank=True, null=True)
|
).order_by("-created")
|
||||||
height = models.FloatField(blank=True, null=True)
|
|
||||||
depth = models.FloatField(blank=True, null=True)
|
|
||||||
color = models.CharField(max_length=20, blank=True, null=True)
|
|
||||||
image = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
|
||||||
|
|
||||||
|
if self.annotations.count():
|
||||||
|
self.algorithm = self.annotations[0].key
|
||||||
|
self.owner = self.annotations[0].owner
|
||||||
|
|
||||||
class Computer(models.Model):
|
return self.annotations
|
||||||
class Chassis(models.TextChoices):
|
|
||||||
TOWER = 'Tower'
|
|
||||||
ALLINONE = 'All in one'
|
|
||||||
MICROTOWER = 'Microtower'
|
|
||||||
NETBOOK = 'Netbook'
|
|
||||||
LAPTOP = 'Laptop'
|
|
||||||
TABLER = 'Tablet'
|
|
||||||
SERVER = "Server"
|
|
||||||
VIRTUAL = 'Non-physical device'
|
|
||||||
|
|
||||||
|
def get_user_annotations(self):
|
||||||
|
if not self.uuids:
|
||||||
|
self.get_uuids()
|
||||||
|
|
||||||
device = models.OneToOneField(Device, models.CASCADE, primary_key=True)
|
annotations = Annotation.objects.filter(
|
||||||
chassis = models.CharField(
|
uuid__in=self.uuids,
|
||||||
blank=True,
|
owner=self.owner,
|
||||||
null=True,
|
type=Annotation.Type.USER
|
||||||
max_length=STR_SM_SIZE,
|
)
|
||||||
choices=Chassis
|
return annotations
|
||||||
)
|
|
||||||
system_uuid = models.UUIDField(blank=True, null=True)
|
|
||||||
sku = models.CharField(max_length=STR_SM_SIZE, blank=True, null=True)
|
|
||||||
erasure_server = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
|
def get_user_documents(self):
|
||||||
|
if not self.uuids:
|
||||||
|
self.get_uuids()
|
||||||
|
|
||||||
class Component(models.Model):
|
annotations = Annotation.objects.filter(
|
||||||
device = models.OneToOneField(Device, models.CASCADE, primary_key=True)
|
uuid__in=self.uuids,
|
||||||
computer = models.ForeignKey(Computer, on_delete=models.CASCADE, null=True)
|
owner=self.owner,
|
||||||
|
type=Annotation.Type.DOCUMENT
|
||||||
|
)
|
||||||
|
return annotations
|
||||||
|
|
||||||
|
def get_uuids(self):
|
||||||
|
for a in self.get_annotations():
|
||||||
|
if a.uuid not in self.uuids:
|
||||||
|
self.uuids.append(a.uuid)
|
||||||
|
|
||||||
class GraphicCard(models.Model):
|
def get_hids(self):
|
||||||
component = models.OneToOneField(Component, models.CASCADE)
|
annotations = self.get_annotations()
|
||||||
memory = models.IntegerField(blank=True, null=True)
|
|
||||||
|
|
||||||
|
self.hids = annotations.filter(
|
||||||
|
type=Annotation.Type.SYSTEM,
|
||||||
|
key__in=ALGOS.keys(),
|
||||||
|
).values_list("value", flat=True)
|
||||||
|
|
||||||
class DataStorage(models.Model):
|
def get_evidences(self):
|
||||||
class Interface(models.TextChoices):
|
if not self.uuids:
|
||||||
ATA = 'ATA'
|
self.get_uuids()
|
||||||
USB = 'USB'
|
|
||||||
PCI = 'PCI'
|
|
||||||
NVME = 'NVME'
|
|
||||||
|
|
||||||
size = models.IntegerField(blank=True, null=True)
|
self.evidences = [Evidence(u) for u in self.uuids]
|
||||||
interface = models.CharField(max_length=STR_SM_SIZE, choices=Interface)
|
|
||||||
component = models.OneToOneField(Component, models.CASCADE)
|
|
||||||
|
|
||||||
|
def get_last_evidence(self):
|
||||||
|
annotations = self.get_annotations()
|
||||||
|
if annotations:
|
||||||
|
annotation = annotations.first()
|
||||||
|
self.last_evidence = Evidence(annotation.uuid)
|
||||||
|
|
||||||
class Motherboard(models.Model):
|
def last_uuid(self):
|
||||||
component = models.OneToOneField(Component, models.CASCADE)
|
return self.uuids[0]
|
||||||
slots = models.SmallIntegerField(blank=True, null=True)
|
|
||||||
usb = models.SmallIntegerField(blank=True, null=True)
|
|
||||||
firewire = models.SmallIntegerField(blank=True, null=True)
|
|
||||||
serial = models.SmallIntegerField(blank=True, null=True)
|
|
||||||
pcmcia = models.SmallIntegerField(blank=True, null=True)
|
|
||||||
bios_date = models.DateTimeField()
|
|
||||||
ram_slots = models.SmallIntegerField(blank=True, null=True)
|
|
||||||
ram_max_size = models.IntegerField(blank=True, null=True)
|
|
||||||
|
|
||||||
|
def get_lots(self):
|
||||||
|
self.lots = [x.lot for x in DeviceLot.objects.filter(device_id=self.id)]
|
||||||
|
|
||||||
class NetworkAdapter(models.Model):
|
@classmethod
|
||||||
component = models.OneToOneField(Component, models.CASCADE)
|
def get_unassigned(cls, user):
|
||||||
speed = models.IntegerField(blank=True, null=True)
|
chids = DeviceLot.objects.filter(lot__owner=user).values_list("device_id", flat=True).distinct()
|
||||||
wireless = models.BooleanField(default=False)
|
annotations = Annotation.objects.filter(
|
||||||
|
owner=user,
|
||||||
|
type=Annotation.Type.SYSTEM,
|
||||||
|
).exclude(value__in=chids).values_list("value", flat=True).distinct()
|
||||||
|
return [cls(id=x) for x in annotations]
|
||||||
|
|
||||||
def __format__(self, format_spec):
|
# return cls.objects.filter(
|
||||||
v = super().__format__(format_spec)
|
# owner=user
|
||||||
if 's' in format_spec:
|
# ).annotate(num_lots=models.Count('lot')).filter(num_lots=0)
|
||||||
v += ' – {} Mbps'.format(self.speed)
|
|
||||||
return v
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_websnapshot(self):
|
||||||
|
if not self.last_evidence:
|
||||||
|
self.get_last_evidence()
|
||||||
|
return self.last_evidence.doc['type'] == "WebSnapshot"
|
||||||
|
|
||||||
class Processor(models.Model):
|
@property
|
||||||
component = models.OneToOneField(Component, models.CASCADE)
|
def last_user_evidence(self):
|
||||||
speed = models.FloatField(blank=True, null=True)
|
if not self.last_evidence:
|
||||||
cores = models.SmallIntegerField(blank=True, null=True)
|
self.get_last_evidence()
|
||||||
threads = models.SmallIntegerField(blank=True, null=True)
|
return self.last_evidence.doc['kv'].items()
|
||||||
address = models.SmallIntegerField(blank=True, null=True)
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def manufacturer(self):
|
||||||
|
if not self.last_evidence:
|
||||||
|
self.get_last_evidence()
|
||||||
|
return self.last_evidence.doc['device']['manufacturer']
|
||||||
|
|
||||||
class RamModule(models.Model):
|
@property
|
||||||
class Interface(models.TextChoices):
|
def type(self):
|
||||||
SDRAM = 'SDRAM'
|
if not self.last_evidence:
|
||||||
DDR = 'DDR SDRAM'
|
self.get_last_evidence()
|
||||||
DDR2 = 'DDR2 SDRAM'
|
return self.last_evidence.doc['device']['type']
|
||||||
DDR3 = 'DDR3 SDRAM'
|
|
||||||
DDR4 = 'DDR4 SDRAM'
|
|
||||||
DDR5 = 'DDR5 SDRAM'
|
|
||||||
DDR6 = 'DDR6 SDRAM'
|
|
||||||
LPDDR3 = 'LPDDR3'
|
|
||||||
|
|
||||||
class Format(models.TextChoices):
|
@property
|
||||||
DIMM = 'DIMM'
|
def model(self):
|
||||||
SODIMM = 'SODIMM'
|
if not self.last_evidence:
|
||||||
|
self.get_last_evidence()
|
||||||
component = models.OneToOneField(Component, models.CASCADE)
|
return self.last_evidence.doc['device']['model']
|
||||||
size = models.IntegerField(blank=True, null=True)
|
|
||||||
interface = models.CharField(max_length=STR_SM_SIZE, choices=Interface)
|
|
||||||
speed = models.SmallIntegerField(blank=True, null=True)
|
|
||||||
interface = models.CharField(max_length=STR_SM_SIZE, choices=Interface)
|
|
||||||
format = models.CharField(max_length=STR_SM_SIZE, choices=Format)
|
|
||||||
|
|
||||||
|
|
||||||
class SoundCard(models.Model):
|
|
||||||
component = models.OneToOneField(Component, models.CASCADE)
|
|
||||||
|
|
||||||
|
|
||||||
class Display(models.Model):
|
|
||||||
component = models.OneToOneField(Component, models.CASCADE)
|
|
||||||
|
|
||||||
|
|
||||||
class Battery(models.Model):
|
|
||||||
component = models.OneToOneField(Component, models.CASCADE)
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type(self):
|
||||||
|
if not self.last_evidence:
|
||||||
|
self.get_last_evidence()
|
||||||
|
return self.last_evidence.doc['device']['type']
|
||||||
|
|
|
@ -4,18 +4,18 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h3>{{ object.pk }}</h3>
|
<h3>{{ object.id }}</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="nav nav-tabs nav-tabs-bordered">
|
<ul class="nav nav-tabs nav-tabs-bordered">
|
||||||
<li class="nav-items">
|
<li class="nav-items">
|
||||||
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#details">General details</button>
|
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#details">General details</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-items">
|
<li class="nav-items">
|
||||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#physicalproperties">Physical properties</button>
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#annotations">User annotations</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-items">
|
<li class="nav-items">
|
||||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#documents">Documents</button>
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#documents">Documents</button>
|
||||||
|
@ -23,20 +23,16 @@
|
||||||
<li class="nav-items">
|
<li class="nav-items">
|
||||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#lots">Lots</button>
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#lots">Lots</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-items">
|
|
||||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#status">Status</button>
|
|
||||||
</li>
|
|
||||||
<li class="nav-items">
|
<li class="nav-items">
|
||||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#components">Components</button>
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#components">Components</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-items">
|
<li class="nav-items">
|
||||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#traceabiliy">Traceability log</button>
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#evidences">Evidences</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-items">
|
<li class="nav-items">
|
||||||
<a class="nav-link" href="">Web</a>
|
<a class="nav-link" href="">Web</a>
|
||||||
</li>
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content pt-2">
|
<div class="tab-content pt-2">
|
||||||
|
@ -44,29 +40,23 @@
|
||||||
<div class="tab-pane fade show active" id="details">
|
<div class="tab-pane fade show active" id="details">
|
||||||
<h5 class="card-title">Details</h5>
|
<h5 class="card-title">Details</h5>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-lg-3 col-md-4 label ">
|
|
||||||
(<a href="{% url 'device:edit' object.id %}">Edit Device</a>)
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-9 col-md-8">
|
|
||||||
{%if object.hid %}Snapshot{% else %}Placeholder{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-3 col-md-4 label ">Phid</div>
|
<div class="col-lg-3 col-md-4 label ">Phid</div>
|
||||||
<div class="col-lg-9 col-md-8">{{ object.id }}</div>
|
<div class="col-lg-9 col-md-8">{{ object.id }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-3 col-md-4 label ">Id device internal</div>
|
|
||||||
<div class="col-lg-9 col-md-8"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-3 col-md-4 label ">Type</div>
|
<div class="col-lg-3 col-md-4 label ">Type</div>
|
||||||
<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 %}
|
||||||
|
{% for k, v in object.last_user_evidence %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-3 col-md-4 label">{{ k }}</div>
|
||||||
|
<div class="col-lg-9 col-md-8">{{ v|default:"" }}</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-3 col-md-4 label">Manufacturer</div>
|
<div class="col-lg-3 col-md-4 label">Manufacturer</div>
|
||||||
<div class="col-lg-9 col-md-8">{{ object.manufacturer|default:"" }}</div>
|
<div class="col-lg-9 col-md-8">{{ object.manufacturer|default:"" }}</div>
|
||||||
|
@ -77,196 +67,146 @@
|
||||||
<div class="col-lg-9 col-md-8">{{ object.model|default:"" }}</div>
|
<div class="col-lg-9 col-md-8">{{ object.model|default:"" }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-3 col-md-4 label">Part Number</div>
|
|
||||||
<div class="col-lg-9 col-md-8">{{ object.part_number|default:"" }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-3 col-md-4 label">Serial Number</div>
|
<div class="col-lg-3 col-md-4 label">Serial Number</div>
|
||||||
<div class="col-lg-9 col-md-8">{{ object.serial_number|default:"" }}</div>
|
<div class="col-lg-9 col-md-8">{{ object.last_evidence.doc.device.serialNumber|default:"" }}</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tab-pane fade profile-overview" id="physicalproperties">
|
|
||||||
<h5 class="card-title">Physical Properties</h5>
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col-lg-3 col-md-4 label ">
|
|
||||||
(<a href="{% url 'device:physical_edit' object.pk %}">Edit Physical Properties</a>)
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if object.has_physical_properties %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-3 col-md-4 label ">
|
|
||||||
Weight:
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-9 col-md-8">
|
|
||||||
{{ object.physicalproperties.weight }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-3 col-md-4 label ">width:</div>
|
|
||||||
<div class="col-lg-9 col-md-8">
|
|
||||||
{{ object.physicalproperties.width }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-3 col-md-4 label ">height:</div>
|
|
||||||
<div class="col-lg-9 col-md-8">
|
|
||||||
{{ object.physicalproperties.height }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-3 col-md-4 label ">depth:</div>
|
|
||||||
<div class="col-lg-9 col-md-8">
|
|
||||||
{{ object.physicalproperties.depth }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-3 col-md-4 label ">color:</div>
|
|
||||||
<div class="col-lg-9 col-md-8">
|
|
||||||
{{ object.physicalproperties.color }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-3 col-md-4 label ">image:</div>
|
|
||||||
<div class="col-lg-9 col-md-8">
|
|
||||||
{% if object.physicalproperties.image %}
|
|
||||||
<img width="200px" src="{{ object.physicalproperties.image }}" />
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-3 col-md-4 label">Identifiers</div>
|
||||||
|
</div>
|
||||||
|
{% for chid in object.hids %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">{{ chid |default:"" }}</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-pane fade profile-overview" id="annotations">
|
||||||
|
<div class="btn-group dropdown ml-1 mt-1" uib-dropdown="">
|
||||||
|
<a href="{% url 'device:add_annotation' object.pk %}" class="btn btn-primary">
|
||||||
|
<i class="bi bi-plus"></i>
|
||||||
|
Add new annotation
|
||||||
|
<span class="caret"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h5 class="card-title mt-2">Annotations</h5>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<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_annotations %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ a.key }}</td>
|
||||||
|
<td>{{ a.value }}</td>
|
||||||
|
<td>{{ a.created }}</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="tab-pane fade profile-overview" id="lots">
|
<div class="tab-pane fade profile-overview" id="lots">
|
||||||
<h5 class="card-title">Incoming Lots</h5>
|
{% for tag in lot_tags %}
|
||||||
|
<h5 class="card-title">{{ tag }}</h5>
|
||||||
|
|
||||||
|
{% for lot in object.lots %}
|
||||||
|
{% if lot.type == tag %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
</div>
|
<a href="{% url 'dashboard:lot' lot.id %}">{{ lot.name }}</a>
|
||||||
|
</div>
|
||||||
<h5 class="card-title">Outgoing Lots</h5>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h5 class="card-title">Temporary Lots</h5>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane fade profile-overview" id="documents">
|
<div class="tab-pane fade profile-overview" id="documents">
|
||||||
<div class="btn-group dropdown ml-1 mt-1" uib-dropdown="">
|
<div class="btn-group dropdown ml-1 mt-1" uib-dropdown="">
|
||||||
<a href="/inventory/device/4W8D3/document/add/" class="btn btn-primary">
|
<a href="{% url 'device:add_document' object.pk %}" class="btn btn-primary">
|
||||||
|
|
||||||
<i class="bi bi-plus"></i>
|
<i class="bi bi-plus"></i>
|
||||||
Add new document
|
Add new document
|
||||||
<span class="caret"></span>
|
<span class="caret"></span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h5 class="card-title">Documents</h5>
|
<h5 class="card-title mt-2">Documents</h5>
|
||||||
<table class="table">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">File</th>
|
<th scope="col">Key</th>
|
||||||
<th scope="col">Type</th>
|
<th scope="col">Value</th>
|
||||||
<th scope="col">Description</th>
|
<th scope="col" data-type="date" data-format="YYYY-MM-DD hh:mm">Created on</th>
|
||||||
<th scope="col" data-type="date" data-format="YYYY-MM-DD hh:mm">Uploaded on</th>
|
|
||||||
<th></th>
|
<th></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane fade profile-overview" id="status">
|
|
||||||
<h5 class="card-title">Status Details</h5>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-3 col-md-4 label">Physical State</div>
|
|
||||||
<div class="col-lg-9 col-md-8">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-3 col-md-4 label">Lifecycle State</div>
|
|
||||||
<div class="col-lg-9 col-md-8">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-3 col-md-4 label">Allocated State</div>
|
|
||||||
<div class="col-lg-9 col-md-8">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tab-pane fade profile-overview" id="traceability">
|
|
||||||
<h5 class="card-title">Traceability log Details</h5>
|
|
||||||
<div class="list-group col-6">
|
|
||||||
|
|
||||||
<div class="list-group-item d-flex justify-content-between align-items-center">
|
|
||||||
Snapshot ✓
|
|
||||||
<small class="text-muted">14:07 23-06-2024</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="list-group-item d-flex justify-content-between align-items-center">
|
|
||||||
EraseCrypto ✓
|
|
||||||
<small class="text-muted">14:07 23-06-2024</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="list-group-item d-flex justify-content-between align-items-center">
|
|
||||||
EraseCrypto ✓
|
|
||||||
<small class="text-muted">14:07 23-06-2024</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tab-pane fade profile-overview" id="components">
|
<div class="tab-pane fade profile-overview" id="components">
|
||||||
<h5 class="card-title">Components Snapshot</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 %}
|
||||||
<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">Motherboard</h5>
|
<h5 class="mb-1">{{ c.type }}</h5>
|
||||||
<small class="text-muted">14:07 23-06-2024</small>
|
<small class="text-muted">{{ evidence.created }}</small>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-1">
|
<p class="mb-1">
|
||||||
hp<br />
|
{{ c.manufacturer }}<br />
|
||||||
890e<br />
|
{{ c.model }}<br />
|
||||||
|
{{ c.serialNumber }}<br />
|
||||||
</p>
|
</p>
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-pane fade profile-overview" id="evidences">
|
||||||
|
<h5 class="card-title">List of evidences</h5>
|
||||||
|
<div class="list-group col-6">
|
||||||
|
{% for snap in object.evidences %}
|
||||||
<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">NetworkAdapter</h5>
|
<h5 class="mb-1"></h5>
|
||||||
<small class="text-muted">14:07 23-06-2024</small>
|
<small class="text-muted">{{ snap.created }}</small>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-1">
|
<p class="mb-1">
|
||||||
realtek semiconductor co., ltd.<br />
|
<a href="{% url 'evidence:details' snap.uuid %}">{{ snap.uuid }}</a>
|
||||||
rtl8852ae 802.11ax pcie wireless network adapter<br />
|
|
||||||
</p>
|
</p>
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
{% 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 %}
|
||||||
|
{{ form.management_form }}
|
||||||
|
<div class="container" id="formset-container">
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col"></div>
|
||||||
|
</div>
|
||||||
|
{% for f in form %}
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col">
|
||||||
|
{% bootstrap_field f %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
<a class="btn btn-grey" href="{% url 'dashboard:unassigned_devices' %}">{% translate "Cancel" %}</a>
|
||||||
|
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -8,25 +8,78 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function addForm(button) {
|
||||||
|
var formCount = parseInt(document.getElementById('id_form-TOTAL_FORMS').value);
|
||||||
|
var formCopy = $(document.querySelector('#id_form-0-name')).parent().parent().parent()[0].cloneNode(true);
|
||||||
|
formCopy.querySelectorAll('input').forEach(function(input) {
|
||||||
|
var name = input.name.replace(/form-\d+/g, 'form-' + formCount);
|
||||||
|
var id = 'id_' + name;
|
||||||
|
input.name = name;
|
||||||
|
input.id = id;
|
||||||
|
input.value = '';
|
||||||
|
});
|
||||||
|
document.getElementById('formset-container').appendChild(formCopy);
|
||||||
|
document.getElementById('id_form-TOTAL_FORMS').value = formCount + 1;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
{% load django_bootstrap5 %}
|
{% load django_bootstrap5 %}
|
||||||
<form role="form" method="post">
|
<form role="form" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% if form.errors %}
|
{% if form.errors %}
|
||||||
<div class="alert alert-danger alert-icon alert-icon-border alert-dismissible" role="alert">
|
<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="icon"><span class="mdi mdi-close-circle-o"></span></div>
|
||||||
<div class="message">
|
<div class="message">
|
||||||
{% for field, error in form.errors.items %}
|
{% for field, error in form.errors.items %}
|
||||||
{{ error }}<br />
|
{{ error }}<br />
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<button class="btn-close" type="button" data-dismiss="alert" aria-label="Close"></button>
|
<button class="btn-close" type="button" data-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{{ form.management_form }}
|
||||||
|
<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="col">
|
||||||
|
{% bootstrap_field form.0.type %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col">
|
||||||
|
{% bootstrap_field form.0.amount %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col">
|
||||||
|
{% bootstrap_field form.0.customer_id %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% for f in form %}
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col">
|
||||||
|
{% bootstrap_field f.name %}
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
{% bootstrap_field f.value %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
<a class="btn btn-grey" href="{% url 'dashboard:unassigned_devices' %}">{% translate "Cancel" %}</a>
|
||||||
|
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% bootstrap_form form %}
|
|
||||||
<div class="form-actions-no-box">
|
|
||||||
<a class="btn btn-grey" href="{% url 'dashboard:unassigned_devices' %}">{% translate "Cancel" %}</a>
|
|
||||||
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -5,7 +5,8 @@ app_name = 'device'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("add/", views.NewDeviceView.as_view(), name="add"),
|
path("add/", views.NewDeviceView.as_view(), name="add"),
|
||||||
path("edit/<int:pk>/", views.EditDeviceView.as_view(), name="edit"),
|
path("edit/<str:pk>/", views.EditDeviceView.as_view(), name="edit"),
|
||||||
path("<int:pk>/", views.DetailsView.as_view(), name="details"),
|
path("<str:pk>/", views.DetailsView.as_view(), name="details"),
|
||||||
path("physical/<int:pk>/", views.PhysicalView.as_view(), name="physical_edit"),
|
path("<str:pk>/annotation/add", views.AddAnnotationView.as_view(), name="add_annotation"),
|
||||||
|
path("<str:pk>/document/add", views.AddDocumentView.as_view(), name="add_document"),
|
||||||
]
|
]
|
||||||
|
|
168
device/views.py
168
device/views.py
|
@ -1,60 +1,74 @@
|
||||||
|
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
|
||||||
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,
|
||||||
UpdateView,
|
UpdateView,
|
||||||
|
FormView,
|
||||||
)
|
)
|
||||||
from dashboard.mixins import DashboardView, DetailsMixin
|
from django.views.generic.base import TemplateView
|
||||||
from device.models import Device, PhysicalProperties
|
from dashboard.mixins import DashboardView
|
||||||
|
from evidence.models import Annotation
|
||||||
|
from evidence.xapian import search
|
||||||
|
from lot.models import LotTag
|
||||||
|
from device.models import Device
|
||||||
|
from device.forms import DeviceFormSet
|
||||||
|
|
||||||
|
|
||||||
class NewDeviceView(DashboardView, CreateView):
|
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('dashboard:unassigned_devices')
|
success_url = reverse_lazy('device:add')
|
||||||
model = Device
|
form_class = DeviceFormSet
|
||||||
fields = (
|
|
||||||
'type',
|
|
||||||
"model",
|
|
||||||
"manufacturer",
|
|
||||||
"serial_number",
|
|
||||||
"part_number",
|
|
||||||
"brand",
|
|
||||||
"generation",
|
|
||||||
"version",
|
|
||||||
"production_date",
|
|
||||||
"variant",
|
|
||||||
"family",
|
|
||||||
)
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.owner = self.request.user
|
form.save(self.request.user)
|
||||||
response = super().form_valid(form)
|
response = super().form_valid(form)
|
||||||
PhysicalProperties.objects.create(device=form.instance)
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
def form_invalid(self, form):
|
||||||
|
response = super().form_invalid(form)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
# class AddToLotView(DashboardView, FormView):
|
||||||
|
# template_name = "list_lots.html"
|
||||||
|
# title = _("Add to lots")
|
||||||
|
# breadcrumb = "lot / add to lots"
|
||||||
|
# success_url = reverse_lazy('dashboard:unassigned_devices')
|
||||||
|
# form_class = LotsForm
|
||||||
|
|
||||||
|
# def get_context_data(self, **kwargs):
|
||||||
|
# context = super().get_context_data(**kwargs)
|
||||||
|
# lots = Lot.objects.filter(owner=self.request.user)
|
||||||
|
# lot_tags = LotTag.objects.filter(owner=self.request.user)
|
||||||
|
# context.update({
|
||||||
|
# 'lots': lots,
|
||||||
|
# 'lot_tags':lot_tags,
|
||||||
|
# })
|
||||||
|
# return context
|
||||||
|
|
||||||
|
# def get_form(self):
|
||||||
|
# form = super().get_form()
|
||||||
|
# form.fields["lots"].queryset = Lot.objects.filter(owner=self.request.user)
|
||||||
|
# return form
|
||||||
|
|
||||||
|
# def form_valid(self, form):
|
||||||
|
# form.devices = self.get_session_devices()
|
||||||
|
# form.save()
|
||||||
|
# response = super().form_valid(form)
|
||||||
|
# return response
|
||||||
|
|
||||||
|
|
||||||
class EditDeviceView(DashboardView, UpdateView):
|
class EditDeviceView(DashboardView, UpdateView):
|
||||||
template_name = "new_device.html"
|
template_name = "new_device.html"
|
||||||
title = _("Update Device")
|
title = _("Update Device")
|
||||||
breadcrumb = "Device / Update Device"
|
breadcrumb = "Device / Update Device"
|
||||||
success_url = reverse_lazy('dashboard:unassigned_devices')
|
success_url = reverse_lazy('dashboard:unassigned_devices')
|
||||||
model = Device
|
model = Annotation
|
||||||
fields = (
|
|
||||||
'type',
|
|
||||||
"model",
|
|
||||||
"manufacturer",
|
|
||||||
"serial_number",
|
|
||||||
"part_number",
|
|
||||||
"brand",
|
|
||||||
"generation",
|
|
||||||
"version",
|
|
||||||
"production_date",
|
|
||||||
"variant",
|
|
||||||
"family",
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
pk = self.kwargs.get('pk')
|
pk = self.kwargs.get('pk')
|
||||||
|
@ -64,48 +78,78 @@ class EditDeviceView(DashboardView, UpdateView):
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
class DetailsView(DetailsMixin):
|
class DetailsView(DashboardView, TemplateView):
|
||||||
template_name = "details.html"
|
template_name = "details.html"
|
||||||
title = _("Device")
|
title = _("Device")
|
||||||
breadcrumb = "Device / Details"
|
breadcrumb = "Device / Details"
|
||||||
model = Device
|
model = Annotation
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
class PhysicalView(DashboardView, UpdateView):
|
self.pk = kwargs['pk']
|
||||||
template_name = "physical_properties.html"
|
self.object = Device(id=self.pk)
|
||||||
title = _("Physical Properties")
|
return super().get(request, *args, **kwargs)
|
||||||
breadcrumb = "Device / Physical properties"
|
|
||||||
success_url = reverse_lazy('dashboard:unassigned_devices')
|
|
||||||
model = PhysicalProperties
|
|
||||||
fields = (
|
|
||||||
"weight",
|
|
||||||
"width",
|
|
||||||
"height",
|
|
||||||
"depth",
|
|
||||||
"color",
|
|
||||||
"image",
|
|
||||||
)
|
|
||||||
|
|
||||||
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()
|
||||||
|
lot_tags = LotTag.objects.filter(owner=self.request.user)
|
||||||
context.update({
|
context.update({
|
||||||
'device': self.device,
|
'object': self.object,
|
||||||
|
'snapshot': self.object.get_last_evidence(),
|
||||||
|
'lot_tags': lot_tags,
|
||||||
})
|
})
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
|
||||||
pk = self.kwargs.get('pk')
|
class AddAnnotationView(DashboardView, CreateView):
|
||||||
self.device = get_object_or_404(Device, pk=pk)
|
template_name = "new_annotation.html"
|
||||||
try:
|
title = _("New annotation")
|
||||||
self.object = self.device.physicalproperties
|
breadcrumb = "Device / New annotation"
|
||||||
except Exception:
|
success_url = reverse_lazy('dashboard:unassigned_devices')
|
||||||
self.object = PhysicalProperties.objects.create(device=self.device)
|
model = Annotation
|
||||||
kwargs = super().get_form_kwargs()
|
fields = ("key", "value")
|
||||||
return kwargs
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
self.success_url = reverse_lazy('device:details', args=[self.device.id])
|
form.instance.owner = self.request.user
|
||||||
|
form.instance.uuid = self.annotation.uuid
|
||||||
|
form.instance.type = Annotation.Type.USER
|
||||||
response = super().form_valid(form)
|
response = super().form_valid(form)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
pk = self.kwargs.get('pk')
|
||||||
|
self.annotation = Annotation.objects.filter(
|
||||||
|
owner=self.request.user, value=pk, type=Annotation.Type.SYSTEM
|
||||||
|
).first()
|
||||||
|
if not self.annotation:
|
||||||
|
get_object_or_404(Annotation, pk=0, owner=self.request.user)
|
||||||
|
self.success_url = reverse_lazy('device:details', args=[pk])
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
|
class AddDocumentView(DashboardView, CreateView):
|
||||||
|
template_name = "new_annotation.html"
|
||||||
|
title = _("New Document")
|
||||||
|
breadcrumb = "Device / New document"
|
||||||
|
success_url = reverse_lazy('dashboard:unassigned_devices')
|
||||||
|
model = Annotation
|
||||||
|
fields = ("key", "value")
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
form.instance.owner = self.request.user
|
||||||
|
form.instance.uuid = self.annotation.uuid
|
||||||
|
form.instance.type = Annotation.Type.DOCUMENT
|
||||||
|
response = super().form_valid(form)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
pk = self.kwargs.get('pk')
|
||||||
|
self.annotation = Annotation.objects.filter(
|
||||||
|
owner=self.request.user, value=pk, type=Annotation.Type.SYSTEM
|
||||||
|
).first()
|
||||||
|
if not self.annotation:
|
||||||
|
get_object_or_404(Annotation, pk=0, owner=self.request.user)
|
||||||
|
self.success_url = reverse_lazy('device:details', args=[pk])
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
return kwargs
|
||||||
|
|
|
@ -10,6 +10,8 @@ For the full list of settings and their values, see
|
||||||
https://docs.djangoproject.com/en/5.0/ref/settings/
|
https://docs.djangoproject.com/en/5.0/ref/settings/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import xapian
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from django.contrib.messages import constants as messages
|
from django.contrib.messages import constants as messages
|
||||||
from decouple import config, Csv
|
from decouple import config, Csv
|
||||||
|
@ -45,7 +47,7 @@ INSTALLED_APPS = [
|
||||||
"login",
|
"login",
|
||||||
"user",
|
"user",
|
||||||
"device",
|
"device",
|
||||||
"snapshot",
|
"evidence",
|
||||||
"action",
|
"action",
|
||||||
"tag",
|
"tag",
|
||||||
"lot",
|
"lot",
|
||||||
|
@ -92,11 +94,10 @@ WSGI_APPLICATION = "dhub.wsgi.application"
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
"default": {
|
"default": {
|
||||||
"ENGINE": "django.db.backends.sqlite3",
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
"NAME": BASE_DIR / "db.sqlite3",
|
"NAME": BASE_DIR / "db/db.sqlite3",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Password validation
|
# Password validation
|
||||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
@ -170,3 +171,4 @@ LOGGING = {
|
||||||
}
|
}
|
||||||
|
|
||||||
SNAPSHOT_PATH="/tmp/"
|
SNAPSHOT_PATH="/tmp/"
|
||||||
|
DATA_UPLOAD_MAX_NUMBER_FILES = 1000
|
||||||
|
|
|
@ -21,7 +21,7 @@ urlpatterns = [
|
||||||
# path('api/', include('snapshot.urls')),
|
# path('api/', include('snapshot.urls')),
|
||||||
path("", include("login.urls")),
|
path("", include("login.urls")),
|
||||||
path("dashboard/", include("dashboard.urls")),
|
path("dashboard/", include("dashboard.urls")),
|
||||||
path("snapshot/", include("snapshot.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")),
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
services:
|
||||||
|
devicehub-django:
|
||||||
|
init: true
|
||||||
|
build:
|
||||||
|
dockerfile: docker/devicehub-django.Dockerfile
|
||||||
|
environment:
|
||||||
|
DEBUG: true
|
||||||
|
volumes:
|
||||||
|
- .:/opt/devicehub-django
|
||||||
|
ports:
|
||||||
|
- 8000:8000
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
FROM python:3.11.7-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
|
||||||
|
RUN apt update && \
|
||||||
|
apt-get install -y \
|
||||||
|
python3-xapian \
|
||||||
|
git \
|
||||||
|
sqlite3 \
|
||||||
|
jq \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /opt/devicehub-django
|
||||||
|
|
||||||
|
# reduce size (python specifics) -> src https://stackoverflow.com/questions/74616667/removing-pip-cache-after-installing-dependencies-in-docker-image
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
|
# here document in dockerfile src https://stackoverflow.com/questions/40359282/launch-a-cat-command-unix-into-dockerfile
|
||||||
|
RUN cat > /etc/pip.conf <<END
|
||||||
|
[install]
|
||||||
|
compile = no
|
||||||
|
|
||||||
|
[global]
|
||||||
|
no-cache-dir = True
|
||||||
|
END
|
||||||
|
|
||||||
|
RUN pip install --upgrade pip
|
||||||
|
|
||||||
|
COPY ./requirements.txt /opt/devicehub-django
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
# TODO Is there a better way?
|
||||||
|
# Set PYTHONPATH to include the directory with the xapian module
|
||||||
|
ENV PYTHONPATH="${PYTHONPATH}:/usr/lib/python3/dist-packages"
|
||||||
|
|
||||||
|
COPY docker/devicehub-django.entrypoint.sh /
|
||||||
|
ENTRYPOINT sh /devicehub-django.entrypoint.sh
|
|
@ -0,0 +1,56 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set -u
|
||||||
|
# DEBUG
|
||||||
|
set -x
|
||||||
|
|
||||||
|
check_app_is_there() {
|
||||||
|
if [ ! -f "./manage.py" ]; then
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
deploy() {
|
||||||
|
# detect if existing deployment (TODO only works with sqlite)
|
||||||
|
if [ -f "${program_dir}/db/db.sqlite3" ]; then
|
||||||
|
echo "INFO: detected EXISTING deployment"
|
||||||
|
./manage.py migrate
|
||||||
|
else
|
||||||
|
# move the migrate thing in docker entrypoint
|
||||||
|
# inspired by https://medium.com/analytics-vidhya/django-with-docker-and-docker-compose-python-part-2-8415976470cc
|
||||||
|
echo "INFO detected NEW deployment"
|
||||||
|
./manage.py migrate
|
||||||
|
./manage.py add_user user@example.org 1234
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
runserver() {
|
||||||
|
PORT="${PORT:-8000}"
|
||||||
|
if [ "${DEBUG:-}" = "true" ]; then
|
||||||
|
./manage.py runserver 0.0.0.0:${PORT}
|
||||||
|
else
|
||||||
|
# TODO
|
||||||
|
#./manage.py collectstatic
|
||||||
|
true
|
||||||
|
if [ "${EXPERIMENTAL:-}" = "true" ]; then
|
||||||
|
# TODO
|
||||||
|
# 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
|
||||||
|
# gunicorn with 1 worker, with more than 1 worker this is not expected to work
|
||||||
|
#gunicorn --access-logfile - --error-logfile - -b :${PORT} trustchain_idhub.wsgi:application
|
||||||
|
true
|
||||||
|
else
|
||||||
|
./manage.py runserver 0.0.0.0:${PORT}
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
program_dir='/opt/devicehub-django'
|
||||||
|
cd "${program_dir}"
|
||||||
|
deploy
|
||||||
|
runserver
|
||||||
|
}
|
||||||
|
|
||||||
|
main "${@}"
|
|
@ -3,4 +3,4 @@ from django.apps import AppConfig
|
||||||
|
|
||||||
class ActionConfig(AppConfig):
|
class ActionConfig(AppConfig):
|
||||||
default_auto_field = "django.db.models.BigAutoField"
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
name = "snapshot"
|
name = "evidence"
|
|
@ -0,0 +1,130 @@
|
||||||
|
import json
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from utils.device import create_annotation, create_doc, create_index
|
||||||
|
from utils.forms import MultipleFileField
|
||||||
|
from device.models import Device
|
||||||
|
from evidence.parse import Build
|
||||||
|
from evidence.models import Annotation
|
||||||
|
|
||||||
|
|
||||||
|
class UploadForm(forms.Form):
|
||||||
|
evidence_file = MultipleFileField(label=_("File"))
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
self.evidences = []
|
||||||
|
data = self.cleaned_data.get('evidence_file')
|
||||||
|
if not data:
|
||||||
|
return False
|
||||||
|
|
||||||
|
for f in data:
|
||||||
|
file_name = f.name
|
||||||
|
file_data = f.read()
|
||||||
|
if not file_name or not file_data:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
file_json = json.loads(file_data)
|
||||||
|
Build(file_json, None, check=True)
|
||||||
|
exist_annotation = Annotation.objects.filter(
|
||||||
|
uuid=file_json['uuid']
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if exist_annotation:
|
||||||
|
raise ValidationError("error: {} exist".format(file_name))
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
raise ValidationError("error in: {}".format(file_name))
|
||||||
|
|
||||||
|
self.evidences.append((file_name, file_json))
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def save(self, user, commit=True):
|
||||||
|
if not commit or not user:
|
||||||
|
return
|
||||||
|
|
||||||
|
for ev in self.evidences:
|
||||||
|
Build(ev[1], user)
|
||||||
|
|
||||||
|
|
||||||
|
class UserTagForm(forms.Form):
|
||||||
|
tag = forms.CharField(label=_("Tag"))
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.uuid = kwargs.pop('uuid', None)
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
data = self.cleaned_data.get('tag')
|
||||||
|
if not data:
|
||||||
|
return False
|
||||||
|
self.tag = data
|
||||||
|
return True
|
||||||
|
|
||||||
|
def save(self, user, commit=True):
|
||||||
|
if not commit:
|
||||||
|
return
|
||||||
|
|
||||||
|
Annotation.objects.create(
|
||||||
|
uuid=self.uuid,
|
||||||
|
owner=user,
|
||||||
|
type=Annotation.Type.SYSTEM,
|
||||||
|
key='CUSTOM_ID',
|
||||||
|
value=self.tag
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ImportForm(forms.Form):
|
||||||
|
file_import = forms.FileField(label=_("File to import"))
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
|
self.rows = []
|
||||||
|
self.properties = {}
|
||||||
|
self.user = kwargs.pop('user')
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def clean_file_import(self):
|
||||||
|
data = self.cleaned_data["file_import"]
|
||||||
|
|
||||||
|
self.file_name = data.name
|
||||||
|
df = pd.read_excel(data)
|
||||||
|
df.fillna('', inplace=True)
|
||||||
|
|
||||||
|
data_pd = df.to_dict(orient='index')
|
||||||
|
|
||||||
|
if not data_pd or df.last_valid_index() is None:
|
||||||
|
self.exception(_("The file you try to import is empty!"))
|
||||||
|
|
||||||
|
for n in data_pd.keys():
|
||||||
|
if 'type' not in [x.lower() for x in data_pd[n]]:
|
||||||
|
raise ValidationError("You need a column with name 'type'")
|
||||||
|
|
||||||
|
for k, v in data_pd[n].items():
|
||||||
|
if k.lower() == "type":
|
||||||
|
if v not in Device.Types.values:
|
||||||
|
raise ValidationError("{} is not a valid device".format(v))
|
||||||
|
|
||||||
|
self.rows.append(data_pd[n])
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
table = []
|
||||||
|
for row in self.rows:
|
||||||
|
doc = create_doc(row)
|
||||||
|
annotation = create_annotation(doc, self.user)
|
||||||
|
table.append((doc, annotation))
|
||||||
|
|
||||||
|
if commit:
|
||||||
|
for doc, cred in table:
|
||||||
|
cred.save()
|
||||||
|
create_index(doc)
|
||||||
|
return table
|
||||||
|
|
||||||
|
return
|
|
@ -4,7 +4,7 @@ import json
|
||||||
|
|
||||||
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 snapshot.parse import Build
|
from evidence.parse import Build
|
||||||
|
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Generated by Django 5.0.6 on 2024-07-27 16:23
|
||||||
|
|
||||||
|
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="Annotation",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("created", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("uuid", models.UUIDField()),
|
||||||
|
(
|
||||||
|
"type",
|
||||||
|
models.SmallIntegerField(choices=[(0, "System"), (1, "User")]),
|
||||||
|
),
|
||||||
|
("key", models.CharField(max_length=256)),
|
||||||
|
("value", models.CharField(max_length=256)),
|
||||||
|
(
|
||||||
|
"owner",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name="annotation",
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
fields=("type", "key", "uuid"), name="unique_type_key_uuid"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# 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")]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# 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")]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,78 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from utils.constants import STR_SM_SIZE, STR_EXTEND_SIZE
|
||||||
|
from evidence.xapian import search
|
||||||
|
from user.models import User
|
||||||
|
|
||||||
|
|
||||||
|
class Annotation(models.Model):
|
||||||
|
class Type(models.IntegerChoices):
|
||||||
|
SYSTEM= 0, "System"
|
||||||
|
USER = 1, "User"
|
||||||
|
DOCUMENT = 2, "Document"
|
||||||
|
|
||||||
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
uuid = models.UUIDField()
|
||||||
|
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
type = models.SmallIntegerField(choices=Type)
|
||||||
|
key = models.CharField(max_length=STR_EXTEND_SIZE)
|
||||||
|
value = models.CharField(max_length=STR_EXTEND_SIZE)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(fields=["type", "key", "uuid"], name="unique_type_key_uuid")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Evidence:
|
||||||
|
def __init__(self, uuid):
|
||||||
|
self.uuid = uuid
|
||||||
|
self.owner = None
|
||||||
|
self.doc = None
|
||||||
|
self.created = None
|
||||||
|
self.annotations = []
|
||||||
|
|
||||||
|
self.get_owner()
|
||||||
|
self.get_time()
|
||||||
|
|
||||||
|
def get_annotations(self):
|
||||||
|
self.annotations = Annotation.objects.filter(
|
||||||
|
uuid=self.uuid
|
||||||
|
).order_by("created")
|
||||||
|
|
||||||
|
def get_owner(self):
|
||||||
|
if not self.annotations:
|
||||||
|
self.get_annotations()
|
||||||
|
a = self.annotations.first()
|
||||||
|
if a:
|
||||||
|
self.owner = a.owner
|
||||||
|
|
||||||
|
def get_doc(self):
|
||||||
|
self.doc = {}
|
||||||
|
qry = 'uuid:"{}"'.format(self.uuid)
|
||||||
|
matches = search(qry, limit=1)
|
||||||
|
if matches.size() < 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
for xa in matches:
|
||||||
|
self.doc = json.loads(xa.document.get_data())
|
||||||
|
|
||||||
|
def get_time(self):
|
||||||
|
if not self.doc:
|
||||||
|
self.get_doc()
|
||||||
|
self.created = self.doc.get("endTime")
|
||||||
|
|
||||||
|
if not self.created:
|
||||||
|
self.created = self.annotations.last().created
|
||||||
|
|
||||||
|
def components(self):
|
||||||
|
return self.doc.get('components', [])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_all(cls, user):
|
||||||
|
return Annotation.objects.filter(
|
||||||
|
owner=user,
|
||||||
|
type=Annotation.Type.SYSTEM,
|
||||||
|
).order_by("-created").values_list("uuid", flat=True).distinct()
|
|
@ -0,0 +1,54 @@
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from evidence.xapian import search, index
|
||||||
|
from evidence.models import Evidence, Annotation
|
||||||
|
from utils.constants import ALGOS
|
||||||
|
|
||||||
|
|
||||||
|
class Build:
|
||||||
|
def __init__(self, evidence_json, user, check=False):
|
||||||
|
self.json = evidence_json
|
||||||
|
self.uuid = self.json['uuid']
|
||||||
|
self.user = user
|
||||||
|
self.hid = None
|
||||||
|
self.generate_chids()
|
||||||
|
|
||||||
|
if check:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.index()
|
||||||
|
self.create_annotations()
|
||||||
|
|
||||||
|
def index(self):
|
||||||
|
snap = json.dumps(self.json)
|
||||||
|
index(self.uuid, snap)
|
||||||
|
|
||||||
|
def generate_chids(self):
|
||||||
|
self.algorithms = {
|
||||||
|
'hidalgo1': self.get_hid_14(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_hid_14(self):
|
||||||
|
device = self.json['device']
|
||||||
|
manufacturer = device.get("manufacturer", '')
|
||||||
|
model = device.get("model", '')
|
||||||
|
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()
|
||||||
|
|
||||||
|
def create_annotations(self):
|
||||||
|
|
||||||
|
for k, v in self.algorithms.items():
|
||||||
|
Annotation.objects.create(
|
||||||
|
uuid=self.uuid,
|
||||||
|
owner=self.user,
|
||||||
|
type=Annotation.Type.SYSTEM,
|
||||||
|
key=k,
|
||||||
|
value=v
|
||||||
|
)
|
|
@ -1,15 +1,15 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from snapshot.models import SnapshotJson
|
from evidence.models import EvidenceJson
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from snapshot.parse import Parse
|
from evidence.parse import Parse
|
||||||
|
|
||||||
class SnapshotSerializer(serializers.ModelSerializer):
|
class EvidenceSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SnapshotJson
|
model = EvidenceJson
|
||||||
fields = ['id', 'title', 'content']
|
fields = ['id', 'title', 'content']
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
|
@ -0,0 +1,76 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h3>{{ object.id }}</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="#device">Devices</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-items">
|
||||||
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#tag">Tag</button>
|
||||||
|
<li class="nav-items">
|
||||||
|
<a href="{% url 'evidence:download' object.uuid %}" class="nav-link">Download File</a>
|
||||||
|
</li>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-content pt-2">
|
||||||
|
|
||||||
|
<div class="tab-pane fade show active" id="device">
|
||||||
|
<h5 class="card-title">List of chids</h5>
|
||||||
|
<div class="list-group col-6">
|
||||||
|
{% for snap in object.annotations %}
|
||||||
|
{% if snap.type == 0 %}
|
||||||
|
<div class="list-group-item">
|
||||||
|
<div class="d-flex w-100 justify-content-between">
|
||||||
|
<h5 class="mb-1"></h5>
|
||||||
|
<small class="text-muted">
|
||||||
|
{{ snap.created }}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<p class="mb-1">
|
||||||
|
{{ snap.key }}<br />
|
||||||
|
</p>
|
||||||
|
<small class="text-muted">
|
||||||
|
<a href="{% url 'device:details' snap.value %}">{{ snap.value }}</a>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="tag">
|
||||||
|
{% load django_bootstrap5 %}
|
||||||
|
<div class="list-group col-6">
|
||||||
|
<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="container">
|
||||||
|
<a class="btn btn-grey" href="{% url 'dashboard:unassigned_devices' %}">{% translate "Cancel" %}</a>
|
||||||
|
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,26 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h3>{{ subtitle }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<table class="table table-striped table-sm">
|
||||||
|
{% for ev in evidences %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="{% url 'evidence:details' ev %}">{{ ev }}</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="{# url 'evidence:delete' ev #}"><i class="bi bi-trash text-danger"></i></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</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" enctype="multipart/form-data">
|
||||||
|
{% 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 'dashboard:unassigned_devices' %}">{% translate "Cancel" %}</a>
|
||||||
|
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,22 @@
|
||||||
|
# from django.urls import path, include
|
||||||
|
# from rest_framework.routers import DefaultRouter
|
||||||
|
# from snapshot.views import SnapshotViewSet
|
||||||
|
|
||||||
|
# router = DefaultRouter()
|
||||||
|
# router.register(r'snapshots', SnapshotViewSet)
|
||||||
|
|
||||||
|
# urlpatterns = [
|
||||||
|
# path('', include(router.urls)),
|
||||||
|
# ]
|
||||||
|
from django.urls import path
|
||||||
|
from evidence import views
|
||||||
|
|
||||||
|
app_name = 'evidence'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("", views.ListEvidencesView.as_view(), name="list"),
|
||||||
|
path("upload", views.UploadView.as_view(), name="upload"),
|
||||||
|
path("import", views.ImportView.as_view(), name="import"),
|
||||||
|
path("<uuid:pk>", views.EvidenceView.as_view(), name="details"),
|
||||||
|
path("<uuid:pk>/download", views.DownloadEvidenceView.as_view(), name="download"),
|
||||||
|
]
|
|
@ -0,0 +1,137 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.views.generic.base import TemplateView
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from django.views.generic.edit import (
|
||||||
|
FormView,
|
||||||
|
)
|
||||||
|
|
||||||
|
from dashboard.mixins import DashboardView, Http403
|
||||||
|
from evidence.models import Evidence
|
||||||
|
from evidence.forms import UploadForm, UserTagForm, ImportForm
|
||||||
|
# from django.shortcuts import render
|
||||||
|
# from rest_framework import viewsets
|
||||||
|
# from snapshot.serializers import SnapshotSerializer
|
||||||
|
|
||||||
|
|
||||||
|
# class SnapshotViewSet(viewsets.ModelViewSet):
|
||||||
|
# queryset = Snapshot.objects.all()
|
||||||
|
# serializer_class = SnapshotSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class ListEvidencesView(DashboardView, TemplateView):
|
||||||
|
template_name = "evidences.html"
|
||||||
|
section = "evidences"
|
||||||
|
title = _("Evidences")
|
||||||
|
breadcrumb = "Evidences"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
evidences = Evidence.get_all(self.request.user)
|
||||||
|
|
||||||
|
context.update({
|
||||||
|
'evidences': evidences,
|
||||||
|
})
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class UploadView(DashboardView, FormView):
|
||||||
|
template_name = "upload.html"
|
||||||
|
section = "evidences"
|
||||||
|
title = _("Upload Evidence")
|
||||||
|
breadcrumb = "Evidences / Upload"
|
||||||
|
success_url = reverse_lazy('evidence:list')
|
||||||
|
form_class = UploadForm
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
form.save(self.request.user)
|
||||||
|
response = super().form_valid(form)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def form_invalid(self, form):
|
||||||
|
response = super().form_invalid(form)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class ImportView(DashboardView, FormView):
|
||||||
|
template_name = "upload.html"
|
||||||
|
section = "evidences"
|
||||||
|
title = _("Import Evidence")
|
||||||
|
breadcrumb = "Evidences / Import"
|
||||||
|
success_url = reverse_lazy('evidence:list')
|
||||||
|
form_class = ImportForm
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
kwargs['user'] = self.request.user
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
form.save()
|
||||||
|
response = super().form_valid(form)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def form_invalid(self, form):
|
||||||
|
response = super().form_invalid(form)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class EvidenceView(DashboardView, FormView):
|
||||||
|
template_name = "ev_details.html"
|
||||||
|
section = "evidences"
|
||||||
|
title = _("Evidences")
|
||||||
|
breadcrumb = "Evidences / Details"
|
||||||
|
success_url = reverse_lazy('evidence:list')
|
||||||
|
form_class = UserTagForm
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
self.pk = kwargs['pk']
|
||||||
|
self.object = Evidence(self.pk)
|
||||||
|
if self.object.owner != self.request.user:
|
||||||
|
raise Http403
|
||||||
|
|
||||||
|
self.object.get_annotations()
|
||||||
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context.update({
|
||||||
|
'object': self.object,
|
||||||
|
})
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
self.pk = self.kwargs.get('pk')
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
kwargs['uuid'] = self.pk
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
form.save(self.request.user)
|
||||||
|
response = super().form_valid(form)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def form_invalid(self, form):
|
||||||
|
response = super().form_invalid(form)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
success_url = reverse_lazy('evidence:details', args=[self.pk])
|
||||||
|
return success_url
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadEvidenceView(DashboardView, TemplateView):
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
pk = kwargs['pk']
|
||||||
|
evidence = Evidence(pk)
|
||||||
|
if evidence.owner != self.request.user:
|
||||||
|
raise Http403()
|
||||||
|
|
||||||
|
evidence.get_doc()
|
||||||
|
data = json.dumps(evidence.doc)
|
||||||
|
response = HttpResponse(data, content_type="application/json")
|
||||||
|
response['Content-Disposition'] = 'attachment; filename={}'.format("credential.json")
|
||||||
|
return response
|
|
@ -0,0 +1,51 @@
|
||||||
|
import xapian
|
||||||
|
|
||||||
|
# database = xapian.WritableDatabase("db", xapian.DB_CREATE_OR_OPEN)
|
||||||
|
|
||||||
|
# Read Only
|
||||||
|
# database = xapian.Database("db")
|
||||||
|
|
||||||
|
# indexer = xapian.TermGenerator()
|
||||||
|
# stemmer = xapian.Stem("english")
|
||||||
|
# indexer.set_stemmer(stemmer)
|
||||||
|
|
||||||
|
|
||||||
|
def search(qs, offset=0, limit=10):
|
||||||
|
database = xapian.Database("db")
|
||||||
|
|
||||||
|
qp = xapian.QueryParser()
|
||||||
|
qp.set_database(database)
|
||||||
|
qp.set_stemmer(xapian.Stem("english"))
|
||||||
|
qp.set_stemming_strategy(xapian.QueryParser.STEM_SOME)
|
||||||
|
qp.add_prefix("uuid", "uuid")
|
||||||
|
# qp.add_prefix("snapshot", "snapshot")
|
||||||
|
query = qp.parse_query(qs)
|
||||||
|
enquire = xapian.Enquire(database)
|
||||||
|
enquire.set_query(query)
|
||||||
|
matches = enquire.get_mset(offset, limit)
|
||||||
|
return matches
|
||||||
|
|
||||||
|
|
||||||
|
def index(uuid, snap):
|
||||||
|
uuid = 'uuid:"{}"'.format(uuid)
|
||||||
|
try:
|
||||||
|
matches = search(uuid, limit=1)
|
||||||
|
if matches.size() > 0:
|
||||||
|
return
|
||||||
|
except (xapian.DatabaseNotFoundError, xapian.DatabaseOpeningError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
database = xapian.WritableDatabase("db", xapian.DB_CREATE_OR_OPEN)
|
||||||
|
indexer = xapian.TermGenerator()
|
||||||
|
stemmer = xapian.Stem("english")
|
||||||
|
indexer.set_stemmer(stemmer)
|
||||||
|
|
||||||
|
doc = xapian.Document()
|
||||||
|
doc.set_data(snap)
|
||||||
|
|
||||||
|
indexer.set_document(doc)
|
||||||
|
indexer.index_text(snap)
|
||||||
|
indexer.index_text(uuid, 10, "uuid")
|
||||||
|
# indexer.index_text(snap, 1, "snapshot")
|
||||||
|
|
||||||
|
database.add_document(doc)
|
Binary file not shown.
|
@ -0,0 +1 @@
|
||||||
|
{"type": "Snapshot", "components": [{"type": "SoundCard", "model": "Xeon E3-1200 V3/4th Gen Core Processor Hd Audio Controller", "manufacturer": "Intel Corporation"}, {"type": "SoundCard", "model": "Hp Hd Webcam", "manufacturer": "Chicony Electronics Co.,ltd.", "serialNumber": "200901010001"}, {"type": "SoundCard", "model": "8 Series/c220 Series Chipset High Definition Audio Controller", "manufacturer": "Intel Corporation"}, {"type": "RamModule", "model": "M471b5273ch0-Yk0", "manufacturer": "Samsung", "serialNumber": "96C0FA89", "size": 4096, "speed": 1600, "interface": "DDR3", "format": "SODIMM"}, {"type": "Processor", "actions": [{"type": "BenchmarkProcessor", "elapsed": 0, "rate": 19951.48}, {"type": "BenchmarkProcessorSysbench", "elapsed": 15, "rate": 14.6652}], "model": "Intel Core I5-4200m Cpu @ 2.50ghz", "manufacturer": "Intel Corp.", "brand": "Core i5", "generation": 4, "speed": 2.524414, "cores": 2, "threads": 4, "address": 64}, {"type": "NetworkAdapter", "model": "Ethernet Connection I217-V", "manufacturer": "Intel Corporation", "serialNumber": "FC:15:B4:E7:5D:D7", "variant": "04", "speed": 1000, "wireless": false}, {"type": "NetworkAdapter", "model": "Bcm43228 802.11a/b/g/n", "manufacturer": "Broadcom Inc. and Subsidiaries", "variant": "00", "wireless": false}, {"type": "SolidStateDrive", "actions": [{"type": "BenchmarkDataStorage", "elapsed": 2, "readSpeed": 487, "writeSpeed": 179}], "model": "Emtec X150 120gb", "serialNumber": "LDS645R002202", "variant": "5.0", "size": 120034.123776, "interface": "ATA"}, {"type": "Display", "model": "Lcd Monitor", "manufacturer": "Auo", "productionDate": "2012-01-01T00:00:00", "size": 15.529237982414482, "technology": "LCD", "resolutionWidth": 1366, "resolutionHeight": 768, "refreshRate": 60}, {"type": "GraphicCard", "model": "4th Gen Core Processor Integrated Graphics Controller", "manufacturer": "Intel Corporation"}, {"type": "Motherboard", "model": "1993", "manufacturer": "Hewlett-Packard", "serialNumber": "PEBJK001X5ZI3Z", "version": "L77 Ver. 01.05", "slots": 2, "usb": 3, "firewire": 0, "serial": 1, "pcmcia": 0, "biosDate": "2014-04-28T22:00:00.000Z", "ramSlots": 2, "ramMaxSize": 16}], "device": {"type": "Laptop", "actions": [{"type": "BenchmarkRamSysbench", "elapsed": 1, "rate": 0.731}], "model": "Hp Probook 650 G1", "manufacturer": "Hewlett-Packard", "serialNumber": "CNU406CLGR", "version": "A3009DD10303", "sku": "H5G75EA#ABE", "chassis": "Netbook"}, "closed": true, "endTime": "2022-06-09T12:10:50.809Z", "uuid": "7928afeb-e6a4-464a-a842-0c3de0d01677", "software": "Workbench", "version": "12.0b0", "elapsed": 34}
|
|
@ -1,6 +1,7 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from lot.models import Lot
|
from lot.models import Lot
|
||||||
|
|
||||||
|
|
||||||
class LotsForm(forms.Form):
|
class LotsForm(forms.Form):
|
||||||
lots = forms.ModelMultipleChoiceField(
|
lots = forms.ModelMultipleChoiceField(
|
||||||
queryset=Lot.objects.all(),
|
queryset=Lot.objects.all(),
|
||||||
|
@ -8,20 +9,20 @@ class LotsForm(forms.Form):
|
||||||
)
|
)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
# import pdb; pdb.set_trace()
|
|
||||||
self._lots = self.cleaned_data.get("lots")
|
self._lots = self.cleaned_data.get("lots")
|
||||||
return self._lots
|
return self._lots
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
if not commit:
|
if not commit:
|
||||||
return
|
return
|
||||||
|
|
||||||
for dev in self.devices:
|
for dev in self.devices:
|
||||||
for lot in self._lots:
|
for lot in self._lots:
|
||||||
lot.devices.add(dev.id)
|
lot.add(dev.id)
|
||||||
return
|
return
|
||||||
|
|
||||||
def remove(self):
|
def remove(self):
|
||||||
for dev in self.devices:
|
for dev in self.devices:
|
||||||
for lot in self._lots:
|
for lot in self._lots:
|
||||||
lot.devices.remove(dev.id)
|
lot.remove(dev.id)
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 5.0.6 on 2024-07-08 13:55
|
# Generated by Django 5.0.6 on 2024-07-27 16:23
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -10,7 +10,6 @@ class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("device", "0003_remove_component_type_remove_computer_type_and_more"),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -29,23 +28,10 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
("created", models.DateTimeField(auto_now_add=True)),
|
("created", models.DateTimeField(auto_now_add=True)),
|
||||||
("updated", models.DateTimeField(auto_now=True)),
|
("updated", models.DateTimeField(auto_now=True)),
|
||||||
(
|
|
||||||
"type",
|
|
||||||
models.CharField(
|
|
||||||
choices=[
|
|
||||||
("Incoming", "Incoming"),
|
|
||||||
("Outgoing", "Outgoing"),
|
|
||||||
("Temporal", "Temporal"),
|
|
||||||
],
|
|
||||||
default="Temporal",
|
|
||||||
max_length=32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("name", models.CharField(blank=True, max_length=64, null=True)),
|
("name", models.CharField(blank=True, max_length=64, null=True)),
|
||||||
("code", models.CharField(blank=True, max_length=64, null=True)),
|
("code", models.CharField(blank=True, max_length=64, null=True)),
|
||||||
("description", models.CharField(blank=True, max_length=64, null=True)),
|
("description", models.CharField(blank=True, max_length=64, null=True)),
|
||||||
("closed", models.BooleanField(default=True)),
|
("closed", models.BooleanField(default=True)),
|
||||||
("devices", models.ManyToManyField(to="device.device")),
|
|
||||||
(
|
(
|
||||||
"owner",
|
"owner",
|
||||||
models.ForeignKey(
|
models.ForeignKey(
|
||||||
|
@ -55,4 +41,54 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="DeviceLot",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("device_id", models.CharField(max_length=256)),
|
||||||
|
(
|
||||||
|
"lot",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE, to="lot.lot"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="LotTag",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=64)),
|
||||||
|
(
|
||||||
|
"owner",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="lot",
|
||||||
|
name="type",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE, to="lot.lottag"
|
||||||
|
),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
# 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,41 +1,58 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from utils.constants import STR_SM_SIZE, STR_SIZE
|
from utils.constants import (
|
||||||
|
STR_SM_SIZE,
|
||||||
|
STR_SIZE,
|
||||||
|
STR_EXTEND_SIZE,
|
||||||
|
)
|
||||||
|
|
||||||
from user.models import User
|
from user.models import User
|
||||||
from device.models import Device
|
# from device.models import Device
|
||||||
|
from evidence.models import Annotation
|
||||||
|
|
||||||
|
|
||||||
|
class LotTag(models.Model):
|
||||||
|
name = models.CharField(max_length=STR_SIZE, blank=False, null=False)
|
||||||
|
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceLot(models.Model):
|
||||||
|
lot = models.ForeignKey("Lot", on_delete=models.CASCADE)
|
||||||
|
device_id = models.CharField(max_length=STR_EXTEND_SIZE, blank=False, null=False)
|
||||||
|
|
||||||
|
|
||||||
class Lot(models.Model):
|
class Lot(models.Model):
|
||||||
class Types(models.TextChoices):
|
|
||||||
INCOMING = "Incoming"
|
|
||||||
OUTGOING = "Outgoing"
|
|
||||||
TEMPORAL = "Temporal"
|
|
||||||
|
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
updated = models.DateTimeField(auto_now=True)
|
updated = models.DateTimeField(auto_now=True)
|
||||||
type = models.CharField(max_length=STR_SM_SIZE, choices=Types, default=Types.TEMPORAL)
|
|
||||||
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=True)
|
||||||
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
devices = models.ManyToManyField(Device)
|
type = models.ForeignKey(LotTag, on_delete=models.CASCADE)
|
||||||
|
|
||||||
@property
|
def add(self, v):
|
||||||
def is_incoming(self):
|
if DeviceLot.objects.filter(lot=self, device_id=v).exists():
|
||||||
if self.type == self.Types.INCOMING:
|
return
|
||||||
return True
|
DeviceLot.objects.create(lot=self, device_id=v)
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
def remove(self, v):
|
||||||
def is_outgoing(self):
|
for d in DeviceLot.objects.filter(lot=self, device_id=v):
|
||||||
if self.type == self.Types.OUTGOING:
|
d.delete()
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_temporal(self):
|
class LotAnnotation(models.Model):
|
||||||
if self.type == self.Types.TEMPORAL:
|
class Type(models.IntegerChoices):
|
||||||
return True
|
SYSTEM= 0, "System"
|
||||||
return False
|
USER = 1, "User"
|
||||||
|
DOCUMENT = 2, "Document"
|
||||||
|
|
||||||
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
lot = models.ForeignKey(Lot, on_delete=models.CASCADE)
|
||||||
|
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
type = models.SmallIntegerField(choices=Type)
|
||||||
|
key = models.CharField(max_length=STR_EXTEND_SIZE)
|
||||||
|
value = models.CharField(max_length=STR_EXTEND_SIZE)
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h3>Lot {{ lot.name }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="tab-pane fade show active" id="details">
|
||||||
|
<div class="btn-group dropdown ml-1 mt-1" uib-dropdown="">
|
||||||
|
<a href="{% url 'lot:add_annotation' lot.pk %}" class="btn btn-primary">
|
||||||
|
|
||||||
|
<i class="bi bi-plus"></i>
|
||||||
|
Add new annotation
|
||||||
|
<span class="caret"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h5 class="card-title mt-2">Annotations</h5>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<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 annotations %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ a.key }}</td>
|
||||||
|
<td>{{ a.value }}</td>
|
||||||
|
<td>{{ a.created }}</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,48 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h3>Lot {{ lot.name }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="tab-pane fade show active" id="details">
|
||||||
|
<div class="btn-group dropdown ml-1 mt-1" uib-dropdown="">
|
||||||
|
<a href="{% url 'lot:add_document' lot.pk %}" class="btn btn-primary">
|
||||||
|
|
||||||
|
<i class="bi bi-plus"></i>
|
||||||
|
Add new document
|
||||||
|
<span class="caret"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h5 class="card-title mt-2">Documents</h5>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<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 documents %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ a.key }}</td>
|
||||||
|
<td>{{ a.value }}</td>
|
||||||
|
<td>{{ a.created }}</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -11,50 +11,21 @@
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
{% if incoming %}
|
{% for tag in lot_tags %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-3 col-md-4 label ">Incoming Lots</div>
|
<div class="col-lg-3 col-md-4 label ">{{ tag }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% for lot in lots %}
|
{% for lot in lots %}
|
||||||
{% if lot.is_incoming %}
|
{% if lot.type == tag %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-3 col-md-4 label "><input type="checkbox" name="lots" value="{{ lot.id }}" /></div>
|
<div class="col-lg-3 col-md-4 label "><input type="checkbox" name="lots" value="{{ lot.id }}" /></div>
|
||||||
<div class="col-lg-3 col-md-4 label ">{{ lot.name }}</div>
|
<div class="col-lg-3 col-md-4 label ">{{ lot.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if outgoing %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-3 col-md-4 label ">Outgoing Lots</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% for lot in lots %}
|
|
||||||
{% if lot.is_outgoing %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-3 col-md-4 label "><input type="checkbox" name="lots" value="{{ lot.id }}" /></div>
|
|
||||||
<div class="col-lg-3 col-md-4 label ">{{ lot.name }}</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if temporal %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-3 col-md-4 label ">Temporary Lots</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% for lot in lots %}
|
|
||||||
{% if lot.is_temporal %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-3 col-md-4 label "><input type="checkbox" name="lots" value="{{ lot.id }}" /></div>
|
|
||||||
<div class="col-lg-3 col-md-4 label ">{{ lot.name }}</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
<button type="submit">Save</button>
|
<button type="submit">Save</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
{% 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 %}
|
||||||
|
{{ form.management_form }}
|
||||||
|
<div class="container" id="formset-container">
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col"></div>
|
||||||
|
</div>
|
||||||
|
{% for f in form %}
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col">
|
||||||
|
{% bootstrap_field f %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
<a class="btn btn-grey" href="{% url 'dashboard:unassigned_devices' %}">{% translate "Cancel" %}</a>
|
||||||
|
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -9,7 +9,9 @@ urlpatterns = [
|
||||||
path("edit/<int:pk>/", views.EditLotView.as_view(), name="edit"),
|
path("edit/<int:pk>/", views.EditLotView.as_view(), name="edit"),
|
||||||
path("add/devices/", views.AddToLotView.as_view(), name="add_devices"),
|
path("add/devices/", views.AddToLotView.as_view(), name="add_devices"),
|
||||||
path("del/devices/", views.DelToLotView.as_view(), name="del_devices"),
|
path("del/devices/", views.DelToLotView.as_view(), name="del_devices"),
|
||||||
path("temporal/", views.LotsTemporalView.as_view(), name="lots_temporal"),
|
path("tag/<int:pk>/", views.LotsTagsView.as_view(), name="tag"),
|
||||||
path("outgoing/", views.LotsOutgoingView.as_view(), name="lots_outgoing"),
|
path("<int:pk>/document/", views.LotDocumentsView.as_view(), name="documents"),
|
||||||
path("incoming/", views.LotsIncomingView.as_view(), name="lots_incoming"),
|
path("<int:pk>/document/add", views.LotAddDocumentView.as_view(), name="add_document"),
|
||||||
|
path("<int:pk>/annotation", views.LotAnnotationsView.as_view(), name="annotations"),
|
||||||
|
path("<int:pk>/annotation/add", views.LotAddAnnotationView.as_view(), name="add_annotation"),
|
||||||
]
|
]
|
||||||
|
|
127
lot/views.py
127
lot/views.py
|
@ -6,10 +6,10 @@ from django.views.generic.edit import (
|
||||||
CreateView,
|
CreateView,
|
||||||
DeleteView,
|
DeleteView,
|
||||||
UpdateView,
|
UpdateView,
|
||||||
FormView
|
FormView,
|
||||||
)
|
)
|
||||||
from dashboard.mixins import DashboardView
|
from dashboard.mixins import DashboardView
|
||||||
from lot.models import Lot
|
from lot.models import Lot, LotTag, LotAnnotation
|
||||||
from lot.forms import LotsForm
|
from lot.forms import LotsForm
|
||||||
|
|
||||||
|
|
||||||
|
@ -84,20 +84,15 @@ 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)
|
||||||
lots_incoming = lots.filter(type=Lot.Types.INCOMING).exists()
|
lot_tags = LotTag.objects.filter(owner=self.request.user)
|
||||||
lots_outgoing = lots.filter(type=Lot.Types.OUTGOING).exists()
|
|
||||||
lots_temporal = lots.filter(type=Lot.Types.TEMPORAL).exists()
|
|
||||||
context.update({
|
context.update({
|
||||||
'lots': lots,
|
'lots': lots,
|
||||||
'incoming': lots_incoming,
|
'lot_tags':lot_tags,
|
||||||
'outgoing': lots_outgoing,
|
|
||||||
'temporal': lots_temporal
|
|
||||||
})
|
})
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_form(self):
|
def get_form(self):
|
||||||
form = super().get_form()
|
form = super().get_form()
|
||||||
# import pdb; pdb.set_trace()
|
|
||||||
form.fields["lots"].queryset = Lot.objects.filter(owner=self.request.user)
|
form.fields["lots"].queryset = Lot.objects.filter(owner=self.request.user)
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
@ -119,28 +114,114 @@ class DelToLotView(AddToLotView):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
class LotsTemporalView(DashboardView, TemplateView):
|
class LotsTagsView(DashboardView, TemplateView):
|
||||||
template_name = "lots.html"
|
template_name = "lots.html"
|
||||||
title = _("Temporal lots")
|
title = _("lots")
|
||||||
breadcrumb = "lot / temporal lots"
|
breadcrumb = _("lots") + " /"
|
||||||
success_url = reverse_lazy('dashboard:unassigned_devices')
|
success_url = reverse_lazy('dashboard:unassigned_devices')
|
||||||
lot_type = Lot.Types.TEMPORAL
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
self.pk = kwargs.get('pk')
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
lots = Lot.objects.filter(owner=self.request.user)
|
tag = get_object_or_404(LotTag, owner=self.request.user, id=self.pk)
|
||||||
|
self.title += " {}".format(tag.name)
|
||||||
|
self.breadcrumb += " {}".format(tag.name)
|
||||||
|
lots = Lot.objects.filter(owner=self.request.user).filter(type=tag)
|
||||||
context.update({
|
context.update({
|
||||||
'lots': lots.filter(type=self.lot_type),
|
'lots': lots,
|
||||||
|
'title': self.title,
|
||||||
|
'breadcrumb': self.breadcrumb
|
||||||
})
|
})
|
||||||
return context
|
return context
|
||||||
|
|
||||||
class LotsOutgoingView(LotsTemporalView):
|
|
||||||
title = _("Outgoing lots")
|
class LotAddDocumentView(DashboardView, CreateView):
|
||||||
breadcrumb = "lot / outging lots"
|
template_name = "new_annotation.html"
|
||||||
lot_type = Lot.Types.OUTGOING
|
title = _("New Document")
|
||||||
|
breadcrumb = "Device / New document"
|
||||||
|
success_url = reverse_lazy('dashboard:unassigned_devices')
|
||||||
|
model = LotAnnotation
|
||||||
|
fields = ("key", "value")
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
form.instance.owner = self.request.user
|
||||||
|
form.instance.lot = self.lot
|
||||||
|
form.instance.type = LotAnnotation.Type.DOCUMENT
|
||||||
|
response = super().form_valid(form)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
pk = self.kwargs.get('pk')
|
||||||
|
self.lot = get_object_or_404(Lot, pk=pk, owner=self.request.user)
|
||||||
|
self.success_url = reverse_lazy('lot:documents', args=[pk])
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
class LotsIncomingView(LotsTemporalView):
|
class LotDocumentsView(DashboardView, TemplateView):
|
||||||
title = _("Incoming lots")
|
template_name = "documents.html"
|
||||||
breadcrumb = "lot / Incoming lots"
|
title = _("New Document")
|
||||||
lot_type = Lot.Types.INCOMING
|
breadcrumb = "Device / New document"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
self.pk = kwargs.get('pk')
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
lot = get_object_or_404(Lot, owner=self.request.user, id=self.pk)
|
||||||
|
documents = LotAnnotation.objects.filter(
|
||||||
|
lot=lot,
|
||||||
|
owner=self.request.user,
|
||||||
|
type=LotAnnotation.Type.DOCUMENT,
|
||||||
|
)
|
||||||
|
context.update({
|
||||||
|
'lot': lot,
|
||||||
|
'documents': documents,
|
||||||
|
'title': self.title,
|
||||||
|
'breadcrumb': self.breadcrumb
|
||||||
|
})
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class LotAnnotationsView(DashboardView, TemplateView):
|
||||||
|
template_name = "annotations.html"
|
||||||
|
title = _("New Annotation")
|
||||||
|
breadcrumb = "Device / New annotation"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
self.pk = kwargs.get('pk')
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
lot = get_object_or_404(Lot, owner=self.request.user, id=self.pk)
|
||||||
|
annotations = LotAnnotation.objects.filter(
|
||||||
|
lot=lot,
|
||||||
|
owner=self.request.user,
|
||||||
|
type=LotAnnotation.Type.USER,
|
||||||
|
)
|
||||||
|
context.update({
|
||||||
|
'lot': lot,
|
||||||
|
'annotations': annotations,
|
||||||
|
'title': self.title,
|
||||||
|
'breadcrumb': self.breadcrumb
|
||||||
|
})
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class LotAddAnnotationView(DashboardView, CreateView):
|
||||||
|
template_name = "new_annotation.html"
|
||||||
|
title = _("New Annotation")
|
||||||
|
breadcrumb = "Device / New annotation"
|
||||||
|
success_url = reverse_lazy('dashboard:unassigned_devices')
|
||||||
|
model = LotAnnotation
|
||||||
|
fields = ("key", "value")
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
form.instance.owner = self.request.user
|
||||||
|
form.instance.lot = self.lot
|
||||||
|
form.instance.type = LotAnnotation.Type.USER
|
||||||
|
response = super().form_valid(form)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
pk = self.kwargs.get('pk')
|
||||||
|
self.lot = get_object_or_404(Lot, pk=pk, owner=self.request.user)
|
||||||
|
self.success_url = reverse_lazy('lot:annotations', args=[pk])
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
return kwargs
|
||||||
|
|
|
@ -3,5 +3,9 @@ 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
|
||||||
py-dmidecode==0.1.3
|
|
||||||
python-decouple==3.3
|
python-decouple==3.3
|
||||||
|
py-dmidecode==0.1.3
|
||||||
|
pandas==2.2.2
|
||||||
|
xlrd==2.0.1
|
||||||
|
odfpy==1.4.1
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
rm db/*
|
||||||
|
python3 manage.py migrate
|
||||||
|
python3 manage.py add_user user@example.org 1234
|
||||||
|
python3 manage.py up_snapshots example/snapshots/ user@example.org
|
|
@ -1,75 +0,0 @@
|
||||||
# Generated by Django 5.0.6 on 2024-06-11 09:20
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("device", "0001_initial"),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="Snapshot",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.BigAutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("created", models.DateTimeField(auto_now_add=True)),
|
|
||||||
(
|
|
||||||
"software",
|
|
||||||
models.CharField(
|
|
||||||
choices=[("Workbench", "Workbench")],
|
|
||||||
default="Workbench",
|
|
||||||
max_length=32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("uuid", models.UUIDField()),
|
|
||||||
("version", models.CharField(max_length=32)),
|
|
||||||
("sid", models.CharField(max_length=32)),
|
|
||||||
("settings_version", models.CharField(max_length=32)),
|
|
||||||
("is_server_erase", models.BooleanField(default=False)),
|
|
||||||
(
|
|
||||||
"severity",
|
|
||||||
models.SmallIntegerField(
|
|
||||||
choices=[
|
|
||||||
(0, "Info"),
|
|
||||||
(1, "Notice"),
|
|
||||||
(2, "Warning"),
|
|
||||||
(3, "Error"),
|
|
||||||
],
|
|
||||||
default=0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("start_time", models.DateTimeField()),
|
|
||||||
("end_time", models.DateTimeField()),
|
|
||||||
("components", models.ManyToManyField(to="device.component")),
|
|
||||||
(
|
|
||||||
"computer",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to="device.computer",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"owner",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to=settings.AUTH_USER_MODEL,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,18 +0,0 @@
|
||||||
# Generated by Django 5.0.6 on 2024-07-11 14:16
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("snapshot", "0001_initial"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="snapshot",
|
|
||||||
name="uuid",
|
|
||||||
field=models.UUIDField(unique=True),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,17 +0,0 @@
|
||||||
# Generated by Django 5.0.6 on 2024-07-11 14:18
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("snapshot", "0002_alter_snapshot_uuid"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="snapshot",
|
|
||||||
name="start_time",
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,31 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
from utils.constants import STR_SM_SIZE
|
|
||||||
from user.models import User
|
|
||||||
from device.models import Computer, Component
|
|
||||||
|
|
||||||
# Create your models here.
|
|
||||||
|
|
||||||
|
|
||||||
class Snapshot(models.Model):
|
|
||||||
class SoftWare(models.TextChoices):
|
|
||||||
WORKBENCH= "Workbench"
|
|
||||||
|
|
||||||
class Severity(models.IntegerChoices):
|
|
||||||
Info = 0, "Info"
|
|
||||||
Notice = 1, "Notice"
|
|
||||||
Warning = 2, "Warning"
|
|
||||||
Error = 3, "Error"
|
|
||||||
|
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
|
||||||
software = models.CharField(max_length=STR_SM_SIZE, choices=SoftWare, default=SoftWare.WORKBENCH)
|
|
||||||
uuid = models.UUIDField(unique=True)
|
|
||||||
version = models.CharField(max_length=STR_SM_SIZE)
|
|
||||||
sid = models.CharField(max_length=STR_SM_SIZE)
|
|
||||||
settings_version = models.CharField(max_length=STR_SM_SIZE)
|
|
||||||
is_server_erase = models.BooleanField(default=False)
|
|
||||||
severity = models.SmallIntegerField(choices=Severity, default=Severity.Info)
|
|
||||||
end_time = models.DateTimeField()
|
|
||||||
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
||||||
computer = models.ForeignKey(Computer, on_delete=models.CASCADE)
|
|
||||||
components = models.ManyToManyField(Component)
|
|
||||||
|
|
|
@ -1,673 +0,0 @@
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import shutil
|
|
||||||
import hashlib
|
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from django.conf import settings
|
|
||||||
from device.models import Device, Computer
|
|
||||||
from snapshot.models import Snapshot
|
|
||||||
|
|
||||||
|
|
||||||
HID = [
|
|
||||||
"manufacturer",
|
|
||||||
"model",
|
|
||||||
"chassis",
|
|
||||||
"serialNumber",
|
|
||||||
"sku"
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class Build:
|
|
||||||
def __init__(self, snapshot_json, user):
|
|
||||||
self.json = snapshot_json
|
|
||||||
self.user = user
|
|
||||||
self.hid = None
|
|
||||||
self.result = False
|
|
||||||
|
|
||||||
self.save_disk()
|
|
||||||
self.json_device = self.json["device"]
|
|
||||||
self.json_components = self.json["components"]
|
|
||||||
self.uuid = self.json["uuid"]
|
|
||||||
self.get_hid()
|
|
||||||
self.gen_computer()
|
|
||||||
self.gen_components()
|
|
||||||
self.gen_actions()
|
|
||||||
self.gen_snapshot()
|
|
||||||
|
|
||||||
self.result = True
|
|
||||||
self.move_json()
|
|
||||||
|
|
||||||
def save_disk(self):
|
|
||||||
snapshot_path = settings.SNAPSHOT_PATH
|
|
||||||
user = self.user.email
|
|
||||||
uuid = self.json.get("uuid", "")
|
|
||||||
now = datetime.now().strftime("%Y-%m-%d-%H-%M")
|
|
||||||
filename = f"{now}_{user}_{uuid}.json"
|
|
||||||
path_dir_base = os.path.join(snapshot_path, user)
|
|
||||||
path_upload = os.path.join(path_dir_base, 'upload')
|
|
||||||
path_name = os.path.join(path_upload, filename)
|
|
||||||
self.filename = path_name
|
|
||||||
self.path_dir_base = path_dir_base
|
|
||||||
|
|
||||||
if not os.path.isdir(path_dir_base):
|
|
||||||
os.system(f'mkdir -p {path_upload}')
|
|
||||||
|
|
||||||
with open(path_name, 'w') as file:
|
|
||||||
file.write(json.dumps(self.json))
|
|
||||||
|
|
||||||
def move_json(self):
|
|
||||||
if not self.result:
|
|
||||||
return
|
|
||||||
|
|
||||||
shutil.copy(self.filename, self.path_dir_base)
|
|
||||||
os.remove(self.filename)
|
|
||||||
|
|
||||||
def get_hid(self):
|
|
||||||
hid = ""
|
|
||||||
for f in HID:
|
|
||||||
hid += "-" + self.json_device[f]
|
|
||||||
self.hid = hid
|
|
||||||
|
|
||||||
def gen_computer(self):
|
|
||||||
self.device = Device.objects.filter(
|
|
||||||
hid=self.hid,
|
|
||||||
active=True,
|
|
||||||
reliable=True,
|
|
||||||
owner=self.user
|
|
||||||
).first()
|
|
||||||
|
|
||||||
if self.device:
|
|
||||||
return
|
|
||||||
|
|
||||||
chid = hashlib.sha3_256(self.hid.encode()).hexdigest()
|
|
||||||
self.device = Device.objects.create(
|
|
||||||
serial_number=self.json_device["serialNumber"],
|
|
||||||
manufacturer=self.json_device["manufacturer"],
|
|
||||||
version=self.json_device["version"],
|
|
||||||
model=self.json_device["model"],
|
|
||||||
type=self.json_device["type"],
|
|
||||||
hid=self.hid,
|
|
||||||
chid=chid,
|
|
||||||
owner=self.user
|
|
||||||
)
|
|
||||||
|
|
||||||
Computer.objects.create(
|
|
||||||
device=self.device,
|
|
||||||
sku=self.json_device["sku"],
|
|
||||||
chassis=self.json_device["chassis"]
|
|
||||||
)
|
|
||||||
|
|
||||||
def gen_snapshot(self):
|
|
||||||
self.snapshot = Snapshot.objects.create(
|
|
||||||
uuid = self.uuid,
|
|
||||||
version = self.json["version"],
|
|
||||||
computer = self.device.computer,
|
|
||||||
sid = self.json.get("sid", ""),
|
|
||||||
settings_version = self.json.get("settings_version", ""),
|
|
||||||
end_time = self.json.get("endTime"),
|
|
||||||
owner=self.user
|
|
||||||
)
|
|
||||||
|
|
||||||
def gen_components(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def gen_actions(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# class ParseSnapshot:
|
|
||||||
# def __init__(self, snapshot, default="n/a"):
|
|
||||||
# self.default = default
|
|
||||||
# self.dmidecode_raw = snapshot["hwmd"]["dmidecode"]
|
|
||||||
# self.smart_raw = snapshot["hwmd"]["smart"]
|
|
||||||
# self.hwinfo_raw = snapshot["hwmd"]["hwinfo"]
|
|
||||||
# self.lshw_raw = snapshot["hwmd"]["lshw"]
|
|
||||||
# self.lscpi_raw = snapshot["hwmd"]["lspci"]
|
|
||||||
# self.sanitize_raw = snapshot.get("sanitize", [])
|
|
||||||
# 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": "Workbench",
|
|
||||||
# # "software": snapshot["software"],
|
|
||||||
# "components": self.components,
|
|
||||||
# "uuid": snapshot['uuid'],
|
|
||||||
# "version": "15.0.0",
|
|
||||||
# # "version": snapshot['version'],
|
|
||||||
# "settings_version": snapshot['settings_version'],
|
|
||||||
# "endTime": snapshot["timestamp"],
|
|
||||||
# "elapsed": 1,
|
|
||||||
# "sid": snapshot["sid"],
|
|
||||||
# }
|
|
||||||
|
|
||||||
# def get_snapshot(self):
|
|
||||||
# return Snapshot().load(self.snapshot_json)
|
|
||||||
|
|
||||||
# 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,
|
|
||||||
# "generation": None,
|
|
||||||
# "brand": cpu.get('Family'),
|
|
||||||
# "address": self.get_cpu_address(cpu),
|
|
||||||
# }
|
|
||||||
# )
|
|
||||||
|
|
||||||
# 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):
|
|
||||||
# nodes = get_nested_dicts_with_key_value(self.lshw, 'class', 'display')
|
|
||||||
# for c in nodes:
|
|
||||||
# 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, disk):
|
|
||||||
# disk_sanitize = None
|
|
||||||
# for d in self.sanitize_raw:
|
|
||||||
# s = d.get('device_info', {}).get('export_data', {})
|
|
||||||
# s = s.get('block', {}).get('serial')
|
|
||||||
# if s == disk.get('serial_number'):
|
|
||||||
# disk_sanitize = d
|
|
||||||
# break
|
|
||||||
# if not disk_sanitize:
|
|
||||||
# return []
|
|
||||||
|
|
||||||
# steps = []
|
|
||||||
# step_type = 'EraseBasic'
|
|
||||||
# if d.get("method", {}).get('name') == 'Baseline Cryptographic':
|
|
||||||
# step_type = 'EraseCrypto'
|
|
||||||
|
|
||||||
# if disk.get('type') == 'EraseCrypto':
|
|
||||||
# step_type = 'EraseCrypto'
|
|
||||||
|
|
||||||
# erase = {
|
|
||||||
# 'type': step_type,
|
|
||||||
# 'severity': "Info",
|
|
||||||
# 'steps': steps,
|
|
||||||
# 'startTime': None,
|
|
||||||
# 'endTime': None,
|
|
||||||
# }
|
|
||||||
# severities = []
|
|
||||||
|
|
||||||
# for step in disk_sanitize.get('steps', []):
|
|
||||||
# severity = "Info"
|
|
||||||
# if not step['success']:
|
|
||||||
# severity = "Error"
|
|
||||||
|
|
||||||
# steps.append(
|
|
||||||
# {
|
|
||||||
# 'severity': severity,
|
|
||||||
# 'startTime': unix_isoformat(step['start_time']),
|
|
||||||
# 'endTime': unix_isoformat(step['end_time']),
|
|
||||||
# 'type': 'StepRandom',
|
|
||||||
# }
|
|
||||||
# )
|
|
||||||
# severities.append(severity)
|
|
||||||
|
|
||||||
# erase['endTime'] = unix_isoformat(step['end_time'])
|
|
||||||
# if not erase['startTime']:
|
|
||||||
# erase['startTime'] = unix_isoformat(step['start_time'])
|
|
||||||
|
|
||||||
# if "Error" in severities:
|
|
||||||
# erase['severity'] = "Error"
|
|
||||||
|
|
||||||
# return [erase]
|
|
||||||
|
|
||||||
# def get_networks(self):
|
|
||||||
# nodes = get_nested_dicts_with_key_value(self.lshw, 'class', 'network')
|
|
||||||
# for c in nodes:
|
|
||||||
# capacity = c.get('capacity')
|
|
||||||
# units = c.get('units')
|
|
||||||
# speed = None
|
|
||||||
# if capacity and units:
|
|
||||||
# speed = unit.Quantity(capacity, units).to('Mbit/s').m
|
|
||||||
# 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": speed,
|
|
||||||
# "variant": c.get('version', 1),
|
|
||||||
# "wireless": wireless,
|
|
||||||
# }
|
|
||||||
# )
|
|
||||||
|
|
||||||
# def get_sound_card(self):
|
|
||||||
# nodes = get_nested_dicts_with_key_value(self.lshw, 'class', 'multimedia')
|
|
||||||
# for c in nodes:
|
|
||||||
# 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 numpy.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):
|
|
||||||
# try:
|
|
||||||
# memory = ram.get("Size", "0")
|
|
||||||
# memory = memory.split(' ')
|
|
||||||
# if len(memory) > 1:
|
|
||||||
# size = int(memory[0])
|
|
||||||
# units = memory[1]
|
|
||||||
# return base2.Quantity(size, units).to('MiB').m
|
|
||||||
# return int(size.split(" ")[0])
|
|
||||||
# except Exception as err:
|
|
||||||
# logger.error("get_ram_size error: {}".format(err))
|
|
||||||
# return 0
|
|
||||||
|
|
||||||
# def get_ram_speed(self, ram):
|
|
||||||
# size = ram.get("Speed", "0")
|
|
||||||
# return int(size.split(" ")[0])
|
|
||||||
|
|
||||||
# def get_cpu_speed(self, cpu):
|
|
||||||
# speed = cpu.get('Max Speed', "0")
|
|
||||||
# return float(speed.split(" ")[0]) / 1024
|
|
||||||
|
|
||||||
# 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_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'},
|
|
||||||
# }
|
|
||||||
|
|
||||||
# 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')
|
|
||||||
# try:
|
|
||||||
# DataStorageInterface(interface.upper())
|
|
||||||
# except ValueError as err:
|
|
||||||
# txt = "Sid: {}, interface {} is not in DataStorageInterface Enum".format(
|
|
||||||
# self.sid, interface
|
|
||||||
# )
|
|
||||||
# self.errors("{}".format(err))
|
|
||||||
# self.errors(txt, severity=Severity.Warning)
|
|
||||||
# return "ATA"
|
|
||||||
|
|
||||||
# def get_data_storage_size(self, x):
|
|
||||||
# total_capacity = x.get('user_capacity', {}).get('bytes')
|
|
||||||
# if not total_capacity:
|
|
||||||
# return 1
|
|
||||||
# # convert bytes to Mb
|
|
||||||
# return total_capacity / 1024**2
|
|
||||||
|
|
||||||
# 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):
|
|
||||||
# return json.loads(x)
|
|
||||||
# return x
|
|
||||||
|
|
||||||
# def errors(self, txt=None, severity=Severity.Error):
|
|
||||||
# if not txt:
|
|
||||||
# return self._errors
|
|
||||||
|
|
||||||
# logger.error(txt)
|
|
||||||
# self._errors.append(txt)
|
|
||||||
# error = SnapshotsLog(
|
|
||||||
# description=txt,
|
|
||||||
# snapshot_uuid=self.uuid,
|
|
||||||
# severity=severity,
|
|
||||||
# sid=self.sid,
|
|
||||||
# version=self.version,
|
|
||||||
# )
|
|
||||||
# error.save()
|
|
||||||
|
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<h3>{{ subtitle }}</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="dataTable-container">
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col" data-sortable="">
|
|
||||||
<a class="dataTable-sorter" href="#">Title</a>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
{% for snap in snapshots %}
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<input type="checkbox" name="snapshots" value="{{ snap.id }}" />
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="{# url 'snapshot:details' snap.pk #}">{{ snap.uuid }} {{ snap.sid }} {{ snap.version }}</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="{% url 'device:details' snap.computer.device.pk %}">{{ snap.computer.device.manufacturer }} {{ snap.computer.device.model }}</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
|
@ -1,18 +0,0 @@
|
||||||
# from django.urls import path, include
|
|
||||||
# from rest_framework.routers import DefaultRouter
|
|
||||||
# from snapshot.views import SnapshotViewSet
|
|
||||||
|
|
||||||
# router = DefaultRouter()
|
|
||||||
# router.register(r'snapshots', SnapshotViewSet)
|
|
||||||
|
|
||||||
# urlpatterns = [
|
|
||||||
# path('', include(router.urls)),
|
|
||||||
# ]
|
|
||||||
from django.urls import path
|
|
||||||
from snapshot import views
|
|
||||||
|
|
||||||
app_name = 'snapshot'
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path("", views.ListSnapshotsView.as_view(), name="list"),
|
|
||||||
]
|
|
|
@ -1,27 +0,0 @@
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from django.views.generic.base import TemplateView
|
|
||||||
from dashboard.mixins import DashboardView
|
|
||||||
from snapshot.models import Snapshot
|
|
||||||
# from django.shortcuts import render
|
|
||||||
# from rest_framework import viewsets
|
|
||||||
# from snapshot.serializers import SnapshotSerializer
|
|
||||||
|
|
||||||
|
|
||||||
# class SnapshotViewSet(viewsets.ModelViewSet):
|
|
||||||
# queryset = Snapshot.objects.all()
|
|
||||||
# serializer_class = SnapshotSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class ListSnapshotsView(DashboardView, TemplateView):
|
|
||||||
template_name = "snapshots.html"
|
|
||||||
section = "snapshots"
|
|
||||||
title = _("Snapshots")
|
|
||||||
breadcrumb = "Snapshots"
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super().get_context_data(**kwargs)
|
|
||||||
snapshots = Snapshot.objects.filter(owner=self.request.user)
|
|
||||||
context.update({
|
|
||||||
'snapshots': snapshots,
|
|
||||||
})
|
|
||||||
return context
|
|
|
@ -1,5 +1,6 @@
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
@ -16,8 +17,21 @@ class Command(BaseCommand):
|
||||||
email = kwargs['email']
|
email = kwargs['email']
|
||||||
password = kwargs['password']
|
password = kwargs['password']
|
||||||
self.create_user(email, password)
|
self.create_user(email, password)
|
||||||
|
self.create_lot_tags()
|
||||||
|
|
||||||
def create_user(self, email, password):
|
def create_user(self, email, password):
|
||||||
u = User.objects.create(email=email, password=password)
|
self.u = User.objects.create(email=email, password=password)
|
||||||
u.set_password(password)
|
self.u.set_password(password)
|
||||||
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,4 +1,4 @@
|
||||||
# Generated by Django 5.0.6 on 2024-06-11 09:19
|
# Generated by Django 5.0.6 on 2024-07-17 14:57
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
|
@ -5,3 +5,18 @@ STR_XSM_SIZE = 16
|
||||||
STR_SM_SIZE = 32
|
STR_SM_SIZE = 32
|
||||||
STR_SIZE = 64
|
STR_SIZE = 64
|
||||||
STR_BIG_SIZE = 128
|
STR_BIG_SIZE = 128
|
||||||
|
STR_EXTEND_SIZE = 256
|
||||||
|
|
||||||
|
|
||||||
|
# Algorithms for build hids
|
||||||
|
HID_ALGO1 = [
|
||||||
|
"manufacturer",
|
||||||
|
"model",
|
||||||
|
"chassis",
|
||||||
|
"serialNumber",
|
||||||
|
"sku"
|
||||||
|
]
|
||||||
|
|
||||||
|
ALGOS = {
|
||||||
|
"hidalgo1": HID_ALGO1,
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
import json
|
||||||
|
import uuid
|
||||||
|
import hashlib
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from evidence.xapian import index
|
||||||
|
from evidence.models import Annotation
|
||||||
|
from device.models import Device
|
||||||
|
|
||||||
|
def create_doc(data):
|
||||||
|
if not data:
|
||||||
|
return
|
||||||
|
|
||||||
|
doc = {}
|
||||||
|
device = {"manufacturer": "", "model": "", "amount": 1}
|
||||||
|
kv = {}
|
||||||
|
_uuid = str(uuid.uuid4())
|
||||||
|
customer_id = hashlib.sha3_256(_uuid.encode()).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
for k, v in data.items():
|
||||||
|
if not v:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if k.upper() == "CUSTOMER_ID":
|
||||||
|
customer_id = v
|
||||||
|
continue
|
||||||
|
|
||||||
|
if k.lower() == "type":
|
||||||
|
if v not in Device.Types.values:
|
||||||
|
raise ValidationError("{} is not a valid device".format(v))
|
||||||
|
|
||||||
|
device["type"] = v
|
||||||
|
|
||||||
|
elif k.lower() == "amount":
|
||||||
|
try:
|
||||||
|
amount = int(v)
|
||||||
|
device["amount"] = amount
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
else:
|
||||||
|
kv[k] = v
|
||||||
|
|
||||||
|
if not device:
|
||||||
|
return
|
||||||
|
|
||||||
|
doc["device"] = device
|
||||||
|
|
||||||
|
if kv:
|
||||||
|
doc["kv"] = kv
|
||||||
|
|
||||||
|
date = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
|
||||||
|
|
||||||
|
if doc:
|
||||||
|
doc["uuid"] = _uuid
|
||||||
|
doc["endTime"] = date
|
||||||
|
doc["software"] = "DeviceHub"
|
||||||
|
doc["CUSTOMER_ID"] = customer_id
|
||||||
|
doc["type"] = "WebSnapshot"
|
||||||
|
|
||||||
|
return doc
|
||||||
|
|
||||||
|
|
||||||
|
def create_annotation(doc, user, commit=False):
|
||||||
|
if not doc or not doc.get('uuid') or not doc.get("CUSTOMER_ID"):
|
||||||
|
return []
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'uuid': doc['uuid'],
|
||||||
|
'owner': user,
|
||||||
|
'type': Annotation.Type.SYSTEM,
|
||||||
|
'key': 'CUSTOMER_ID',
|
||||||
|
'value': doc['CUSTOMER_ID'],
|
||||||
|
}
|
||||||
|
if commit:
|
||||||
|
return Annotation.objects.create(**data)
|
||||||
|
|
||||||
|
return Annotation(**data)
|
||||||
|
|
||||||
|
|
||||||
|
def create_index(doc):
|
||||||
|
if not doc or not doc.get('uuid'):
|
||||||
|
return []
|
||||||
|
|
||||||
|
_uuid = doc['uuid']
|
||||||
|
ev = json.dumps(doc)
|
||||||
|
index(_uuid, ev)
|
|
@ -0,0 +1,19 @@
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
|
||||||
|
class MultipleFileInput(forms.ClearableFileInput):
|
||||||
|
allow_multiple_selected = True
|
||||||
|
|
||||||
|
|
||||||
|
class MultipleFileField(forms.FileField):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
kwargs.setdefault("widget", MultipleFileInput())
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def clean(self, data, initial=None):
|
||||||
|
single_file_clean = super().clean
|
||||||
|
if isinstance(data, (list, tuple)):
|
||||||
|
result = [single_file_clean(d, initial) for d in data]
|
||||||
|
else:
|
||||||
|
result = [single_file_clean(data, initial)]
|
||||||
|
return result
|
Loading…
Reference in New Issue