Merge pull request 'xapian' (#1) from xapian into master
Reviewed-on: https://gitea.pangea.org/ereuse/devicehub-django/pulls/1
This commit is contained in:
commit
e8743cd793
|
@ -5,6 +5,8 @@ from django.core.exceptions import PermissionDenied
|
|||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.views.generic.base import TemplateView
|
||||
from device.models import Device
|
||||
from evidence.models import Annotation
|
||||
from lot.models import LotTag
|
||||
|
||||
|
||||
class Http403(PermissionDenied):
|
||||
|
@ -37,13 +39,17 @@ class DashboardView(LoginRequiredMixin):
|
|||
'section': self.section,
|
||||
'path': resolve(self.request.path).url_name,
|
||||
'user': self.request.user,
|
||||
'lot_tags': LotTag.objects.filter(owner=self.request.user)
|
||||
})
|
||||
return context
|
||||
|
||||
def get_session_devices(self):
|
||||
# import pdb; pdb.set_trace()
|
||||
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
|
||||
|
||||
|
||||
|
@ -51,7 +57,7 @@ class DetailsMixin(DashboardView, TemplateView):
|
|||
|
||||
def get(self, request, *args, **kwargs):
|
||||
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)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
@ -61,7 +67,7 @@ class DetailsMixin(DashboardView, TemplateView):
|
|||
})
|
||||
return context
|
||||
|
||||
|
||||
|
||||
class InventaryMixin(DashboardView, TemplateView):
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
@ -76,5 +82,3 @@ class InventaryMixin(DashboardView, TemplateView):
|
|||
except Exception:
|
||||
pass
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
</head>
|
||||
<body>
|
||||
<header class="navbar navbar-dark sticky-top admin bg-green flex-md-nowrap p-0 shadow">
|
||||
<a class="navbar-brand col-md-3 col-lg-2 me-0 px-3" href="#">DEVICE HUB</a>
|
||||
<a class="navbar-brand col-md-3 col-lg-2 me-0 px-3" href="#">DEVICEHUB</a>
|
||||
<div class="navbar-nav navbar-sub-brand">
|
||||
PANGEA
|
||||
</div>
|
||||
|
@ -90,11 +90,6 @@
|
|||
{% trans 'Unassigned devices' %}
|
||||
</a>
|
||||
</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>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
|
@ -103,37 +98,29 @@
|
|||
{% trans 'Lots' %}
|
||||
</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">
|
||||
{% for tag in lot_tags %}
|
||||
<li class="nav-items">
|
||||
<a class="nav-link{% if path == 'admin_people_list' %} active2{% endif %}" href="{% url 'lot:lots_incoming' %}">
|
||||
{% trans 'Incoming ' %}
|
||||
</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 class="nav-link{% if path == 'admin_people_list' %} active2{% endif %}" href="{% url 'lot:tag' tag.id %}">
|
||||
{{ tag.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
<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>
|
||||
{% trans 'Snapshots' %}
|
||||
{% trans 'evidences' %}
|
||||
</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">
|
||||
<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' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link{% if path == 'admin_people_list' %} active2{% endif %}" href="{% url 'snapshot:list' %}">
|
||||
{% trans 'Old snapshots' %}
|
||||
<a class="nav-link{% if path == 'admin_people_list' %} active2{% endif %}" href="{% url 'evidence:list' %}">
|
||||
{% trans 'Old evidences' %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -145,7 +132,7 @@
|
|||
</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">
|
||||
<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' %}
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
@ -151,12 +151,12 @@
|
|||
</a>
|
||||
</li><!-- End Dashboard Nav -->
|
||||
|
||||
<li class="nav-heading">Snapshots</li>
|
||||
<li class="nav-heading">evidences</li>
|
||||
|
||||
<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>
|
||||
<span>Uploaded Snapshots</span>
|
||||
<span>Uploaded evidences</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
|
|
@ -7,22 +7,22 @@
|
|||
<h3>{{ subtitle }}</h3>
|
||||
</div>
|
||||
<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>
|
||||
{% trans 'Lots' %}
|
||||
</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' %}
|
||||
{% trans 'Documents' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{# url 'idhub:admin_people_activate' object.id #}" type="button" class="btn btn-green-admin">
|
||||
<i class="bi bi-reply"></i>
|
||||
{% trans 'Exports' %}
|
||||
</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>
|
||||
{% trans 'Labels' %}
|
||||
{% trans 'Annotations' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -44,7 +44,9 @@
|
|||
<input type="checkbox" name="devices" value="{{ dev.id }}" />
|
||||
</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>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
|
@ -5,6 +5,5 @@ app_name = 'dashboard'
|
|||
|
||||
urlpatterns = [
|
||||
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"),
|
||||
]
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from django.db.models import Count
|
||||
from dashboard.mixins import InventaryMixin, DetailsMixin
|
||||
from device.models import Device
|
||||
from lot.models import Lot
|
||||
|
@ -13,41 +12,32 @@ class UnassignedDevicesView(InventaryMixin):
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
devices = Device.objects.filter(
|
||||
owner=self.request.user
|
||||
).annotate(num_lots=Count('lot')).filter(num_lots=0)
|
||||
devices = Device.get_unassigned(self.request.user)
|
||||
|
||||
context.update({
|
||||
'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
|
||||
|
||||
|
||||
class LotDashboardView(InventaryMixin, DetailsMixin):
|
||||
template_name = "unassigned_devices.html"
|
||||
section = "Unassigned"
|
||||
section = "dashboard_lot"
|
||||
title = _("Lot Devices")
|
||||
breadcrumb = "Devices / Lot Devices"
|
||||
breadcrumb = "Lot / Devices"
|
||||
model = Lot
|
||||
|
||||
def get_context_data(self, **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({
|
||||
'devices': devices,
|
||||
'lot': lot,
|
||||
})
|
||||
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
6
db/.gitignore
vendored
Normal 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
|
|
@ -1,3 +1,65 @@
|
|||
from django import forms
|
||||
from utils.device import create_annotation, create_doc, create_index
|
||||
|
||||
|
||||
DEVICE_TYPES = [
|
||||
("Desktop", "Desktop"),
|
||||
("Laptop", "Laptop"),
|
||||
("Server", "Server"),
|
||||
("GraphicCard", "GraphicCard"),
|
||||
("HardDrive", "HardDrive"),
|
||||
("SolidStateDrive", "SolidStateDrive"),
|
||||
("Motherboard", "Motherboard"),
|
||||
("NetworkAdapter", "NetworkAdapter"),
|
||||
("Processor", "Processor"),
|
||||
("RamModule", "RamModule"),
|
||||
("SoundCard", "SoundCard"),
|
||||
("Display", "Display"),
|
||||
("Battery", "Battery"),
|
||||
("Camera", "Camera"),
|
||||
]
|
||||
|
||||
|
||||
class DeviceForm(forms.Form):
|
||||
type = forms.ChoiceField(choices = DEVICE_TYPES, required=False)
|
||||
amount = forms.IntegerField(required=False, initial=1)
|
||||
customer_id = forms.CharField(required=False)
|
||||
name = forms.CharField(required=False)
|
||||
value = forms.CharField(required=False)
|
||||
|
||||
|
||||
class BaseDeviceFormSet(forms.BaseFormSet):
|
||||
def clean(self):
|
||||
for x in self.cleaned_data:
|
||||
if x.get("amount"):
|
||||
return True
|
||||
return False
|
||||
|
||||
def save(self, user, commit=True):
|
||||
self.user = user
|
||||
row = {}
|
||||
for f in self.forms:
|
||||
d = f.cleaned_data
|
||||
if not d:
|
||||
continue
|
||||
|
||||
if d.get("type"):
|
||||
row["type"] = d["type"]
|
||||
if d.get("amount"):
|
||||
row["amount"] = d["amount"]
|
||||
if d.get("name"):
|
||||
row[d["name"]] = d.get("value", '')
|
||||
if d.get("customer_id"):
|
||||
row['CUSTOMER_ID']= d["customer_id"]
|
||||
|
||||
doc = create_doc(row)
|
||||
if not commit:
|
||||
return doc
|
||||
|
||||
create_index(doc)
|
||||
create_annotation(doc, user, commit=commit)
|
||||
return doc
|
||||
|
||||
|
||||
DeviceFormSet = forms.formset_factory(form=DeviceForm, formset=BaseDeviceFormSet, extra=1)
|
||||
|
||||
|
|
|
@ -1,404 +0,0 @@
|
|||
# Generated by Django 5.0.6 on 2024-06-11 09:20
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Device",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("created", models.DateTimeField(auto_now_add=True)),
|
||||
("updated", models.DateTimeField(auto_now=True)),
|
||||
("type", models.CharField(max_length=32)),
|
||||
("model", models.CharField(blank=True, max_length=64, null=True)),
|
||||
(
|
||||
"manufacturer",
|
||||
models.CharField(blank=True, max_length=64, null=True),
|
||||
),
|
||||
(
|
||||
"serial_number",
|
||||
models.CharField(blank=True, max_length=64, null=True),
|
||||
),
|
||||
("part_number", models.CharField(blank=True, max_length=64, null=True)),
|
||||
("brand", models.TextField(blank=True, null=True)),
|
||||
("generation", models.SmallIntegerField(blank=True, null=True)),
|
||||
("version", models.TextField(blank=True, null=True)),
|
||||
("production_date", models.DateTimeField(blank=True, null=True)),
|
||||
("variant", models.TextField(blank=True, null=True)),
|
||||
("devicehub_id", models.TextField(blank=True, null=True, unique=True)),
|
||||
("dhid_bk", models.CharField(blank=True, max_length=64, null=True)),
|
||||
("phid_bk", models.CharField(blank=True, max_length=64, null=True)),
|
||||
("family", models.CharField(blank=True, max_length=64, null=True)),
|
||||
("hid", models.CharField(blank=True, max_length=64, null=True)),
|
||||
("chid", models.CharField(blank=True, max_length=64, null=True)),
|
||||
("active", models.BooleanField(default=True)),
|
||||
(
|
||||
"owner",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Component",
|
||||
fields=[
|
||||
(
|
||||
"device",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="device.device",
|
||||
),
|
||||
),
|
||||
(
|
||||
"type",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("GraphicCard", "Graphiccard"),
|
||||
("DataStorage", "Datastorage"),
|
||||
("Motherboard", "Motherboard"),
|
||||
("NetworkAdapter", "Networkadapter"),
|
||||
("Processor", "Processor"),
|
||||
("RamModule", "Rammodule"),
|
||||
("SoundCard", "Soundcard"),
|
||||
("Display", "Display"),
|
||||
("Battery", "Battery"),
|
||||
("Camera", "Camera"),
|
||||
],
|
||||
max_length=32,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Computer",
|
||||
fields=[
|
||||
(
|
||||
"device",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="device.device",
|
||||
),
|
||||
),
|
||||
("chassis", models.TextField(blank=True, null=True)),
|
||||
("system_uuid", models.UUIDField()),
|
||||
("sku", models.TextField(blank=True, null=True)),
|
||||
(
|
||||
"type",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("Desktop", "Desktop"),
|
||||
("Laptop", "Laptop"),
|
||||
("Server", "Server"),
|
||||
],
|
||||
default="Laptop",
|
||||
max_length=32,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="PhysicalProperties",
|
||||
fields=[
|
||||
(
|
||||
"device",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="device.device",
|
||||
),
|
||||
),
|
||||
("weight", models.FloatField(blank=True, null=True)),
|
||||
("width", models.FloatField(blank=True, null=True)),
|
||||
("height", models.FloatField(blank=True, null=True)),
|
||||
("depth", models.FloatField(blank=True, null=True)),
|
||||
("color", models.CharField(blank=True, max_length=20, null=True)),
|
||||
("image", models.CharField(blank=True, max_length=64, null=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="SoundCard",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"component",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="device.component",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="RamModule",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("size", models.IntegerField(blank=True, null=True)),
|
||||
("speed", models.SmallIntegerField(blank=True, null=True)),
|
||||
(
|
||||
"interface",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("SDRAM", "Sdram"),
|
||||
("DDR SDRAM", "Ddr"),
|
||||
("DDR2 SDRAM", "Ddr2"),
|
||||
("DDR3 SDRAM", "Ddr3"),
|
||||
("DDR4 SDRAM", "Ddr4"),
|
||||
("DDR5 SDRAM", "Ddr5"),
|
||||
("DDR6 SDRAM", "Ddr6"),
|
||||
("LPDDR3", "Lpddr3"),
|
||||
],
|
||||
max_length=32,
|
||||
),
|
||||
),
|
||||
(
|
||||
"format",
|
||||
models.CharField(
|
||||
choices=[("DIMM", "Dimm"), ("SODIMM", "Sodimm")], max_length=32
|
||||
),
|
||||
),
|
||||
(
|
||||
"component",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="device.component",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Processor",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("speed", models.FloatField(blank=True, null=True)),
|
||||
("cores", models.SmallIntegerField(blank=True, null=True)),
|
||||
("threads", models.SmallIntegerField(blank=True, null=True)),
|
||||
("address", models.SmallIntegerField(blank=True, null=True)),
|
||||
(
|
||||
"component",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="device.component",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="NetworkAdapter",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("speed", models.IntegerField(blank=True, null=True)),
|
||||
("wireless", models.BooleanField(default=False)),
|
||||
(
|
||||
"component",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="device.component",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Motherboard",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("slots", models.SmallIntegerField(blank=True, null=True)),
|
||||
("usb", models.SmallIntegerField(blank=True, null=True)),
|
||||
("firewire", models.SmallIntegerField(blank=True, null=True)),
|
||||
("serial", models.SmallIntegerField(blank=True, null=True)),
|
||||
("pcmcia", models.SmallIntegerField(blank=True, null=True)),
|
||||
("bios_date", models.DateTimeField()),
|
||||
("ram_slots", models.SmallIntegerField(blank=True, null=True)),
|
||||
("ram_max_size", models.IntegerField(blank=True, null=True)),
|
||||
(
|
||||
"component",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="device.component",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="GraphicCard",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("memory", models.IntegerField(blank=True, null=True)),
|
||||
(
|
||||
"component",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="device.component",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Display",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"component",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="device.component",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="DataStorage",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("size", models.IntegerField(blank=True, null=True)),
|
||||
(
|
||||
"interface",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("ATA", "Ata"),
|
||||
("USB", "Usb"),
|
||||
("PCI", "Pci"),
|
||||
("NVME", "Nvme"),
|
||||
],
|
||||
max_length=32,
|
||||
),
|
||||
),
|
||||
(
|
||||
"type",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("HardDrive", "Harddrive"),
|
||||
("SolidStateDrive", "Solidstatedrive"),
|
||||
],
|
||||
max_length=32,
|
||||
),
|
||||
),
|
||||
(
|
||||
"component",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="device.component",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Battery",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"component",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="device.component",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="component",
|
||||
name="computer",
|
||||
field=models.OneToOneField(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="device.computer",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,33 +0,0 @@
|
|||
# Generated by Django 5.0.6 on 2024-07-03 11:07
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("device", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="device",
|
||||
name="brand",
|
||||
field=models.CharField(blank=True, max_length=64, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="device",
|
||||
name="devicehub_id",
|
||||
field=models.CharField(blank=True, max_length=64, null=True, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="device",
|
||||
name="variant",
|
||||
field=models.CharField(blank=True, max_length=64, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="device",
|
||||
name="version",
|
||||
field=models.CharField(blank=True, max_length=64, null=True),
|
||||
),
|
||||
]
|
|
@ -1,73 +0,0 @@
|
|||
# Generated by Django 5.0.6 on 2024-07-03 12:33
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("device", "0002_alter_device_brand_alter_device_devicehub_id_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="component",
|
||||
name="type",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="computer",
|
||||
name="type",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="datastorage",
|
||||
name="type",
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="computer",
|
||||
name="chassis",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("Tower", "Tower"),
|
||||
("All in one", "Allinone"),
|
||||
("Microtower", "Microtower"),
|
||||
("Netbook", "Netbook"),
|
||||
("Laptop", "Laptop"),
|
||||
("Tablet", "Tabler"),
|
||||
("Server", "Server"),
|
||||
("Non-physical device", "Virtual"),
|
||||
],
|
||||
max_length=32,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="computer",
|
||||
name="sku",
|
||||
field=models.CharField(blank=True, max_length=32, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="device",
|
||||
name="type",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("Desktop", "Desktop"),
|
||||
("Laptop", "Laptop"),
|
||||
("Server", "Server"),
|
||||
("GraphicCard", "Graphiccard"),
|
||||
("HardDrive", "Harddrive"),
|
||||
("SolidStateDrive", "Solidstatedrive"),
|
||||
("Motherboard", "Motherboard"),
|
||||
("NetworkAdapter", "Networkadapter"),
|
||||
("Processor", "Processor"),
|
||||
("RamModule", "Rammodule"),
|
||||
("SoundCard", "Soundcard"),
|
||||
("Display", "Display"),
|
||||
("Battery", "Battery"),
|
||||
("Camera", "Camera"),
|
||||
],
|
||||
default="Laptop",
|
||||
max_length=32,
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,46 +0,0 @@
|
|||
# Generated by Django 5.0.6 on 2024-07-11 13:58
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("device", "0003_remove_component_type_remove_computer_type_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="device",
|
||||
name="dhid_bk",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="device",
|
||||
name="phid_bk",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="computer",
|
||||
name="erasure_server",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="device",
|
||||
name="reliable",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="component",
|
||||
name="computer",
|
||||
field=models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="device.computer",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="computer",
|
||||
name="system_uuid",
|
||||
field=models.UUIDField(blank=True, null=True),
|
||||
),
|
||||
]
|
248
device/models.py
248
device/models.py
|
@ -1,9 +1,12 @@
|
|||
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 utils.constants import STR_SM_SIZE, STR_SIZE
|
||||
from lot.models import DeviceLot
|
||||
|
||||
|
||||
class Device(models.Model):
|
||||
class Device:
|
||||
class Types(models.TextChoices):
|
||||
DESKTOP = "Desktop"
|
||||
LAPTOP = "Laptop"
|
||||
|
@ -20,155 +23,140 @@ class Device(models.Model):
|
|||
BATTERY = "Battery"
|
||||
CAMERA = "Camera"
|
||||
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
updated = models.DateTimeField(auto_now=True)
|
||||
type = models.CharField(max_length=STR_SM_SIZE, choices=Types, default=Types.LAPTOP)
|
||||
model = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
||||
manufacturer = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
||||
serial_number = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
||||
part_number = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
||||
brand = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
||||
generation = models.SmallIntegerField(blank=True, null=True)
|
||||
version = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
||||
production_date = models.DateTimeField(blank=True, null=True)
|
||||
variant = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
||||
devicehub_id = models.CharField(max_length=STR_SIZE, unique=True, blank=True, null=True)
|
||||
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 __init__(self, *args, **kwargs):
|
||||
# the id is the chid of the device
|
||||
self.id = kwargs["id"]
|
||||
self.pk = self.id
|
||||
self.algorithm = None
|
||||
self.owner = None
|
||||
self.annotations = []
|
||||
self.hids = []
|
||||
self.uuids = []
|
||||
self.evidences = []
|
||||
self.lots = []
|
||||
self.last_evidence = None
|
||||
self.get_last_evidence()
|
||||
|
||||
def has_physical_properties(self):
|
||||
try:
|
||||
if self.physicalproperties:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
def initial(self):
|
||||
self.get_annotations()
|
||||
self.get_uuids()
|
||||
self.get_hids()
|
||||
self.get_evidences()
|
||||
self.get_lots()
|
||||
|
||||
def get_annotations(self):
|
||||
if self.annotations:
|
||||
return self.annotations
|
||||
|
||||
class PhysicalProperties(models.Model):
|
||||
device = models.OneToOneField(Device, models.CASCADE, primary_key=True)
|
||||
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(max_length=20, blank=True, null=True)
|
||||
image = models.CharField(max_length=STR_SIZE, blank=True, null=True)
|
||||
self.annotations = Annotation.objects.filter(
|
||||
type=Annotation.Type.SYSTEM,
|
||||
value=self.id
|
||||
).order_by("-created")
|
||||
|
||||
if self.annotations.count():
|
||||
self.algorithm = self.annotations[0].key
|
||||
self.owner = self.annotations[0].owner
|
||||
|
||||
class Computer(models.Model):
|
||||
class Chassis(models.TextChoices):
|
||||
TOWER = 'Tower'
|
||||
ALLINONE = 'All in one'
|
||||
MICROTOWER = 'Microtower'
|
||||
NETBOOK = 'Netbook'
|
||||
LAPTOP = 'Laptop'
|
||||
TABLER = 'Tablet'
|
||||
SERVER = "Server"
|
||||
VIRTUAL = 'Non-physical device'
|
||||
return self.annotations
|
||||
|
||||
def get_user_annotations(self):
|
||||
if not self.uuids:
|
||||
self.get_uuids()
|
||||
|
||||
device = models.OneToOneField(Device, models.CASCADE, primary_key=True)
|
||||
chassis = models.CharField(
|
||||
blank=True,
|
||||
null=True,
|
||||
max_length=STR_SM_SIZE,
|
||||
choices=Chassis
|
||||
)
|
||||
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)
|
||||
annotations = Annotation.objects.filter(
|
||||
uuid__in=self.uuids,
|
||||
owner=self.owner,
|
||||
type=Annotation.Type.USER
|
||||
)
|
||||
return annotations
|
||||
|
||||
def get_user_documents(self):
|
||||
if not self.uuids:
|
||||
self.get_uuids()
|
||||
|
||||
class Component(models.Model):
|
||||
device = models.OneToOneField(Device, models.CASCADE, primary_key=True)
|
||||
computer = models.ForeignKey(Computer, on_delete=models.CASCADE, null=True)
|
||||
annotations = Annotation.objects.filter(
|
||||
uuid__in=self.uuids,
|
||||
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):
|
||||
component = models.OneToOneField(Component, models.CASCADE)
|
||||
memory = models.IntegerField(blank=True, null=True)
|
||||
def get_hids(self):
|
||||
annotations = self.get_annotations()
|
||||
|
||||
self.hids = annotations.filter(
|
||||
type=Annotation.Type.SYSTEM,
|
||||
key__in=ALGOS.keys(),
|
||||
).values_list("value", flat=True)
|
||||
|
||||
class DataStorage(models.Model):
|
||||
class Interface(models.TextChoices):
|
||||
ATA = 'ATA'
|
||||
USB = 'USB'
|
||||
PCI = 'PCI'
|
||||
NVME = 'NVME'
|
||||
def get_evidences(self):
|
||||
if not self.uuids:
|
||||
self.get_uuids()
|
||||
|
||||
size = models.IntegerField(blank=True, null=True)
|
||||
interface = models.CharField(max_length=STR_SM_SIZE, choices=Interface)
|
||||
component = models.OneToOneField(Component, models.CASCADE)
|
||||
self.evidences = [Evidence(u) for u in self.uuids]
|
||||
|
||||
def get_last_evidence(self):
|
||||
annotations = self.get_annotations()
|
||||
if annotations:
|
||||
annotation = annotations.first()
|
||||
self.last_evidence = Evidence(annotation.uuid)
|
||||
|
||||
class Motherboard(models.Model):
|
||||
component = models.OneToOneField(Component, models.CASCADE)
|
||||
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 last_uuid(self):
|
||||
return self.uuids[0]
|
||||
|
||||
def get_lots(self):
|
||||
self.lots = [x.lot for x in DeviceLot.objects.filter(device_id=self.id)]
|
||||
|
||||
class NetworkAdapter(models.Model):
|
||||
component = models.OneToOneField(Component, models.CASCADE)
|
||||
speed = models.IntegerField(blank=True, null=True)
|
||||
wireless = models.BooleanField(default=False)
|
||||
@classmethod
|
||||
def get_unassigned(cls, user):
|
||||
chids = DeviceLot.objects.filter(lot__owner=user).values_list("device_id", flat=True).distinct()
|
||||
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):
|
||||
v = super().__format__(format_spec)
|
||||
if 's' in format_spec:
|
||||
v += ' – {} Mbps'.format(self.speed)
|
||||
return v
|
||||
# return cls.objects.filter(
|
||||
# owner=user
|
||||
# ).annotate(num_lots=models.Count('lot')).filter(num_lots=0)
|
||||
|
||||
@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):
|
||||
component = models.OneToOneField(Component, models.CASCADE)
|
||||
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)
|
||||
|
||||
|
||||
class RamModule(models.Model):
|
||||
class Interface(models.TextChoices):
|
||||
SDRAM = 'SDRAM'
|
||||
DDR = 'DDR SDRAM'
|
||||
DDR2 = 'DDR2 SDRAM'
|
||||
DDR3 = 'DDR3 SDRAM'
|
||||
DDR4 = 'DDR4 SDRAM'
|
||||
DDR5 = 'DDR5 SDRAM'
|
||||
DDR6 = 'DDR6 SDRAM'
|
||||
LPDDR3 = 'LPDDR3'
|
||||
|
||||
class Format(models.TextChoices):
|
||||
DIMM = 'DIMM'
|
||||
SODIMM = 'SODIMM'
|
||||
|
||||
component = models.OneToOneField(Component, models.CASCADE)
|
||||
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)
|
||||
@property
|
||||
def last_user_evidence(self):
|
||||
if not self.last_evidence:
|
||||
self.get_last_evidence()
|
||||
return self.last_evidence.doc['kv'].items()
|
||||
|
||||
@property
|
||||
def manufacturer(self):
|
||||
if not self.last_evidence:
|
||||
self.get_last_evidence()
|
||||
return self.last_evidence.doc['device']['manufacturer']
|
||||
|
||||
class Display(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']
|
||||
|
||||
class Battery(models.Model):
|
||||
component = models.OneToOneField(Component, models.CASCADE)
|
||||
|
||||
@property
|
||||
def model(self):
|
||||
if not self.last_evidence:
|
||||
self.get_last_evidence()
|
||||
return self.last_evidence.doc['device']['model']
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
if not self.last_evidence:
|
||||
self.get_last_evidence()
|
||||
return self.last_evidence.doc['device']['type']
|
||||
|
|
|
@ -4,18 +4,18 @@
|
|||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h3>{{ object.pk }}</h3>
|
||||
<h3>{{ object.id }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="nav nav-tabs nav-tabs-bordered">
|
||||
<ul class="nav nav-tabs nav-tabs-bordered">
|
||||
<li class="nav-items">
|
||||
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#details">General details</button>
|
||||
</li>
|
||||
<li class="nav-items">
|
||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#physicalproperties">Physical properties</button>
|
||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#annotations">User annotations</button>
|
||||
</li>
|
||||
<li class="nav-items">
|
||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#documents">Documents</button>
|
||||
|
@ -23,20 +23,16 @@
|
|||
<li class="nav-items">
|
||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#lots">Lots</button>
|
||||
</li>
|
||||
<li class="nav-items">
|
||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#status">Status</button>
|
||||
</li>
|
||||
<li class="nav-items">
|
||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#components">Components</button>
|
||||
</li>
|
||||
<li class="nav-items">
|
||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#traceabiliy">Traceability log</button>
|
||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#evidences">Evidences</button>
|
||||
</li>
|
||||
<li class="nav-items">
|
||||
<a class="nav-link" href="">Web</a>
|
||||
</li>
|
||||
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-content pt-2">
|
||||
|
@ -44,29 +40,23 @@
|
|||
<div class="tab-pane fade show active" id="details">
|
||||
<h5 class="card-title">Details</h5>
|
||||
<div class="row mb-3">
|
||||
<div class="col-lg-3 col-md-4 label ">
|
||||
(<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-9 col-md-8">{{ object.id }}</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="col-lg-3 col-md-4 label ">Type</div>
|
||||
<div class="col-lg-9 col-md-8">{{ object.type }}</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="col-lg-3 col-md-4 label">Manufacturer</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>
|
||||
|
||||
<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="col-lg-3 col-md-4 label">Serial Number</div>
|
||||
<div class="col-lg-9 col-md-8">{{ object.serial_number|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 class="col-lg-9 col-md-8">{{ object.last_evidence.doc.device.serialNumber|default:"" }}</div>
|
||||
</div>
|
||||
{% 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 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>
|
||||
|
||||
<h5 class="card-title">Outgoing Lots</h5>
|
||||
|
||||
<div class="row">
|
||||
|
||||
</div>
|
||||
|
||||
<h5 class="card-title">Temporary Lots</h5>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col">
|
||||
<a href="{% url 'dashboard:lot' lot.id %}">{{ lot.name }}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade profile-overview" id="documents">
|
||||
<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>
|
||||
Add new document
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h5 class="card-title">Documents</h5>
|
||||
<table class="table">
|
||||
<h5 class="card-title mt-2">Documents</h5>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">File</th>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Description</th>
|
||||
<th scope="col" data-type="date" data-format="YYYY-MM-DD hh:mm">Uploaded on</th>
|
||||
<th scope="col">Key</th>
|
||||
<th scope="col">Value</th>
|
||||
<th scope="col" data-type="date" data-format="YYYY-MM-DD hh:mm">Created on</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for a in object.get_user_documents %}
|
||||
<tr>
|
||||
<td>{{ a.key }}</td>
|
||||
<td>{{ a.value }}</td>
|
||||
<td>{{ a.created }}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade profile-overview" id="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">
|
||||
<h5 class="card-title">Components Snapshot</h5>
|
||||
<h5 class="card-title">Components last evidence</h5>
|
||||
<div class="list-group col-6">
|
||||
|
||||
{% for c in object.last_evidence.doc.components %}
|
||||
<div class="list-group-item">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h5 class="mb-1">Motherboard</h5>
|
||||
<small class="text-muted">14:07 23-06-2024</small>
|
||||
<h5 class="mb-1">{{ c.type }}</h5>
|
||||
<small class="text-muted">{{ evidence.created }}</small>
|
||||
</div>
|
||||
<p class="mb-1">
|
||||
hp<br />
|
||||
890e<br />
|
||||
{{ c.manufacturer }}<br />
|
||||
{{ c.model }}<br />
|
||||
{{ c.serialNumber }}<br />
|
||||
</p>
|
||||
<small class="text-muted">
|
||||
</small>
|
||||
</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="d-flex w-100 justify-content-between">
|
||||
<h5 class="mb-1">NetworkAdapter</h5>
|
||||
<small class="text-muted">14:07 23-06-2024</small>
|
||||
<h5 class="mb-1"></h5>
|
||||
<small class="text-muted">{{ snap.created }}</small>
|
||||
</div>
|
||||
<p class="mb-1">
|
||||
realtek semiconductor co., ltd.<br />
|
||||
rtl8852ae 802.11ax pcie wireless network adapter<br />
|
||||
<a href="{% url 'evidence:details' snap.uuid %}">{{ snap.uuid }}</a>
|
||||
</p>
|
||||
<small class="text-muted">
|
||||
</small>
|
||||
</div>
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
45
device/templates/new_annotation.html
Normal file
45
device/templates/new_annotation.html
Normal 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 %}
|
|
@ -8,25 +8,78 @@
|
|||
</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 %}
|
||||
<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>
|
||||
{% 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 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>
|
||||
{% 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 %}
|
||||
|
|
|
@ -5,7 +5,8 @@ app_name = 'device'
|
|||
|
||||
urlpatterns = [
|
||||
path("add/", views.NewDeviceView.as_view(), name="add"),
|
||||
path("edit/<int:pk>/", views.EditDeviceView.as_view(), name="edit"),
|
||||
path("<int:pk>/", views.DetailsView.as_view(), name="details"),
|
||||
path("physical/<int:pk>/", views.PhysicalView.as_view(), name="physical_edit"),
|
||||
path("edit/<str:pk>/", views.EditDeviceView.as_view(), name="edit"),
|
||||
path("<str:pk>/", views.DetailsView.as_view(), name="details"),
|
||||
path("<str:pk>/annotation/add", views.AddAnnotationView.as_view(), name="add_annotation"),
|
||||
path("<str:pk>/document/add", views.AddDocumentView.as_view(), name="add_document"),
|
||||
]
|
||||
|
|
176
device/views.py
176
device/views.py
|
@ -1,60 +1,74 @@
|
|||
import json
|
||||
|
||||
from django.urls import reverse_lazy
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic.edit import (
|
||||
CreateView,
|
||||
UpdateView,
|
||||
FormView,
|
||||
)
|
||||
from dashboard.mixins import DashboardView, DetailsMixin
|
||||
from device.models import Device, PhysicalProperties
|
||||
from django.views.generic.base import TemplateView
|
||||
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"
|
||||
title = _("New Device")
|
||||
breadcrumb = "Device / New Device"
|
||||
success_url = reverse_lazy('dashboard:unassigned_devices')
|
||||
model = Device
|
||||
fields = (
|
||||
'type',
|
||||
"model",
|
||||
"manufacturer",
|
||||
"serial_number",
|
||||
"part_number",
|
||||
"brand",
|
||||
"generation",
|
||||
"version",
|
||||
"production_date",
|
||||
"variant",
|
||||
"family",
|
||||
)
|
||||
success_url = reverse_lazy('device:add')
|
||||
form_class = DeviceFormSet
|
||||
|
||||
def form_valid(self, form):
|
||||
form.instance.owner = self.request.user
|
||||
form.save(self.request.user)
|
||||
response = super().form_valid(form)
|
||||
PhysicalProperties.objects.create(device=form.instance)
|
||||
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):
|
||||
template_name = "new_device.html"
|
||||
title = _("Update Device")
|
||||
breadcrumb = "Device / Update Device"
|
||||
success_url = reverse_lazy('dashboard:unassigned_devices')
|
||||
model = Device
|
||||
fields = (
|
||||
'type',
|
||||
"model",
|
||||
"manufacturer",
|
||||
"serial_number",
|
||||
"part_number",
|
||||
"brand",
|
||||
"generation",
|
||||
"version",
|
||||
"production_date",
|
||||
"variant",
|
||||
"family",
|
||||
)
|
||||
model = Annotation
|
||||
|
||||
def get_form_kwargs(self):
|
||||
pk = self.kwargs.get('pk')
|
||||
|
@ -62,50 +76,80 @@ class EditDeviceView(DashboardView, UpdateView):
|
|||
self.success_url = reverse_lazy('device:details', args=[pk])
|
||||
kwargs = super().get_form_kwargs()
|
||||
return kwargs
|
||||
|
||||
|
||||
class DetailsView(DetailsMixin):
|
||||
|
||||
class DetailsView(DashboardView, TemplateView):
|
||||
template_name = "details.html"
|
||||
title = _("Device")
|
||||
breadcrumb = "Device / Details"
|
||||
model = Device
|
||||
model = Annotation
|
||||
|
||||
|
||||
class PhysicalView(DashboardView, UpdateView):
|
||||
template_name = "physical_properties.html"
|
||||
title = _("Physical Properties")
|
||||
breadcrumb = "Device / Physical properties"
|
||||
success_url = reverse_lazy('dashboard:unassigned_devices')
|
||||
model = PhysicalProperties
|
||||
fields = (
|
||||
"weight",
|
||||
"width",
|
||||
"height",
|
||||
"depth",
|
||||
"color",
|
||||
"image",
|
||||
)
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.pk = kwargs['pk']
|
||||
self.object = Device(id=self.pk)
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
self.object.initial()
|
||||
lot_tags = LotTag.objects.filter(owner=self.request.user)
|
||||
context.update({
|
||||
'device': self.device,
|
||||
'object': self.object,
|
||||
'snapshot': self.object.get_last_evidence(),
|
||||
'lot_tags': lot_tags,
|
||||
})
|
||||
return context
|
||||
|
||||
def get_form_kwargs(self):
|
||||
pk = self.kwargs.get('pk')
|
||||
self.device = get_object_or_404(Device, pk=pk)
|
||||
try:
|
||||
self.object = self.device.physicalproperties
|
||||
except Exception:
|
||||
self.object = PhysicalProperties.objects.create(device=self.device)
|
||||
kwargs = super().get_form_kwargs()
|
||||
return kwargs
|
||||
|
||||
|
||||
class AddAnnotationView(DashboardView, CreateView):
|
||||
template_name = "new_annotation.html"
|
||||
title = _("New annotation")
|
||||
breadcrumb = "Device / New annotation"
|
||||
success_url = reverse_lazy('dashboard:unassigned_devices')
|
||||
model = Annotation
|
||||
fields = ("key", "value")
|
||||
|
||||
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)
|
||||
return response
|
||||
|
||||
|
||||
def get_form_kwargs(self):
|
||||
pk = self.kwargs.get('pk')
|
||||
self.annotation = Annotation.objects.filter(
|
||||
owner=self.request.user, value=pk, type=Annotation.Type.SYSTEM
|
||||
).first()
|
||||
if not self.annotation:
|
||||
get_object_or_404(Annotation, pk=0, owner=self.request.user)
|
||||
self.success_url = reverse_lazy('device:details', args=[pk])
|
||||
kwargs = super().get_form_kwargs()
|
||||
return kwargs
|
||||
|
||||
|
||||
class AddDocumentView(DashboardView, CreateView):
|
||||
template_name = "new_annotation.html"
|
||||
title = _("New Document")
|
||||
breadcrumb = "Device / New document"
|
||||
success_url = reverse_lazy('dashboard:unassigned_devices')
|
||||
model = Annotation
|
||||
fields = ("key", "value")
|
||||
|
||||
def form_valid(self, form):
|
||||
form.instance.owner = self.request.user
|
||||
form.instance.uuid = self.annotation.uuid
|
||||
form.instance.type = Annotation.Type.DOCUMENT
|
||||
response = super().form_valid(form)
|
||||
return response
|
||||
|
||||
def get_form_kwargs(self):
|
||||
pk = self.kwargs.get('pk')
|
||||
self.annotation = Annotation.objects.filter(
|
||||
owner=self.request.user, value=pk, type=Annotation.Type.SYSTEM
|
||||
).first()
|
||||
if not self.annotation:
|
||||
get_object_or_404(Annotation, pk=0, owner=self.request.user)
|
||||
self.success_url = reverse_lazy('device:details', args=[pk])
|
||||
kwargs = super().get_form_kwargs()
|
||||
return kwargs
|
||||
|
|
|
@ -10,6 +10,8 @@ For the full list of settings and their values, see
|
|||
https://docs.djangoproject.com/en/5.0/ref/settings/
|
||||
"""
|
||||
|
||||
import xapian
|
||||
|
||||
from pathlib import Path
|
||||
from django.contrib.messages import constants as messages
|
||||
from decouple import config, Csv
|
||||
|
@ -45,7 +47,7 @@ INSTALLED_APPS = [
|
|||
"login",
|
||||
"user",
|
||||
"device",
|
||||
"snapshot",
|
||||
"evidence",
|
||||
"action",
|
||||
"tag",
|
||||
"lot",
|
||||
|
@ -92,11 +94,10 @@ WSGI_APPLICATION = "dhub.wsgi.application"
|
|||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.sqlite3",
|
||||
"NAME": BASE_DIR / "db.sqlite3",
|
||||
"NAME": BASE_DIR / "db/db.sqlite3",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
|
||||
|
||||
|
@ -170,3 +171,4 @@ LOGGING = {
|
|||
}
|
||||
|
||||
SNAPSHOT_PATH="/tmp/"
|
||||
DATA_UPLOAD_MAX_NUMBER_FILES = 1000
|
||||
|
|
|
@ -21,7 +21,7 @@ urlpatterns = [
|
|||
# path('api/', include('snapshot.urls')),
|
||||
path("", include("login.urls")),
|
||||
path("dashboard/", include("dashboard.urls")),
|
||||
path("snapshot/", include("snapshot.urls")),
|
||||
path("evidence/", include("evidence.urls")),
|
||||
path("device/", include("device.urls")),
|
||||
path("lot/", include("lot.urls")),
|
||||
]
|
||||
|
|
12
docker-compose.yml
Normal file
12
docker-compose.yml
Normal 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
|
||||
|
35
docker/devicehub-django.Dockerfile
Normal file
35
docker/devicehub-django.Dockerfile
Normal 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
|
56
docker/devicehub-django.entrypoint.sh
Normal file
56
docker/devicehub-django.entrypoint.sh
Normal 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 "${@}"
|
|
@ -3,4 +3,4 @@ from django.apps import AppConfig
|
|||
|
||||
class ActionConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "snapshot"
|
||||
name = "evidence"
|
130
evidence/forms.py
Normal file
130
evidence/forms.py
Normal 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
|
|
@ -4,7 +4,7 @@ import json
|
|||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.contrib.auth import get_user_model
|
||||
from snapshot.parse import Build
|
||||
from evidence.parse import Build
|
||||
|
||||
|
||||
User = get_user_model()
|
52
evidence/migrations/0001_initial.py
Normal file
52
evidence/migrations/0001_initial.py
Normal 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"
|
||||
),
|
||||
),
|
||||
]
|
20
evidence/migrations/0002_alter_annotation_type.py
Normal file
20
evidence/migrations/0002_alter_annotation_type.py
Normal 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")]
|
||||
),
|
||||
),
|
||||
]
|
20
evidence/migrations/0003_alter_annotation_type.py
Normal file
20
evidence/migrations/0003_alter_annotation_type.py
Normal 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
78
evidence/models.py
Normal 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
54
evidence/parse.py
Normal 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
|
||||
)
|
|
@ -1,15 +1,15 @@
|
|||
from rest_framework import serializers
|
||||
from snapshot.models import SnapshotJson
|
||||
from evidence.models import EvidenceJson
|
||||
|
||||
import json
|
||||
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
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:
|
||||
model = SnapshotJson
|
||||
model = EvidenceJson
|
||||
fields = ['id', 'title', 'content']
|
||||
|
||||
@csrf_exempt
|
76
evidence/templates/ev_details.html
Normal file
76
evidence/templates/ev_details.html
Normal 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 %}
|
26
evidence/templates/evidences.html
Normal file
26
evidence/templates/evidences.html
Normal 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 %}
|
32
evidence/templates/upload.html
Normal file
32
evidence/templates/upload.html
Normal 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
22
evidence/urls.py
Normal 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
137
evidence/views.py
Normal 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
51
evidence/xapian.py
Normal 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
BIN
example/placeholders1.ods
Normal file
Binary file not shown.
1
example/snapshots/snapshot1.json
Normal file
1
example/snapshots/snapshot1.json
Normal 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}
|
|
@ -1,6 +1,7 @@
|
|||
from django import forms
|
||||
from lot.models import Lot
|
||||
|
||||
|
||||
class LotsForm(forms.Form):
|
||||
lots = forms.ModelMultipleChoiceField(
|
||||
queryset=Lot.objects.all(),
|
||||
|
@ -8,20 +9,20 @@ class LotsForm(forms.Form):
|
|||
)
|
||||
|
||||
def clean(self):
|
||||
# import pdb; pdb.set_trace()
|
||||
self._lots = self.cleaned_data.get("lots")
|
||||
return self._lots
|
||||
|
||||
def save(self, commit=True):
|
||||
if not commit:
|
||||
return
|
||||
|
||||
for dev in self.devices:
|
||||
for lot in self._lots:
|
||||
lot.devices.add(dev.id)
|
||||
lot.add(dev.id)
|
||||
return
|
||||
|
||||
def remove(self):
|
||||
for dev in self.devices:
|
||||
for lot in self._lots:
|
||||
lot.devices.remove(dev.id)
|
||||
lot.remove(dev.id)
|
||||
return
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 5.0.6 on 2024-07-08 13:55
|
||||
# Generated by Django 5.0.6 on 2024-07-27 16:23
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
|
@ -10,7 +10,6 @@ class Migration(migrations.Migration):
|
|||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("device", "0003_remove_component_type_remove_computer_type_and_more"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
|
@ -29,23 +28,10 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
("created", models.DateTimeField(auto_now_add=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)),
|
||||
("code", models.CharField(blank=True, max_length=64, null=True)),
|
||||
("description", models.CharField(blank=True, max_length=64, null=True)),
|
||||
("closed", models.BooleanField(default=True)),
|
||||
("devices", models.ManyToManyField(to="device.device")),
|
||||
(
|
||||
"owner",
|
||||
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"
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
52
lot/migrations/0002_lotannotation.py
Normal file
52
lot/migrations/0002_lotannotation.py
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -1,41 +1,58 @@
|
|||
from django.db import models
|
||||
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 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 Types(models.TextChoices):
|
||||
INCOMING = "Incoming"
|
||||
OUTGOING = "Outgoing"
|
||||
TEMPORAL = "Temporal"
|
||||
|
||||
created = models.DateTimeField(auto_now_add=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)
|
||||
code = 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)
|
||||
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
devices = models.ManyToManyField(Device)
|
||||
type = models.ForeignKey(LotTag, on_delete=models.CASCADE)
|
||||
|
||||
@property
|
||||
def is_incoming(self):
|
||||
if self.type == self.Types.INCOMING:
|
||||
return True
|
||||
return False
|
||||
def add(self, v):
|
||||
if DeviceLot.objects.filter(lot=self, device_id=v).exists():
|
||||
return
|
||||
DeviceLot.objects.create(lot=self, device_id=v)
|
||||
|
||||
def remove(self, v):
|
||||
for d in DeviceLot.objects.filter(lot=self, device_id=v):
|
||||
d.delete()
|
||||
|
||||
@property
|
||||
def is_outgoing(self):
|
||||
if self.type == self.Types.OUTGOING:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_temporal(self):
|
||||
if self.type == self.Types.TEMPORAL:
|
||||
return True
|
||||
return False
|
||||
class LotAnnotation(models.Model):
|
||||
class Type(models.IntegerChoices):
|
||||
SYSTEM= 0, "System"
|
||||
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)
|
||||
|
|
48
lot/templates/annotations.html
Normal file
48
lot/templates/annotations.html
Normal 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 %}
|
48
lot/templates/documents.html
Normal file
48
lot/templates/documents.html
Normal 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 %}
|
|
@ -11,50 +11,21 @@
|
|||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{% if incoming %}
|
||||
{% for tag in lot_tags %}
|
||||
<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>
|
||||
|
||||
{% for lot in lots %}
|
||||
{% if lot.is_incoming %}
|
||||
{% if lot.type == tag %}
|
||||
<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 %}
|
||||
|
||||
{% 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 %}
|
||||
{% 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>
|
||||
</form>
|
||||
|
||||
|
|
45
lot/templates/new_annotation.html
Normal file
45
lot/templates/new_annotation.html
Normal 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 %}
|
|
@ -9,7 +9,9 @@ urlpatterns = [
|
|||
path("edit/<int:pk>/", views.EditLotView.as_view(), name="edit"),
|
||||
path("add/devices/", views.AddToLotView.as_view(), name="add_devices"),
|
||||
path("del/devices/", views.DelToLotView.as_view(), name="del_devices"),
|
||||
path("temporal/", views.LotsTemporalView.as_view(), name="lots_temporal"),
|
||||
path("outgoing/", views.LotsOutgoingView.as_view(), name="lots_outgoing"),
|
||||
path("incoming/", views.LotsIncomingView.as_view(), name="lots_incoming"),
|
||||
path("tag/<int:pk>/", views.LotsTagsView.as_view(), name="tag"),
|
||||
path("<int:pk>/document/", views.LotDocumentsView.as_view(), name="documents"),
|
||||
path("<int:pk>/document/add", views.LotAddDocumentView.as_view(), name="add_document"),
|
||||
path("<int:pk>/annotation", views.LotAnnotationsView.as_view(), name="annotations"),
|
||||
path("<int:pk>/annotation/add", views.LotAddAnnotationView.as_view(), name="add_annotation"),
|
||||
]
|
||||
|
|
127
lot/views.py
127
lot/views.py
|
@ -6,10 +6,10 @@ from django.views.generic.edit import (
|
|||
CreateView,
|
||||
DeleteView,
|
||||
UpdateView,
|
||||
FormView
|
||||
FormView,
|
||||
)
|
||||
from dashboard.mixins import DashboardView
|
||||
from lot.models import Lot
|
||||
from lot.models import Lot, LotTag, LotAnnotation
|
||||
from lot.forms import LotsForm
|
||||
|
||||
|
||||
|
@ -84,20 +84,15 @@ class AddToLotView(DashboardView, FormView):
|
|||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
lots = Lot.objects.filter(owner=self.request.user)
|
||||
lots_incoming = lots.filter(type=Lot.Types.INCOMING).exists()
|
||||
lots_outgoing = lots.filter(type=Lot.Types.OUTGOING).exists()
|
||||
lots_temporal = lots.filter(type=Lot.Types.TEMPORAL).exists()
|
||||
lot_tags = LotTag.objects.filter(owner=self.request.user)
|
||||
context.update({
|
||||
'lots': lots,
|
||||
'incoming': lots_incoming,
|
||||
'outgoing': lots_outgoing,
|
||||
'temporal': lots_temporal
|
||||
'lot_tags':lot_tags,
|
||||
})
|
||||
return context
|
||||
|
||||
def get_form(self):
|
||||
form = super().get_form()
|
||||
# import pdb; pdb.set_trace()
|
||||
form.fields["lots"].queryset = Lot.objects.filter(owner=self.request.user)
|
||||
return form
|
||||
|
||||
|
@ -119,28 +114,114 @@ class DelToLotView(AddToLotView):
|
|||
return response
|
||||
|
||||
|
||||
class LotsTemporalView(DashboardView, TemplateView):
|
||||
class LotsTagsView(DashboardView, TemplateView):
|
||||
template_name = "lots.html"
|
||||
title = _("Temporal lots")
|
||||
breadcrumb = "lot / temporal lots"
|
||||
title = _("lots")
|
||||
breadcrumb = _("lots") + " /"
|
||||
success_url = reverse_lazy('dashboard:unassigned_devices')
|
||||
lot_type = Lot.Types.TEMPORAL
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
self.pk = kwargs.get('pk')
|
||||
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({
|
||||
'lots': lots.filter(type=self.lot_type),
|
||||
'lots': lots,
|
||||
'title': self.title,
|
||||
'breadcrumb': self.breadcrumb
|
||||
})
|
||||
return context
|
||||
|
||||
class LotsOutgoingView(LotsTemporalView):
|
||||
title = _("Outgoing lots")
|
||||
breadcrumb = "lot / outging lots"
|
||||
lot_type = Lot.Types.OUTGOING
|
||||
|
||||
class LotAddDocumentView(DashboardView, CreateView):
|
||||
template_name = "new_annotation.html"
|
||||
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):
|
||||
title = _("Incoming lots")
|
||||
breadcrumb = "lot / Incoming lots"
|
||||
lot_type = Lot.Types.INCOMING
|
||||
class LotDocumentsView(DashboardView, TemplateView):
|
||||
template_name = "documents.html"
|
||||
title = _("New Document")
|
||||
breadcrumb = "Device / New document"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
self.pk = kwargs.get('pk')
|
||||
context = super().get_context_data(**kwargs)
|
||||
lot = get_object_or_404(Lot, owner=self.request.user, id=self.pk)
|
||||
documents = LotAnnotation.objects.filter(
|
||||
lot=lot,
|
||||
owner=self.request.user,
|
||||
type=LotAnnotation.Type.DOCUMENT,
|
||||
)
|
||||
context.update({
|
||||
'lot': lot,
|
||||
'documents': documents,
|
||||
'title': self.title,
|
||||
'breadcrumb': self.breadcrumb
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
class LotAnnotationsView(DashboardView, TemplateView):
|
||||
template_name = "annotations.html"
|
||||
title = _("New Annotation")
|
||||
breadcrumb = "Device / New annotation"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
self.pk = kwargs.get('pk')
|
||||
context = super().get_context_data(**kwargs)
|
||||
lot = get_object_or_404(Lot, owner=self.request.user, id=self.pk)
|
||||
annotations = LotAnnotation.objects.filter(
|
||||
lot=lot,
|
||||
owner=self.request.user,
|
||||
type=LotAnnotation.Type.USER,
|
||||
)
|
||||
context.update({
|
||||
'lot': lot,
|
||||
'annotations': annotations,
|
||||
'title': self.title,
|
||||
'breadcrumb': self.breadcrumb
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
class LotAddAnnotationView(DashboardView, CreateView):
|
||||
template_name = "new_annotation.html"
|
||||
title = _("New Annotation")
|
||||
breadcrumb = "Device / New annotation"
|
||||
success_url = reverse_lazy('dashboard:unassigned_devices')
|
||||
model = LotAnnotation
|
||||
fields = ("key", "value")
|
||||
|
||||
def form_valid(self, form):
|
||||
form.instance.owner = self.request.user
|
||||
form.instance.lot = self.lot
|
||||
form.instance.type = LotAnnotation.Type.USER
|
||||
response = super().form_valid(form)
|
||||
return response
|
||||
|
||||
def get_form_kwargs(self):
|
||||
pk = self.kwargs.get('pk')
|
||||
self.lot = get_object_or_404(Lot, pk=pk, owner=self.request.user)
|
||||
self.success_url = reverse_lazy('lot:annotations', args=[pk])
|
||||
kwargs = super().get_form_kwargs()
|
||||
return kwargs
|
||||
|
|
|
@ -3,5 +3,9 @@ Django==5.0.6
|
|||
django-bootstrap5==24.2
|
||||
django-extensions==3.2.3
|
||||
djangorestframework==3.15.1
|
||||
py-dmidecode==0.1.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
4
reset.sh
Normal 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
|
|
@ -1,75 +0,0 @@
|
|||
# Generated by Django 5.0.6 on 2024-06-11 09:20
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("device", "0001_initial"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Snapshot",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("created", models.DateTimeField(auto_now_add=True)),
|
||||
(
|
||||
"software",
|
||||
models.CharField(
|
||||
choices=[("Workbench", "Workbench")],
|
||||
default="Workbench",
|
||||
max_length=32,
|
||||
),
|
||||
),
|
||||
("uuid", models.UUIDField()),
|
||||
("version", models.CharField(max_length=32)),
|
||||
("sid", models.CharField(max_length=32)),
|
||||
("settings_version", models.CharField(max_length=32)),
|
||||
("is_server_erase", models.BooleanField(default=False)),
|
||||
(
|
||||
"severity",
|
||||
models.SmallIntegerField(
|
||||
choices=[
|
||||
(0, "Info"),
|
||||
(1, "Notice"),
|
||||
(2, "Warning"),
|
||||
(3, "Error"),
|
||||
],
|
||||
default=0,
|
||||
),
|
||||
),
|
||||
("start_time", models.DateTimeField()),
|
||||
("end_time", models.DateTimeField()),
|
||||
("components", models.ManyToManyField(to="device.component")),
|
||||
(
|
||||
"computer",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="device.computer",
|
||||
),
|
||||
),
|
||||
(
|
||||
"owner",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -1,18 +0,0 @@
|
|||
# Generated by Django 5.0.6 on 2024-07-11 14:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("snapshot", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="snapshot",
|
||||
name="uuid",
|
||||
field=models.UUIDField(unique=True),
|
||||
),
|
||||
]
|
|
@ -1,17 +0,0 @@
|
|||
# Generated by Django 5.0.6 on 2024-07-11 14:18
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("snapshot", "0002_alter_snapshot_uuid"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="snapshot",
|
||||
name="start_time",
|
||||
),
|
||||
]
|
|
@ -1,31 +0,0 @@
|
|||
from django.db import models
|
||||
from utils.constants import STR_SM_SIZE
|
||||
from user.models import User
|
||||
from device.models import Computer, Component
|
||||
|
||||
# Create your models here.
|
||||
|
||||
|
||||
class Snapshot(models.Model):
|
||||
class SoftWare(models.TextChoices):
|
||||
WORKBENCH= "Workbench"
|
||||
|
||||
class Severity(models.IntegerChoices):
|
||||
Info = 0, "Info"
|
||||
Notice = 1, "Notice"
|
||||
Warning = 2, "Warning"
|
||||
Error = 3, "Error"
|
||||
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
software = models.CharField(max_length=STR_SM_SIZE, choices=SoftWare, default=SoftWare.WORKBENCH)
|
||||
uuid = models.UUIDField(unique=True)
|
||||
version = models.CharField(max_length=STR_SM_SIZE)
|
||||
sid = models.CharField(max_length=STR_SM_SIZE)
|
||||
settings_version = models.CharField(max_length=STR_SM_SIZE)
|
||||
is_server_erase = models.BooleanField(default=False)
|
||||
severity = models.SmallIntegerField(choices=Severity, default=Severity.Info)
|
||||
end_time = models.DateTimeField()
|
||||
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
computer = models.ForeignKey(Computer, on_delete=models.CASCADE)
|
||||
components = models.ManyToManyField(Component)
|
||||
|
|
@ -1,673 +0,0 @@
|
|||
import os
|
||||
import json
|
||||
import shutil
|
||||
import hashlib
|
||||
|
||||
from datetime import datetime
|
||||
from django.conf import settings
|
||||
from device.models import Device, Computer
|
||||
from snapshot.models import Snapshot
|
||||
|
||||
|
||||
HID = [
|
||||
"manufacturer",
|
||||
"model",
|
||||
"chassis",
|
||||
"serialNumber",
|
||||
"sku"
|
||||
]
|
||||
|
||||
|
||||
class Build:
|
||||
def __init__(self, snapshot_json, user):
|
||||
self.json = snapshot_json
|
||||
self.user = user
|
||||
self.hid = None
|
||||
self.result = False
|
||||
|
||||
self.save_disk()
|
||||
self.json_device = self.json["device"]
|
||||
self.json_components = self.json["components"]
|
||||
self.uuid = self.json["uuid"]
|
||||
self.get_hid()
|
||||
self.gen_computer()
|
||||
self.gen_components()
|
||||
self.gen_actions()
|
||||
self.gen_snapshot()
|
||||
|
||||
self.result = True
|
||||
self.move_json()
|
||||
|
||||
def save_disk(self):
|
||||
snapshot_path = settings.SNAPSHOT_PATH
|
||||
user = self.user.email
|
||||
uuid = self.json.get("uuid", "")
|
||||
now = datetime.now().strftime("%Y-%m-%d-%H-%M")
|
||||
filename = f"{now}_{user}_{uuid}.json"
|
||||
path_dir_base = os.path.join(snapshot_path, user)
|
||||
path_upload = os.path.join(path_dir_base, 'upload')
|
||||
path_name = os.path.join(path_upload, filename)
|
||||
self.filename = path_name
|
||||
self.path_dir_base = path_dir_base
|
||||
|
||||
if not os.path.isdir(path_dir_base):
|
||||
os.system(f'mkdir -p {path_upload}')
|
||||
|
||||
with open(path_name, 'w') as file:
|
||||
file.write(json.dumps(self.json))
|
||||
|
||||
def move_json(self):
|
||||
if not self.result:
|
||||
return
|
||||
|
||||
shutil.copy(self.filename, self.path_dir_base)
|
||||
os.remove(self.filename)
|
||||
|
||||
def get_hid(self):
|
||||
hid = ""
|
||||
for f in HID:
|
||||
hid += "-" + self.json_device[f]
|
||||
self.hid = hid
|
||||
|
||||
def gen_computer(self):
|
||||
self.device = Device.objects.filter(
|
||||
hid=self.hid,
|
||||
active=True,
|
||||
reliable=True,
|
||||
owner=self.user
|
||||
).first()
|
||||
|
||||
if self.device:
|
||||
return
|
||||
|
||||
chid = hashlib.sha3_256(self.hid.encode()).hexdigest()
|
||||
self.device = Device.objects.create(
|
||||
serial_number=self.json_device["serialNumber"],
|
||||
manufacturer=self.json_device["manufacturer"],
|
||||
version=self.json_device["version"],
|
||||
model=self.json_device["model"],
|
||||
type=self.json_device["type"],
|
||||
hid=self.hid,
|
||||
chid=chid,
|
||||
owner=self.user
|
||||
)
|
||||
|
||||
Computer.objects.create(
|
||||
device=self.device,
|
||||
sku=self.json_device["sku"],
|
||||
chassis=self.json_device["chassis"]
|
||||
)
|
||||
|
||||
def gen_snapshot(self):
|
||||
self.snapshot = Snapshot.objects.create(
|
||||
uuid = self.uuid,
|
||||
version = self.json["version"],
|
||||
computer = self.device.computer,
|
||||
sid = self.json.get("sid", ""),
|
||||
settings_version = self.json.get("settings_version", ""),
|
||||
end_time = self.json.get("endTime"),
|
||||
owner=self.user
|
||||
)
|
||||
|
||||
def gen_components(self):
|
||||
pass
|
||||
|
||||
def gen_actions(self):
|
||||
pass
|
||||
|
||||
|
||||
# class ParseSnapshot:
|
||||
# def __init__(self, snapshot, default="n/a"):
|
||||
# self.default = default
|
||||
# self.dmidecode_raw = snapshot["hwmd"]["dmidecode"]
|
||||
# self.smart_raw = snapshot["hwmd"]["smart"]
|
||||
# self.hwinfo_raw = snapshot["hwmd"]["hwinfo"]
|
||||
# self.lshw_raw = snapshot["hwmd"]["lshw"]
|
||||
# self.lscpi_raw = snapshot["hwmd"]["lspci"]
|
||||
# self.sanitize_raw = snapshot.get("sanitize", [])
|
||||
# self.device = {"actions": []}
|
||||
# self.components = []
|
||||
# self.monitors = []
|
||||
|
||||
# self.dmi = DMIParse(self.dmidecode_raw)
|
||||
# self.smart = self.loads(self.smart_raw)
|
||||
# self.lshw = self.loads(self.lshw_raw)
|
||||
# self.hwinfo = self.parse_hwinfo()
|
||||
|
||||
# self.set_computer()
|
||||
# self.get_hwinfo_monitors()
|
||||
# self.set_components()
|
||||
# self.snapshot_json = {
|
||||
# "type": "Snapshot",
|
||||
# "device": self.device,
|
||||
# "software": "Workbench",
|
||||
# # "software": snapshot["software"],
|
||||
# "components": self.components,
|
||||
# "uuid": snapshot['uuid'],
|
||||
# "version": "15.0.0",
|
||||
# # "version": snapshot['version'],
|
||||
# "settings_version": snapshot['settings_version'],
|
||||
# "endTime": snapshot["timestamp"],
|
||||
# "elapsed": 1,
|
||||
# "sid": snapshot["sid"],
|
||||
# }
|
||||
|
||||
# def get_snapshot(self):
|
||||
# return Snapshot().load(self.snapshot_json)
|
||||
|
||||
# def set_computer(self):
|
||||
# self.device['manufacturer'] = self.dmi.manufacturer().strip()
|
||||
# self.device['model'] = self.dmi.model().strip()
|
||||
# self.device['serialNumber'] = self.dmi.serial_number()
|
||||
# self.device['type'] = self.get_type()
|
||||
# self.device['sku'] = self.get_sku()
|
||||
# self.device['version'] = self.get_version()
|
||||
# self.device['system_uuid'] = self.get_uuid()
|
||||
# self.device['family'] = self.get_family()
|
||||
# self.device['chassis'] = self.get_chassis_dh()
|
||||
|
||||
# def set_components(self):
|
||||
# self.get_cpu()
|
||||
# self.get_ram()
|
||||
# self.get_mother_board()
|
||||
# self.get_graphic()
|
||||
# self.get_data_storage()
|
||||
# self.get_display()
|
||||
# self.get_sound_card()
|
||||
# self.get_networks()
|
||||
|
||||
# def get_cpu(self):
|
||||
# for cpu in self.dmi.get('Processor'):
|
||||
# serial = cpu.get('Serial Number')
|
||||
# if serial == 'Not Specified' or not serial:
|
||||
# serial = cpu.get('ID').replace(' ', '')
|
||||
# self.components.append(
|
||||
# {
|
||||
# "actions": [],
|
||||
# "type": "Processor",
|
||||
# "speed": self.get_cpu_speed(cpu),
|
||||
# "cores": int(cpu.get('Core Count', 1)),
|
||||
# "model": cpu.get('Version'),
|
||||
# "threads": int(cpu.get('Thread Count', 1)),
|
||||
# "manufacturer": cpu.get('Manufacturer'),
|
||||
# "serialNumber": serial,
|
||||
# "generation": None,
|
||||
# "brand": cpu.get('Family'),
|
||||
# "address": self.get_cpu_address(cpu),
|
||||
# }
|
||||
# )
|
||||
|
||||
# def get_ram(self):
|
||||
# for ram in self.dmi.get("Memory Device"):
|
||||
# if ram.get('size') == 'No Module Installed':
|
||||
# continue
|
||||
# if not ram.get("Speed"):
|
||||
# continue
|
||||
|
||||
# self.components.append(
|
||||
# {
|
||||
# "actions": [],
|
||||
# "type": "RamModule",
|
||||
# "size": self.get_ram_size(ram),
|
||||
# "speed": self.get_ram_speed(ram),
|
||||
# "manufacturer": ram.get("Manufacturer", self.default),
|
||||
# "serialNumber": ram.get("Serial Number", self.default),
|
||||
# "interface": ram.get("Type", "DDR"),
|
||||
# "format": ram.get("Form Factor", "DIMM"),
|
||||
# "model": ram.get("Part Number", self.default),
|
||||
# }
|
||||
# )
|
||||
|
||||
# def get_mother_board(self):
|
||||
# for moder_board in self.dmi.get("Baseboard"):
|
||||
# self.components.append(
|
||||
# {
|
||||
# "actions": [],
|
||||
# "type": "Motherboard",
|
||||
# "version": moder_board.get("Version"),
|
||||
# "serialNumber": moder_board.get("Serial Number", "").strip(),
|
||||
# "manufacturer": moder_board.get("Manufacturer", "").strip(),
|
||||
# "biosDate": self.get_bios_date(),
|
||||
# "ramMaxSize": self.get_max_ram_size(),
|
||||
# "ramSlots": len(self.dmi.get("Memory Device")),
|
||||
# "slots": self.get_ram_slots(),
|
||||
# "model": moder_board.get("Product Name", "").strip(),
|
||||
# "firewire": self.get_firmware_num(),
|
||||
# "pcmcia": self.get_pcmcia_num(),
|
||||
# "serial": self.get_serial_num(),
|
||||
# "usb": self.get_usb_num(),
|
||||
# }
|
||||
# )
|
||||
|
||||
# def get_graphic(self):
|
||||
# nodes = get_nested_dicts_with_key_value(self.lshw, 'class', 'display')
|
||||
# for c in nodes:
|
||||
# if not c['configuration'].get('driver', None):
|
||||
# continue
|
||||
|
||||
# self.components.append(
|
||||
# {
|
||||
# "actions": [],
|
||||
# "type": "GraphicCard",
|
||||
# "memory": self.get_memory_video(c),
|
||||
# "manufacturer": c.get("vendor", self.default),
|
||||
# "model": c.get("product", self.default),
|
||||
# "serialNumber": c.get("serial", self.default),
|
||||
# }
|
||||
# )
|
||||
|
||||
# def get_memory_video(self, c):
|
||||
# # get info of lspci
|
||||
# # pci_id = c['businfo'].split('@')[1]
|
||||
# # lspci.get(pci_id) | grep size
|
||||
# # lspci -v -s 00:02.0
|
||||
# return None
|
||||
|
||||
# def get_data_storage(self):
|
||||
# for sm in self.smart:
|
||||
# if sm.get('smartctl', {}).get('exit_status') == 1:
|
||||
# continue
|
||||
# model = sm.get('model_name')
|
||||
# manufacturer = None
|
||||
# if model and len(model.split(" ")) > 1:
|
||||
# mm = model.split(" ")
|
||||
# model = mm[-1]
|
||||
# manufacturer = " ".join(mm[:-1])
|
||||
|
||||
# self.components.append(
|
||||
# {
|
||||
# "actions": self.sanitize(sm),
|
||||
# "type": self.get_data_storage_type(sm),
|
||||
# "model": model,
|
||||
# "manufacturer": manufacturer,
|
||||
# "serialNumber": sm.get('serial_number'),
|
||||
# "size": self.get_data_storage_size(sm),
|
||||
# "variant": sm.get("firmware_version"),
|
||||
# "interface": self.get_data_storage_interface(sm),
|
||||
# }
|
||||
# )
|
||||
|
||||
# def sanitize(self, disk):
|
||||
# disk_sanitize = None
|
||||
# for d in self.sanitize_raw:
|
||||
# s = d.get('device_info', {}).get('export_data', {})
|
||||
# s = s.get('block', {}).get('serial')
|
||||
# if s == disk.get('serial_number'):
|
||||
# disk_sanitize = d
|
||||
# break
|
||||
# if not disk_sanitize:
|
||||
# return []
|
||||
|
||||
# steps = []
|
||||
# step_type = 'EraseBasic'
|
||||
# if d.get("method", {}).get('name') == 'Baseline Cryptographic':
|
||||
# step_type = 'EraseCrypto'
|
||||
|
||||
# if disk.get('type') == 'EraseCrypto':
|
||||
# step_type = 'EraseCrypto'
|
||||
|
||||
# erase = {
|
||||
# 'type': step_type,
|
||||
# 'severity': "Info",
|
||||
# 'steps': steps,
|
||||
# 'startTime': None,
|
||||
# 'endTime': None,
|
||||
# }
|
||||
# severities = []
|
||||
|
||||
# for step in disk_sanitize.get('steps', []):
|
||||
# severity = "Info"
|
||||
# if not step['success']:
|
||||
# severity = "Error"
|
||||
|
||||
# steps.append(
|
||||
# {
|
||||
# 'severity': severity,
|
||||
# 'startTime': unix_isoformat(step['start_time']),
|
||||
# 'endTime': unix_isoformat(step['end_time']),
|
||||
# 'type': 'StepRandom',
|
||||
# }
|
||||
# )
|
||||
# severities.append(severity)
|
||||
|
||||
# erase['endTime'] = unix_isoformat(step['end_time'])
|
||||
# if not erase['startTime']:
|
||||
# erase['startTime'] = unix_isoformat(step['start_time'])
|
||||
|
||||
# if "Error" in severities:
|
||||
# erase['severity'] = "Error"
|
||||
|
||||
# return [erase]
|
||||
|
||||
# def get_networks(self):
|
||||
# nodes = get_nested_dicts_with_key_value(self.lshw, 'class', 'network')
|
||||
# for c in nodes:
|
||||
# capacity = c.get('capacity')
|
||||
# units = c.get('units')
|
||||
# speed = None
|
||||
# if capacity and units:
|
||||
# speed = unit.Quantity(capacity, units).to('Mbit/s').m
|
||||
# wireless = bool(c.get('configuration', {}).get('wireless', False))
|
||||
# self.components.append(
|
||||
# {
|
||||
# "actions": [],
|
||||
# "type": "NetworkAdapter",
|
||||
# "model": c.get('product'),
|
||||
# "manufacturer": c.get('vendor'),
|
||||
# "serialNumber": c.get('serial'),
|
||||
# "speed": speed,
|
||||
# "variant": c.get('version', 1),
|
||||
# "wireless": wireless,
|
||||
# }
|
||||
# )
|
||||
|
||||
# def get_sound_card(self):
|
||||
# nodes = get_nested_dicts_with_key_value(self.lshw, 'class', 'multimedia')
|
||||
# for c in nodes:
|
||||
# self.components.append(
|
||||
# {
|
||||
# "actions": [],
|
||||
# "type": "SoundCard",
|
||||
# "model": c.get('product'),
|
||||
# "manufacturer": c.get('vendor'),
|
||||
# "serialNumber": c.get('serial'),
|
||||
# }
|
||||
# )
|
||||
|
||||
# def get_display(self): # noqa: C901
|
||||
# TECHS = 'CRT', 'TFT', 'LED', 'PDP', 'LCD', 'OLED', 'AMOLED'
|
||||
|
||||
# for c in self.monitors:
|
||||
# resolution_width, resolution_height = (None,) * 2
|
||||
# refresh, serial, model, manufacturer, size = (None,) * 5
|
||||
# year, week, production_date = (None,) * 3
|
||||
|
||||
# for x in c:
|
||||
# if "Vendor: " in x:
|
||||
# manufacturer = x.split('Vendor: ')[-1].strip()
|
||||
# if "Model: " in x:
|
||||
# model = x.split('Model: ')[-1].strip()
|
||||
# if "Serial ID: " in x:
|
||||
# serial = x.split('Serial ID: ')[-1].strip()
|
||||
# if " Resolution: " in x:
|
||||
# rs = x.split(' Resolution: ')[-1].strip()
|
||||
# if 'x' in rs:
|
||||
# resolution_width, resolution_height = [
|
||||
# int(r) for r in rs.split('x')
|
||||
# ]
|
||||
# if "Frequencies: " in x:
|
||||
# try:
|
||||
# refresh = int(float(x.split(',')[-1].strip()[:-3]))
|
||||
# except Exception:
|
||||
# pass
|
||||
# if 'Year of Manufacture' in x:
|
||||
# year = x.split(': ')[1]
|
||||
|
||||
# if 'Week of Manufacture' in x:
|
||||
# week = x.split(': ')[1]
|
||||
|
||||
# if "Size: " in x:
|
||||
# size = self.get_size_monitor(x)
|
||||
# technology = next((t for t in TECHS if t in c[0]), None)
|
||||
|
||||
# if year and week:
|
||||
# d = '{} {} 0'.format(year, week)
|
||||
# production_date = datetime.strptime(d, '%Y %W %w').isoformat()
|
||||
|
||||
# self.components.append(
|
||||
# {
|
||||
# "actions": [],
|
||||
# "type": "Display",
|
||||
# "model": model,
|
||||
# "manufacturer": manufacturer,
|
||||
# "serialNumber": serial,
|
||||
# 'size': size,
|
||||
# 'resolutionWidth': resolution_width,
|
||||
# 'resolutionHeight': resolution_height,
|
||||
# "productionDate": production_date,
|
||||
# 'technology': technology,
|
||||
# 'refreshRate': refresh,
|
||||
# }
|
||||
# )
|
||||
|
||||
# def get_hwinfo_monitors(self):
|
||||
# for c in self.hwinfo:
|
||||
# monitor = None
|
||||
# external = None
|
||||
# for x in c:
|
||||
# if 'Hardware Class: monitor' in x:
|
||||
# monitor = c
|
||||
# if 'Driver Info' in x:
|
||||
# external = c
|
||||
|
||||
# if monitor and not external:
|
||||
# self.monitors.append(c)
|
||||
|
||||
# def get_size_monitor(self, x):
|
||||
# i = 1 / 25.4
|
||||
# t = x.split('Size: ')[-1].strip()
|
||||
# tt = t.split('mm')
|
||||
# if not tt:
|
||||
# return 0
|
||||
# sizes = tt[0].strip()
|
||||
# if 'x' not in sizes:
|
||||
# return 0
|
||||
# w, h = [int(x) for x in sizes.split('x')]
|
||||
# return numpy.sqrt(w**2 + h**2) * i
|
||||
|
||||
# def get_cpu_address(self, cpu):
|
||||
# default = 64
|
||||
# for ch in self.lshw.get('children', []):
|
||||
# for c in ch.get('children', []):
|
||||
# if c['class'] == 'processor':
|
||||
# return c.get('width', default)
|
||||
# return default
|
||||
|
||||
# def get_usb_num(self):
|
||||
# return len(
|
||||
# [
|
||||
# u
|
||||
# for u in self.dmi.get("Port Connector")
|
||||
# if "USB" in u.get("Port Type", "").upper()
|
||||
# ]
|
||||
# )
|
||||
|
||||
# def get_serial_num(self):
|
||||
# return len(
|
||||
# [
|
||||
# u
|
||||
# for u in self.dmi.get("Port Connector")
|
||||
# if "SERIAL" in u.get("Port Type", "").upper()
|
||||
# ]
|
||||
# )
|
||||
|
||||
# def get_firmware_num(self):
|
||||
# return len(
|
||||
# [
|
||||
# u
|
||||
# for u in self.dmi.get("Port Connector")
|
||||
# if "FIRMWARE" in u.get("Port Type", "").upper()
|
||||
# ]
|
||||
# )
|
||||
|
||||
# def get_pcmcia_num(self):
|
||||
# return len(
|
||||
# [
|
||||
# u
|
||||
# for u in self.dmi.get("Port Connector")
|
||||
# if "PCMCIA" in u.get("Port Type", "").upper()
|
||||
# ]
|
||||
# )
|
||||
|
||||
# def get_bios_date(self):
|
||||
# return self.dmi.get("BIOS")[0].get("Release Date", self.default)
|
||||
|
||||
# def get_firmware(self):
|
||||
# return self.dmi.get("BIOS")[0].get("Firmware Revision", '1')
|
||||
|
||||
# def get_max_ram_size(self):
|
||||
# size = 0
|
||||
# for slot in self.dmi.get("Physical Memory Array"):
|
||||
# capacity = slot.get("Maximum Capacity", '0').split(" ")[0]
|
||||
# size += int(capacity)
|
||||
|
||||
# return size
|
||||
|
||||
# def get_ram_slots(self):
|
||||
# slots = 0
|
||||
# for x in self.dmi.get("Physical Memory Array"):
|
||||
# slots += int(x.get("Number Of Devices", 0))
|
||||
# return slots
|
||||
|
||||
# def get_ram_size(self, ram):
|
||||
# try:
|
||||
# memory = ram.get("Size", "0")
|
||||
# memory = memory.split(' ')
|
||||
# if len(memory) > 1:
|
||||
# size = int(memory[0])
|
||||
# units = memory[1]
|
||||
# return base2.Quantity(size, units).to('MiB').m
|
||||
# return int(size.split(" ")[0])
|
||||
# except Exception as err:
|
||||
# logger.error("get_ram_size error: {}".format(err))
|
||||
# return 0
|
||||
|
||||
# def get_ram_speed(self, ram):
|
||||
# size = ram.get("Speed", "0")
|
||||
# return int(size.split(" ")[0])
|
||||
|
||||
# def get_cpu_speed(self, cpu):
|
||||
# speed = cpu.get('Max Speed', "0")
|
||||
# return float(speed.split(" ")[0]) / 1024
|
||||
|
||||
# def get_sku(self):
|
||||
# return self.dmi.get("System")[0].get("SKU Number", self.default).strip()
|
||||
|
||||
# def get_version(self):
|
||||
# return self.dmi.get("System")[0].get("Version", self.default).strip()
|
||||
|
||||
# def get_uuid(self):
|
||||
# return self.dmi.get("System")[0].get("UUID", '').strip()
|
||||
|
||||
# def get_family(self):
|
||||
# return self.dmi.get("System")[0].get("Family", '')
|
||||
|
||||
# def get_chassis(self):
|
||||
# return self.dmi.get("Chassis")[0].get("Type", '_virtual')
|
||||
|
||||
# def get_type(self):
|
||||
# chassis_type = self.get_chassis()
|
||||
# return self.translation_to_devicehub(chassis_type)
|
||||
|
||||
# def translation_to_devicehub(self, original_type):
|
||||
# lower_type = original_type.lower()
|
||||
# CHASSIS_TYPE = {
|
||||
# 'Desktop': [
|
||||
# 'desktop',
|
||||
# 'low-profile',
|
||||
# 'tower',
|
||||
# 'docking',
|
||||
# 'all-in-one',
|
||||
# 'pizzabox',
|
||||
# 'mini-tower',
|
||||
# 'space-saving',
|
||||
# 'lunchbox',
|
||||
# 'mini',
|
||||
# 'stick',
|
||||
# ],
|
||||
# 'Laptop': [
|
||||
# 'portable',
|
||||
# 'laptop',
|
||||
# 'convertible',
|
||||
# 'tablet',
|
||||
# 'detachable',
|
||||
# 'notebook',
|
||||
# 'handheld',
|
||||
# 'sub-notebook',
|
||||
# ],
|
||||
# 'Server': ['server'],
|
||||
# 'Computer': ['_virtual'],
|
||||
# }
|
||||
# for k, v in CHASSIS_TYPE.items():
|
||||
# if lower_type in v:
|
||||
# return k
|
||||
# return self.default
|
||||
|
||||
# def get_chassis_dh(self):
|
||||
# CHASSIS_DH = {
|
||||
# 'Tower': {'desktop', 'low-profile', 'tower', 'server'},
|
||||
# 'Docking': {'docking'},
|
||||
# 'AllInOne': {'all-in-one'},
|
||||
# 'Microtower': {'mini-tower', 'space-saving', 'mini'},
|
||||
# 'PizzaBox': {'pizzabox'},
|
||||
# 'Lunchbox': {'lunchbox'},
|
||||
# 'Stick': {'stick'},
|
||||
# 'Netbook': {'notebook', 'sub-notebook'},
|
||||
# 'Handheld': {'handheld'},
|
||||
# 'Laptop': {'portable', 'laptop'},
|
||||
# 'Convertible': {'convertible'},
|
||||
# 'Detachable': {'detachable'},
|
||||
# 'Tablet': {'tablet'},
|
||||
# 'Virtual': {'_virtual'},
|
||||
# }
|
||||
|
||||
# chassis = self.get_chassis()
|
||||
# lower_type = chassis.lower()
|
||||
# for k, v in CHASSIS_DH.items():
|
||||
# if lower_type in v:
|
||||
# return k
|
||||
# return self.default
|
||||
|
||||
# def get_data_storage_type(self, x):
|
||||
# # TODO @cayop add more SSDS types
|
||||
# SSDS = ["nvme"]
|
||||
# SSD = 'SolidStateDrive'
|
||||
# HDD = 'HardDrive'
|
||||
# type_dev = x.get('device', {}).get('type')
|
||||
# trim = x.get('trim', {}).get("supported") in [True, "true"]
|
||||
# return SSD if type_dev in SSDS or trim else HDD
|
||||
|
||||
# def get_data_storage_interface(self, x):
|
||||
# interface = x.get('device', {}).get('protocol', 'ATA')
|
||||
# try:
|
||||
# DataStorageInterface(interface.upper())
|
||||
# except ValueError as err:
|
||||
# txt = "Sid: {}, interface {} is not in DataStorageInterface Enum".format(
|
||||
# self.sid, interface
|
||||
# )
|
||||
# self.errors("{}".format(err))
|
||||
# self.errors(txt, severity=Severity.Warning)
|
||||
# return "ATA"
|
||||
|
||||
# def get_data_storage_size(self, x):
|
||||
# total_capacity = x.get('user_capacity', {}).get('bytes')
|
||||
# if not total_capacity:
|
||||
# return 1
|
||||
# # convert bytes to Mb
|
||||
# return total_capacity / 1024**2
|
||||
|
||||
# def parse_hwinfo(self):
|
||||
# hw_blocks = self.hwinfo_raw.split("\n\n")
|
||||
# return [x.split("\n") for x in hw_blocks]
|
||||
|
||||
# def loads(self, x):
|
||||
# if isinstance(x, str):
|
||||
# return json.loads(x)
|
||||
# return x
|
||||
|
||||
# def errors(self, txt=None, severity=Severity.Error):
|
||||
# if not txt:
|
||||
# return self._errors
|
||||
|
||||
# logger.error(txt)
|
||||
# self._errors.append(txt)
|
||||
# error = SnapshotsLog(
|
||||
# description=txt,
|
||||
# snapshot_uuid=self.uuid,
|
||||
# severity=severity,
|
||||
# sid=self.sid,
|
||||
# version=self.version,
|
||||
# )
|
||||
# error.save()
|
||||
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h3>{{ subtitle }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dataTable-container">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" data-sortable="">
|
||||
<a class="dataTable-sorter" href="#">Title</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for snap in snapshots %}
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="checkbox" name="snapshots" value="{{ snap.id }}" />
|
||||
</td>
|
||||
<td>
|
||||
<a href="{# url 'snapshot:details' snap.pk #}">{{ snap.uuid }} {{ snap.sid }} {{ snap.version }}</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'device:details' snap.computer.device.pk %}">{{ snap.computer.device.manufacturer }} {{ snap.computer.device.model }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,18 +0,0 @@
|
|||
# from django.urls import path, include
|
||||
# from rest_framework.routers import DefaultRouter
|
||||
# from snapshot.views import SnapshotViewSet
|
||||
|
||||
# router = DefaultRouter()
|
||||
# router.register(r'snapshots', SnapshotViewSet)
|
||||
|
||||
# urlpatterns = [
|
||||
# path('', include(router.urls)),
|
||||
# ]
|
||||
from django.urls import path
|
||||
from snapshot import views
|
||||
|
||||
app_name = 'snapshot'
|
||||
|
||||
urlpatterns = [
|
||||
path("", views.ListSnapshotsView.as_view(), name="list"),
|
||||
]
|
|
@ -1,27 +0,0 @@
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic.base import TemplateView
|
||||
from dashboard.mixins import DashboardView
|
||||
from snapshot.models import Snapshot
|
||||
# from django.shortcuts import render
|
||||
# from rest_framework import viewsets
|
||||
# from snapshot.serializers import SnapshotSerializer
|
||||
|
||||
|
||||
# class SnapshotViewSet(viewsets.ModelViewSet):
|
||||
# queryset = Snapshot.objects.all()
|
||||
# serializer_class = SnapshotSerializer
|
||||
|
||||
|
||||
class ListSnapshotsView(DashboardView, TemplateView):
|
||||
template_name = "snapshots.html"
|
||||
section = "snapshots"
|
||||
title = _("Snapshots")
|
||||
breadcrumb = "Snapshots"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
snapshots = Snapshot.objects.filter(owner=self.request.user)
|
||||
context.update({
|
||||
'snapshots': snapshots,
|
||||
})
|
||||
return context
|
|
@ -1,5 +1,6 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
from django.contrib.auth import get_user_model
|
||||
from lot.models import LotTag
|
||||
|
||||
|
||||
User = get_user_model()
|
||||
|
@ -16,8 +17,21 @@ class Command(BaseCommand):
|
|||
email = kwargs['email']
|
||||
password = kwargs['password']
|
||||
self.create_user(email, password)
|
||||
self.create_lot_tags()
|
||||
|
||||
def create_user(self, email, password):
|
||||
u = User.objects.create(email=email, password=password)
|
||||
u.set_password(password)
|
||||
u.save()
|
||||
self.u = User.objects.create(email=email, password=password)
|
||||
self.u.set_password(password)
|
||||
self.u.save()
|
||||
|
||||
def create_lot_tags(self):
|
||||
tags = [
|
||||
"Entrada",
|
||||
"Salida",
|
||||
"Temporal"
|
||||
]
|
||||
for tag in tags:
|
||||
LotTag.objects.create(
|
||||
name=tag,
|
||||
owner=self.u
|
||||
)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 5.0.6 on 2024-06-11 09:19
|
||||
# Generated by Django 5.0.6 on 2024-07-17 14:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
|
|
@ -5,3 +5,18 @@ STR_XSM_SIZE = 16
|
|||
STR_SM_SIZE = 32
|
||||
STR_SIZE = 64
|
||||
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
89
utils/device.py
Normal 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
19
utils/forms.py
Normal 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
|
Loading…
Reference in a new issue