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:
cayop 2024-09-17 10:11:26 +00:00
commit e8743cd793
71 changed files with 1927 additions and 2011 deletions

View File

@ -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)

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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"),
] ]

View File

@ -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]

6
db/.gitignore vendored Normal file
View File

@ -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

View File

@ -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)

View File

@ -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",
),
),
]

View File

@ -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),
),
]

View File

@ -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,
),
),
]

View File

@ -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),
),
]

View File

@ -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']

View File

@ -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>

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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"),
] ]

View File

@ -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

View File

@ -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

View File

@ -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")),
] ]

12
docker-compose.yml Normal file
View File

@ -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

View File

@ -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

View File

@ -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 "${@}"

View File

@ -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"

130
evidence/forms.py Normal file
View File

@ -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

View File

@ -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()

View File

@ -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"
),
),
]

View File

@ -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")]
),
),
]

View File

@ -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")]
),
),
]

78
evidence/models.py Normal file
View File

@ -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()

54
evidence/parse.py Normal file
View File

@ -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
)

View File

@ -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

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

22
evidence/urls.py Normal file
View File

@ -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"),
]

137
evidence/views.py Normal file
View File

@ -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

51
evidence/xapian.py Normal file
View File

@ -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)

BIN
example/placeholders1.ods Normal file

Binary file not shown.

View File

@ -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}

View File

@ -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

View File

@ -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"
),
),
] ]

View File

@ -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,
),
),
],
),
]

View File

@ -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)

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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>

View File

@ -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 %}

View File

@ -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"),
] ]

View File

@ -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

View File

@ -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

4
reset.sh Normal file
View File

@ -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

View File

@ -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,
),
),
],
),
]

View File

@ -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),
),
]

View File

@ -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",
),
]

View File

@ -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)

View File

@ -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()

View File

@ -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 %}

View File

@ -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"),
]

View File

@ -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

View File

@ -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
)

View File

@ -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

View File

@ -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,
}

89
utils/device.py Normal file
View File

@ -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)

19
utils/forms.py Normal file
View File

@ -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