Compare commits
176 commits
main
...
feature/f3
Author | SHA1 | Date | |
---|---|---|---|
|
d4f971bfa3 | ||
|
fcd6e13bf9 | ||
|
e692417c73 | ||
|
9600a6064f | ||
|
b83afa6f12 | ||
|
c6f63d4d44 | ||
|
b2bf894338 | ||
|
fdc375f804 | ||
|
f26935b617 | ||
|
01bb822aa5 | ||
|
5c7fc8fd47 | ||
|
a7c19ac93e | ||
|
de1f090694 | ||
|
a876acc814 | ||
|
a8884ea012 | ||
|
4b1fb26c67 | ||
|
03bdd4818b | ||
|
1f93c88bc8 | ||
|
573603a6ea | ||
|
3d49db9436 | ||
|
d4d0a35e4a | ||
|
d8dc37ba94 | ||
|
8e29ef4bf5 | ||
|
62006eb4e3 | ||
|
e42f2c3ea3 | ||
|
911388718d | ||
|
3d744e7945 | ||
|
277a7606e2 | ||
|
fe1d020618 | ||
|
c8ddec6942 | ||
|
886cf20565 | ||
|
871ed179fb | ||
|
eb796de4d3 | ||
|
4b9bcb054e | ||
|
55e018ad51 | ||
|
782fc4a541 | ||
|
85011076e8 | ||
|
0fc50d7187 | ||
|
746a692118 | ||
|
a3613732a9 | ||
|
711fb9e171 | ||
|
d44310bcfd | ||
|
60c618ce09 | ||
|
4e69062452 | ||
|
490144fd86 | ||
|
e6995e74e0 | ||
|
1fe20e11b8 | ||
|
1267f388c1 | ||
|
369dc83154 | ||
|
3d0527edf1 | ||
|
0bbc3475c2 | ||
|
13a74b133d | ||
|
4f38cdf5b1 | ||
|
6bb31c40ff | ||
|
6fd9792d78 | ||
|
c27b2c2263 | ||
|
97c74ca9bb | ||
|
51cbd2bf62 | ||
|
93a70ed031 | ||
|
83885ceb84 | ||
|
79df0100b1 | ||
|
ccdd292a97 | ||
|
518866bbc9 | ||
|
4533395e3b | ||
|
1a30fa75dc | ||
|
9a3a5fe638 | ||
|
d085d448a9 | ||
|
0974e074d2 | ||
|
b707d78595 | ||
|
6c35210d4e | ||
|
d614fd4756 | ||
|
cd93e6dafa | ||
|
a81172ad8e | ||
|
73a72a7d15 | ||
|
bdf029abbd | ||
|
893bf0c14d | ||
|
96b0a0ad80 | ||
|
eeea0b3879 | ||
|
c30416d038 | ||
|
1757f70963 | ||
|
d7bd47554a | ||
|
ba2ddecc11 | ||
|
59bdab7776 | ||
|
5b90d7a648 | ||
|
9dc7a66ffd | ||
|
01dbe005e4 | ||
|
448287248e | ||
|
e6223420d2 | ||
|
6ee0184f66 | ||
|
8f206340f3 | ||
|
324eaa215c | ||
|
0da3e15a03 | ||
|
bd4f6b7d56 | ||
|
f9c9c9dd7c | ||
|
60ccbec369 | ||
|
3fb0961815 | ||
|
447946a576 | ||
|
5d190d07a3 | ||
|
d1abb206e8 | ||
|
85bae67189 | ||
|
d429485651 | ||
|
07c25f4a92 | ||
|
14277c17cb | ||
|
f7051c3130 | ||
|
09be1a2f74 | ||
|
a3dd5d9639 | ||
|
3f5460b81f | ||
|
bf7975bc24 | ||
|
8e128557c0 | ||
|
25e7e85548 | ||
|
ba126491be | ||
|
81e7ba267d | ||
|
1e08f0fc0c | ||
|
ebabb6b228 | ||
|
4954199610 | ||
|
e84b72c70b | ||
|
99435fff85 | ||
|
6c0e77891f | ||
|
a2d859494b | ||
|
ea6d990e56 | ||
|
612737d46c | ||
|
30be57ee25 | ||
|
88bdabb64f | ||
|
96268c8caf | ||
|
7ed05f0932 | ||
|
b652d7d452 | ||
|
04ecb4f2f1 | ||
|
1613eaaa44 | ||
|
06264558df | ||
|
80b4c3b4ca | ||
|
e2078c7bde | ||
|
cfae9d4ec9 | ||
|
578fa73fe5 | ||
|
f1d57ff618 | ||
|
3cf8ceb5d3 | ||
|
b56dc0dfda | ||
|
1c58bff515 | ||
|
e6c1ede93c | ||
|
371845971c | ||
|
b4efcfb171 | ||
|
ac0d36ea6f | ||
|
6a3a2b3a2b | ||
|
850678fbe4 | ||
|
f43aaf6ac6 | ||
|
355ed08561 | ||
|
d0e46aa0b0 | ||
|
771b216a31 | ||
|
263eacda99 | ||
|
8fcd20f609 | ||
|
15fb5d3739 | ||
|
d7ff3c2798 | ||
|
0e0ad400c2 | ||
|
367d3a7f87 | ||
|
c90ed58ea0 | ||
|
45629db102 | ||
|
1e29f9562d | ||
|
d0cac9d1d9 | ||
|
8b4d1f51f6 | ||
|
34ea4bedfc | ||
|
fe429e7db6 | ||
|
caf2606fd9 | ||
|
73d478f517 | ||
|
0f03171076 | ||
|
bfdcb33538 | ||
|
271ac83d71 | ||
|
f7b2687ca2 | ||
|
1dad22c3d3 | ||
|
7de6d69a6c | ||
|
fa5b9eec67 | ||
|
7fd42db3e4 | ||
|
bed40d3ee0 | ||
|
9553ed6a4c | ||
|
f3c9297ffd | ||
|
cb6c7f6fda | ||
|
a0276f439e | ||
|
a4d361ff9b |
|
@ -13,6 +13,7 @@ DEVICEHUB_PORT=8001
|
|||
DEMO=true
|
||||
# note that with DEBUG=true, logs are more verbose (include tracebacks)
|
||||
DEBUG=true
|
||||
ALLOWED_HOSTS=localhost,localhost:8000,127.0.0.1,
|
||||
DPP=false
|
||||
|
||||
STATIC_ROOT=/tmp/static/
|
||||
|
|
13
api/views.py
13
api/views.py
|
@ -90,15 +90,15 @@ class NewSnapshotView(ApiMixing):
|
|||
ev_uuid = data["credentialSubject"].get("uuid")
|
||||
|
||||
if not ev_uuid:
|
||||
txt = "error: the snapshot does not have an uuid"
|
||||
txt = "error: the snapshot not have uuid"
|
||||
logger.error("%s", txt)
|
||||
return JsonResponse({'status': txt}, status=500)
|
||||
|
||||
exist_property = SystemProperty.objects.filter(
|
||||
exist_annotation = Annotation.objects.filter(
|
||||
uuid=ev_uuid
|
||||
).first()
|
||||
|
||||
if exist_property:
|
||||
if exist_annotation:
|
||||
txt = "error: the snapshot {} exist".format(ev_uuid)
|
||||
logger.warning("%s", txt)
|
||||
return JsonResponse({'status': txt}, status=500)
|
||||
|
@ -115,16 +115,17 @@ class NewSnapshotView(ApiMixing):
|
|||
text = "fail: It is not possible to parse snapshot"
|
||||
return JsonResponse({'status': text}, status=500)
|
||||
|
||||
prop = SystemProperty.objects.filter(
|
||||
annotation = Annotation.objects.filter(
|
||||
uuid=ev_uuid,
|
||||
type=Annotation.Type.SYSTEM,
|
||||
# TODO this is hardcoded, it should select the user preferred algorithm
|
||||
key="ereuse24",
|
||||
owner=self.tk.owner.institution
|
||||
).first()
|
||||
|
||||
|
||||
if not prop:
|
||||
logger.error("Error: No property for uuid: %s", ev_uuid)
|
||||
if not annotation:
|
||||
logger.error("Error: No annotation for uuid: %s", ev_uuid)
|
||||
return JsonResponse({'status': 'fail'}, status=500)
|
||||
|
||||
url_args = reverse_lazy("device:details", args=(prop.value,))
|
||||
|
|
|
@ -75,9 +75,6 @@
|
|||
{{ dev.model }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ dev.updated }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
{% endfor %}
|
||||
|
|
|
@ -119,14 +119,13 @@ class SearchView(InventaryMixin):
|
|||
# TODO fix of pagination, the count is not correct
|
||||
return devices, count
|
||||
|
||||
def get_properties(self, xp):
|
||||
def get_annotations(self, xp):
|
||||
snap = json.loads(xp.document.get_data())
|
||||
if snap.get("credentialSubject"):
|
||||
uuid = snap["credentialSubject"]["uuid"]
|
||||
else:
|
||||
uuid = snap["uuid"]
|
||||
|
||||
return Device.get_properties_from_uuid(uuid, self.request.user.institution)
|
||||
return Device.get_annotation_from_uuid(uuid, self.request.user.institution)
|
||||
|
||||
def search_hids(self, query, offset, limit):
|
||||
qry = Q()
|
||||
|
|
|
@ -99,12 +99,12 @@ class Device:
|
|||
self.last_evidence = Evidence(self.uuid)
|
||||
return
|
||||
|
||||
properties = self.get_properties()
|
||||
if not properties.count():
|
||||
annotations = self.get_annotations()
|
||||
if not annotations.count():
|
||||
return
|
||||
prop = properties.first()
|
||||
|
||||
self.last_evidence = Evidence(prop.uuid)
|
||||
annotation = annotations.first()
|
||||
self.last_evidence = Evidence(annotation.uuid)
|
||||
self.uuid = annotation.uuid
|
||||
|
||||
def is_eraseserver(self):
|
||||
if not self.uuids:
|
||||
|
@ -392,7 +392,8 @@ class Device:
|
|||
|
||||
@property
|
||||
def version(self):
|
||||
self.get_last_evidence()
|
||||
if not self.last_evidence:
|
||||
self.get_last_evidence()
|
||||
return self.last_evidence.get_version()
|
||||
|
||||
@property
|
||||
|
|
|
@ -75,16 +75,22 @@
|
|||
<li class="nav-item">
|
||||
<a href="#evidences" class="nav-link" data-bs-toggle="tab" data-bs-target="#evidences">{% trans 'Evidences' %}</a>
|
||||
</li>
|
||||
{% if dpps %}
|
||||
<li class="nav-item">
|
||||
<a href="#dpps" class="nav-link" data-bs-toggle="tab" data-bs-target="#dpps">{% trans 'Dpps' %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if dpps %}
|
||||
<li class="nav-item">
|
||||
<a href="#dpps" class="nav-link" data-bs-toggle="tab" data-bs-target="#dpps">{% trans 'Dpps' %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'device:device_web' object.id %}" target="_blank">Web</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#log" class="nav-link" data-bs-toggle="tab" data-bs-target="#log">{% trans 'Log' %}</a>
|
||||
<a href="#environmental_impact" class="nav-link" data-bs-toggle="tab" data-bs-target="#environmental_impact">{% trans 'Environmental impact' %}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#environmental_impact" class="nav-link" data-bs-toggle="tab" data-bs-target="#environmental_impact">{% trans 'Environmental Impact' %}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#environmental_impact" class="nav-link" data-bs-toggle="tab" data-bs-target="#environmental_impact">{% trans 'Environmental Impact' %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -105,6 +111,8 @@
|
|||
|
||||
{% include 'tabs/dpps.html' %}
|
||||
|
||||
{% include 'tabs/environmental_impact.html' %}
|
||||
|
||||
<!-- Add a note popup -->
|
||||
<div class="modal fade" id="addNoteModal" tabindex="-1" aria-labelledby="addNoteModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
|
@ -113,22 +121,156 @@
|
|||
<h5 class="modal-title" id="addNoteModalLabel">{% trans "Add a Note" %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% trans 'Close' %}"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="post" action="{% url 'action:add_note' %}">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<input type="hidden" name="snapshot_uuid" value="{{ object.last_uuid }}">
|
||||
<label for="noteDescription" class="form-label">{% trans "Note" %}</label>
|
||||
<textarea class="form-control" id="noteDescription" name="note" placeholder="Max 250 characters" name="note" rows="3" required></textarea>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
||||
<button type="submit" class="btn btn-green-admin">{% trans "Save Note" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<div class="row mb-1">
|
||||
<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 and object.last_user_evidence %}
|
||||
{% for k, v in object.last_user_evidence %}
|
||||
<div class="row mb-1">
|
||||
<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 mb-1">
|
||||
<div class="col-lg-3 col-md-4 label">
|
||||
{% trans 'Manufacturer' %}
|
||||
</div>
|
||||
<div class="col-lg-9 col-md-8">{{ object.manufacturer|default:'' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1">
|
||||
<div class="col-lg-3 col-md-4 label">
|
||||
{% trans 'Model' %}
|
||||
</div>
|
||||
<div class="col-lg-9 col-md-8">{{ object.model|default:'' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1">
|
||||
<div class="col-lg-3 col-md-4 label">
|
||||
{% trans 'Version' %}
|
||||
</div>
|
||||
<div class="col-lg-9 col-md-8">{{ object.version|default:'' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-1">
|
||||
<div class="col-lg-3 col-md-4 label">
|
||||
{% trans 'Serial Number' %}
|
||||
</div>
|
||||
<div class="col-lg-9 col-md-8">{{ object.serial_number|default:'' }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-lg-3 col-md-4 label">
|
||||
{% trans 'Identifiers' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="environmental_impact">
|
||||
<div class="container-fluid py-3">
|
||||
<div class="d-flex justify-content-end mb-3">
|
||||
<a class="btn btn-success">
|
||||
<i class="bi bi-file-earmark-pdf"></i>
|
||||
{% trans 'Export to PDF' %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-md-4">
|
||||
<div class="card h-100 border-success">
|
||||
<div class="card-body text-center">
|
||||
<div class="mb-3">
|
||||
<i class="bi bi-arrow-down-circle text-success" style="font-size: 2rem;"></i>
|
||||
</div>
|
||||
<h5 class="card-title text-success">Carbon Reduction</h5>
|
||||
<h2 class="mb-2">{{ impact.carbon_saved }}</h2>
|
||||
<p class="card-text text-muted">kg CO₂e saved</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card h-100 border-danger">
|
||||
<div class="card-body text-center">
|
||||
<div class="mb-3">
|
||||
<i class="bi bi-cloud-fill text-danger" style="font-size: 2rem;"></i>
|
||||
</div>
|
||||
<h5 class="card-title text-danger">Carbon Consumed</h5>
|
||||
<h2 class="mb-2">{{ impact.co2_emissions }}</h2>
|
||||
<p class="card-text text-muted">kg CO₂e consumed</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card h-100 border-success">
|
||||
<div class="card-body text-center">
|
||||
<div class="mb-3">
|
||||
<i class="bi bi-recycle text-success" style="font-size: 2rem;"></i>
|
||||
</div>
|
||||
<h5 class="card-title text-success">Additional Impact Metric</h5>
|
||||
<h2 class="mb-2">85%</h2>
|
||||
<p class="card-text text-muted">whatever</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Impact Details</h5>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row" class="bg-light" style="width: 30%;">Manufacturing Impact Avoided</th>
|
||||
<td>
|
||||
<span class="text-success">{{ impact.carbon_saved }}</span> kg CO₂e
|
||||
<br />
|
||||
<small class="text-muted">Based on average laptop manufacturing emissions</small>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<h6>Calculation Method</h6>
|
||||
<small class="text-muted">Based on industry standards X Y and Z</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if dpps %}
|
||||
<div class="tab-pane fade" id="dpps">
|
||||
<h5 class="card-title">{% trans 'List of dpps' %}</h5>
|
||||
<div class="list-group col">
|
||||
{% for d in dpps %}
|
||||
<div class="list-group-item">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<small class="text-muted">{{ d.timestamp }}</small>
|
||||
<span>{{ d.type }}</span>
|
||||
</div>
|
||||
<p class="mb-1">
|
||||
<a href="{% url 'did:device_web' d.signature %}">{{ d.signature }}</a>
|
||||
</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
45
device/templates/tabs/environmental_impact.html
Normal file
45
device/templates/tabs/environmental_impact.html
Normal file
|
@ -0,0 +1,45 @@
|
|||
{% load i18n %}
|
||||
|
||||
<div class="tab-pane fade" id="environmental_impact">
|
||||
<h5 class="card-title">{% trans 'Environmental Impact Details' %}</h5>
|
||||
<hr />
|
||||
|
||||
<h6 class="mt-3 text-primary">{% trans 'While device is being used' %}</h6>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-4 text-muted fw-bold">
|
||||
{% trans 'CO2 Emissions' %}
|
||||
</div>
|
||||
<div class="col-sm-8">{{ impact.co2_emissions|default:'0.0' }} kg</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-12 d-flex justify-content-end">
|
||||
<div class="border p-2 rounded d-flex align-items-center">
|
||||
<label for="algorithmSelect" class="text-muted fw-bold me-2">{% trans 'Algorithm Selector' %}</label>
|
||||
<select class="form-select form-select-sm w-auto border-0 shadow-none" id="algorithmSelect" onchange="changeAlgorithm()">
|
||||
<option value="dummy" selected>{% trans 'Dummy Algorithm' %}</option>
|
||||
<option value="advanced">{% trans 'Advanced Algorithm' %}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<button class="btn btn-outline-primary" type="button" data-bs-toggle="collapse" data-bs-target="#docsCollapse" aria-expanded="false" aria-controls="docsCollapse">
|
||||
{% trans 'Read about the algorithm insights' %}
|
||||
</button>
|
||||
|
||||
<div class="collapse mt-3" id="docsCollapse">
|
||||
<div class="card card-body">
|
||||
<div class="markdown-content">{{ impact.docs|safe }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function changeAlgorithm() {
|
||||
var selectedAlgorithm = document.getElementById('algorithmSelect').value;
|
||||
}
|
||||
</script>
|
|
@ -7,11 +7,7 @@ urlpatterns = [
|
|||
path("add/", views.NewDeviceView.as_view(), name="add"),
|
||||
path("edit/<str:pk>/", views.EditDeviceView.as_view(), name="edit"),
|
||||
path("<str:pk>/", views.DetailsView.as_view(), name="details"),
|
||||
path("<str:pk>/user_property/add",
|
||||
views.AddUserPropertyView.as_view(), name="add_user_property"),
|
||||
path("<str:device_id>/user_property/<int:pk>/delete",
|
||||
views.DeleteUserPropertyView.as_view(), name="delete_user_property"),
|
||||
path("<str:device_id>/user_property/<int:pk>/update",
|
||||
views.UpdateUserPropertyView.as_view(), name="update_user_property"),
|
||||
path("<str:pk>/public/", views.PublicDeviceWebView.as_view(), name="device_web"),
|
||||
path("<str:pk>/annotation/add", views.AddAnnotationView.as_view(), name="add_annotation"),
|
||||
path("<str:pk>/document/add", views.AddDocumentView.as_view(), name="add_document"),
|
||||
path("<str:pk>/public/", views.PublicDeviceWebView.as_view(), name="device_web")
|
||||
]
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
import json
|
||||
import logging
|
||||
|
||||
from django.http import JsonResponse
|
||||
from django.conf import settings
|
||||
from django.db import IntegrityError
|
||||
from django.urls import reverse_lazy
|
||||
from django.contrib import messages
|
||||
from django.shortcuts import get_object_or_404, redirect, Http404
|
||||
|
@ -21,6 +17,7 @@ from evidence.models import UserProperty, SystemProperty
|
|||
from lot.models import LotTag
|
||||
from device.models import Device
|
||||
from device.forms import DeviceFormSet
|
||||
from environmental_impact.algorithms.algorithm_factory import FactoryEnvironmentImpactAlgorithm
|
||||
if settings.DPP:
|
||||
from dpp.models import Proof
|
||||
from dpp.api_dlt import PROOF_TYPE
|
||||
|
@ -36,6 +33,7 @@ class DeviceLogMixin(DashboardView):
|
|||
institution=self.request.user.institution
|
||||
)
|
||||
|
||||
|
||||
class NewDeviceView(DashboardView, FormView):
|
||||
template_name = "new_device.html"
|
||||
title = _("New Device")
|
||||
|
@ -94,32 +92,36 @@ class DetailsView(DashboardView, TemplateView):
|
|||
lot_tags = LotTag.objects.filter(owner=self.request.user.institution)
|
||||
dpps = []
|
||||
if settings.DPP:
|
||||
_dpps = Proof.objects.filter(
|
||||
dpps = Proof.objects.filter(
|
||||
uuid__in=self.object.uuids,
|
||||
type=PROOF_TYPE["IssueDPP"]
|
||||
)
|
||||
for x in _dpps:
|
||||
dpp = "{}:{}".format(self.pk, x.signature)
|
||||
dpps.append((dpp, x.signature[:10], x))
|
||||
|
||||
# TODO Specify algorithm via dropdown, if not specified, use default.
|
||||
enviromental_impact_algorithm = FactoryEnvironmentImpactAlgorithm.run_environmental_impact_calculation(
|
||||
"dummy_calc"
|
||||
)
|
||||
enviromental_impact = enviromental_impact_algorithm.get_device_environmental_impact(
|
||||
self.object)
|
||||
last_evidence = self.object.get_last_evidence()
|
||||
uuids = self.object.uuids
|
||||
state_definitions = StateDefinition.objects.filter(
|
||||
institution=self.request.user.institution
|
||||
).order_by('order')
|
||||
device_states = State.objects.filter(snapshot_uuid__in=uuids).order_by('-date')
|
||||
device_states = State.objects.filter(
|
||||
snapshot_uuid__in=uuids).order_by('-date')
|
||||
device_logs = DeviceLog.objects.filter(
|
||||
snapshot_uuid__in=uuids).order_by('-date')
|
||||
device_notes = Note.objects.filter(snapshot_uuid__in=uuids).order_by('-date')
|
||||
device_notes = Note.objects.filter(
|
||||
snapshot_uuid__in=uuids).order_by('-date')
|
||||
context.update({
|
||||
'object': self.object,
|
||||
'snapshot': last_evidence,
|
||||
'lot_tags': lot_tags,
|
||||
'impact': enviromental_impact,
|
||||
'dpps': dpps,
|
||||
"state_definitions": state_definitions,
|
||||
"device_states": device_states,
|
||||
"device_logs": device_logs,
|
||||
"device_notes": device_notes,
|
||||
})
|
||||
return context
|
||||
|
||||
|
@ -180,11 +182,12 @@ class PublicDeviceWebView(TemplateView):
|
|||
return JsonResponse(device_data)
|
||||
|
||||
|
||||
class AddUserPropertyView(DeviceLogMixin, CreateView):
|
||||
template_name = "new_user_property.html"
|
||||
title = _("New User Property")
|
||||
breadcrumb = "Device / New Property"
|
||||
model = UserProperty
|
||||
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):
|
||||
|
@ -277,7 +280,7 @@ class DeleteUserPropertyView(DeviceLogMixin, DeleteView):
|
|||
def get_queryset(self):
|
||||
return UserProperty.objects.filter(owner=self.request.user.institution)
|
||||
|
||||
#using post() method because delete() method from DeleteView has some issues
|
||||
# using post() method because delete() method from DeleteView has some issues
|
||||
# with messages framework
|
||||
def post(self, request, *args, **kwargs):
|
||||
pk = self.kwargs.get('pk')
|
||||
|
|
|
@ -87,6 +87,7 @@ INSTALLED_APPS = [
|
|||
"action",
|
||||
"admin",
|
||||
"api",
|
||||
"environmental_impact"
|
||||
]
|
||||
|
||||
DPP = config("DPP", default=False, cast=bool)
|
||||
|
|
16
did/views.py
16
did/views.py
|
@ -106,10 +106,10 @@ class PublicDeviceWebView(TemplateView):
|
|||
'device': {},
|
||||
}
|
||||
dev = Build(self.object.last_evidence.doc, None, check=True)
|
||||
doc = dev.build.get_doc()
|
||||
doc = dev.get_phid()
|
||||
data['document'] = json.dumps(doc)
|
||||
data['device'] = dev.build.device
|
||||
data['components'] = dev.build.components
|
||||
data['device'] = dev.device
|
||||
data['components'] = dev.components
|
||||
|
||||
self.object.get_evidences()
|
||||
last_dpp = Proof.objects.filter(
|
||||
|
@ -118,7 +118,7 @@ class PublicDeviceWebView(TemplateView):
|
|||
|
||||
key = self.pk
|
||||
if last_dpp:
|
||||
key += ":"+last_dpp.signature
|
||||
key = last_dpp.signature
|
||||
|
||||
url = "https://{}/did/{}".format(
|
||||
self.request.get_host(),
|
||||
|
@ -135,17 +135,17 @@ class PublicDeviceWebView(TemplateView):
|
|||
for d in self.object.evidences:
|
||||
d.get_doc()
|
||||
dev = Build(d.doc, None, check=True)
|
||||
doc = dev.build.get_doc()
|
||||
doc = dev.get_phid()
|
||||
ev = json.dumps(doc)
|
||||
phid = dev.sign(ev)
|
||||
phid = dev.get_signature(doc)
|
||||
dpp = "{}:{}".format(self.pk, phid)
|
||||
rr = {
|
||||
'dpp': dpp,
|
||||
'document': ev,
|
||||
'algorithm': ALGORITHM,
|
||||
'manufacturer DPP': '',
|
||||
'device': dev.build.device,
|
||||
'components': dev.build.components
|
||||
'device': dev.device,
|
||||
'components': dev.components
|
||||
}
|
||||
|
||||
tmpl = dpp_tmpl.copy()
|
||||
|
|
|
@ -15,7 +15,6 @@ services:
|
|||
- DEMO_IDHUB_PREDEFINED_TOKEN=${IDHUB_PREDEFINED_TOKEN:-}
|
||||
- PREDEFINED_TOKEN=${PREDEFINED_TOKEN:-}
|
||||
- DPP=${DPP:-false}
|
||||
# TODO manage volumes dev vs prod
|
||||
volumes:
|
||||
- .:/opt/devicehub-django
|
||||
ports:
|
||||
|
|
|
@ -28,8 +28,6 @@ main() {
|
|||
fi
|
||||
# remove old database
|
||||
rm -vfr ./db/*
|
||||
# deactivate configured flag
|
||||
rm -vfr ./already_configured
|
||||
docker compose down -v
|
||||
if [ "${DEV_DOCKER_ALWAYS_BUILD:-}" = 'true' ]; then
|
||||
docker compose pull --ignore-buildable
|
||||
|
|
|
@ -42,7 +42,21 @@ gen_env_vars() {
|
|||
export API_RESOLVER='http://id_index_api:3012'
|
||||
# TODO hardcoded
|
||||
export ID_FEDERATED='DH1'
|
||||
# propagate to .env
|
||||
dpp_env_vars="$(cat <<END
|
||||
API_DLT=${API_DLT}
|
||||
API_DLT_TOKEN=${API_DLT_TOKEN}
|
||||
API_RESOLVER=${API_RESOLVER}
|
||||
ID_FEDERATED=${ID_FEDERATED}
|
||||
END
|
||||
)"
|
||||
fi
|
||||
|
||||
# generate config using env vars from docker
|
||||
# TODO rethink if this is needed because now this is django, not flask
|
||||
cat > .env <<END
|
||||
${dpp_env_vars:-}
|
||||
END
|
||||
}
|
||||
|
||||
handle_federated_id() {
|
||||
|
@ -105,54 +119,8 @@ END
|
|||
./manage.py dlt_register_user "${DATASET_FILE}"
|
||||
}
|
||||
|
||||
# wait until idhub api is prepared to received requests
|
||||
wait_idhub() {
|
||||
echo "Start waiting idhub API"
|
||||
while true; do
|
||||
result="$(curl -s "${url}" \
|
||||
| jq -r .error \
|
||||
|| echo "Reported errors, idhub API is still not ready")"
|
||||
|
||||
if [ "${result}" = "Invalid request method" ]; then
|
||||
break
|
||||
sleep 2
|
||||
else
|
||||
echo "Waiting idhub API"
|
||||
sleep 3
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
demo__send_to_sign_credential() {
|
||||
filepath="${1}"
|
||||
# hashlib.sha3_256 of PREDEFINED_TOKEN for idhub
|
||||
DEMO_IDHUB_PREDEFINED_TOKEN="${DEMO_IDHUB_PREDEFINED_TOKEN:-}"
|
||||
auth_header="Authorization: Bearer ${DEMO_IDHUB_PREDEFINED_TOKEN}"
|
||||
json_header='Content-Type: application/json'
|
||||
curl -s -X POST \
|
||||
-H "${json_header}" \
|
||||
-H "${auth_header}" \
|
||||
-d @"${filepath}" \
|
||||
"${url}" \
|
||||
| jq -r .data
|
||||
}
|
||||
|
||||
run_demo() {
|
||||
if [ "${DEMO_IDHUB_DOMAIN:-}" ]; then
|
||||
DEMO_IDHUB_DOMAIN="${DEMO_IDHUB_DOMAIN:-}"
|
||||
# this demo only works with FQDN domain (with no ports)
|
||||
url="https://${DEMO_IDHUB_DOMAIN}/webhook/sign/"
|
||||
wait_idhub
|
||||
demo__send_to_sign_credential \
|
||||
'example/demo-snapshots-vc/snapshot_pre-verifiable-credential.json' \
|
||||
> 'example/snapshots/snapshot_workbench-script_verifiable-credential.json'
|
||||
fi
|
||||
./manage.py create_default_states "${INIT_ORG}"
|
||||
/usr/bin/time ./manage.py up_snapshots example/snapshots/ "${INIT_USER}"
|
||||
}
|
||||
|
||||
config_phase() {
|
||||
# TODO review this flag file
|
||||
# TODO review this flag file
|
||||
init_flagfile="${program_dir}/already_configured"
|
||||
if [ ! -f "${init_flagfile}" ]; then
|
||||
|
||||
|
@ -165,7 +133,7 @@ config_phase() {
|
|||
# 12, 13, 14
|
||||
config_dpp_part1
|
||||
|
||||
# cleanup other snapshots and copy dlt/dpp snapshots
|
||||
# cleanup other spnapshots and copy dlt/dpp snapshots
|
||||
# TODO make this better
|
||||
rm example/snapshots/*
|
||||
cp example/dpp-snapshots/*.json example/snapshots/
|
||||
|
@ -173,7 +141,7 @@ config_phase() {
|
|||
|
||||
# # 15. Add inventory snapshots for user "${INIT_USER}".
|
||||
if [ "${DEMO:-}" = 'true' ]; then
|
||||
run_demo
|
||||
/usr/bin/time ./manage.py up_snapshots example/snapshots/ "${INIT_USER}"
|
||||
fi
|
||||
|
||||
# remain next command as the last operation for this if conditional
|
||||
|
@ -188,11 +156,9 @@ check_app_is_there() {
|
|||
}
|
||||
|
||||
deploy() {
|
||||
if [ -d /opt/devicehub-django/.git ]; then
|
||||
# TODO this is weird, find better workaround
|
||||
git config --global --add safe.directory "${program_dir}"
|
||||
export COMMIT=$(git log --format="%H %ad" --date=iso -n 1)
|
||||
fi
|
||||
# TODO this is weird, find better workaround
|
||||
git config --global --add safe.directory "${program_dir}"
|
||||
export COMMIT=$(git log --format="%H %ad" --date=iso -n 1)
|
||||
|
||||
if [ "${DEBUG:-}" = 'true' ]; then
|
||||
./manage.py print_settings
|
||||
|
@ -208,9 +174,6 @@ deploy() {
|
|||
# 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"
|
||||
if [ ! -d "${program_dir}/db/" ]; then
|
||||
mkdir -p "${program_dir}/db/"
|
||||
fi
|
||||
./manage.py migrate
|
||||
config_phase
|
||||
fi
|
||||
|
|
|
@ -12,7 +12,7 @@ from dpp.models import Proof
|
|||
|
||||
|
||||
class ProofView(View):
|
||||
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
timestamp = kwargs.get("proof_id")
|
||||
proof = Proof.objects.filter(timestamp=timestamp).first()
|
||||
|
@ -22,9 +22,9 @@ class ProofView(View):
|
|||
ev = Evidence(proof.uuid)
|
||||
if not ev.doc:
|
||||
return JsonResponse({}, status=404)
|
||||
|
||||
|
||||
dev = Build(ev.doc, None, check=True)
|
||||
doc = dev.build.get_doc()
|
||||
doc = dev.get_phid()
|
||||
|
||||
data = {
|
||||
"algorithm": ALGORITHM,
|
||||
|
|
0
environmental_impact/__init__.py
Normal file
0
environmental_impact/__init__.py
Normal file
3
environmental_impact/admin.py
Normal file
3
environmental_impact/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
0
environmental_impact/algorithms/__init__.py
Normal file
0
environmental_impact/algorithms/__init__.py
Normal file
30
environmental_impact/algorithms/algorithm_factory.py
Normal file
30
environmental_impact/algorithms/algorithm_factory.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .dummy_algo.dummy_calculator import DummyEnvironmentalImpactAlgorithm
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .algorithm_interface import EnvironmentImpactAlgorithm
|
||||
|
||||
|
||||
class AlgorithmNames():
|
||||
"""
|
||||
Enum class for the different types of algorithms.
|
||||
"""
|
||||
|
||||
DUMMY_CALC = "dummy_calc"
|
||||
|
||||
algorithm_names = {
|
||||
DUMMY_CALC: DummyEnvironmentalImpactAlgorithm()
|
||||
}
|
||||
|
||||
|
||||
class FactoryEnvironmentImpactAlgorithm():
|
||||
|
||||
@staticmethod
|
||||
def run_environmental_impact_calculation(algorithm_name: str) -> EnvironmentImpactAlgorithm:
|
||||
try:
|
||||
return AlgorithmNames.algorithm_names[algorithm_name]
|
||||
except KeyError:
|
||||
raise ValueError("Invalid algorithm name. Valid options are: " +
|
||||
", ".join(AlgorithmNames.algorithm_names.keys()))
|
11
environmental_impact/algorithms/algorithm_interface.py
Normal file
11
environmental_impact/algorithms/algorithm_interface.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from functools import lru_cache
|
||||
from device.models import Device
|
||||
from environmental_impact.models import EnvironmentalImpact
|
||||
|
||||
|
||||
class EnvironmentImpactAlgorithm(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def get_device_environmental_impact(self, device: Device) -> EnvironmentalImpact:
|
||||
pass
|
8
environmental_impact/algorithms/docs_renderer.py
Normal file
8
environmental_impact/algorithms/docs_renderer.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
import markdown
|
||||
|
||||
|
||||
def render_docs(file_path):
|
||||
with open(file_path, 'r') as file:
|
||||
markdown_content = file.read()
|
||||
html_content = markdown.markdown(markdown_content)
|
||||
return html_content
|
24
environmental_impact/algorithms/dummy_algo/docs.md
Normal file
24
environmental_impact/algorithms/dummy_algo/docs.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
## _Dummy_ Algorithm Docs
|
||||
|
||||
This function calculates the **carbon footprint** of a device based on its power consumption and usage time.
|
||||
|
||||
### 1. Define Constants
|
||||
- `avg_watts = 40`: Assumed average power consumption of the device in watts.
|
||||
- `co2_per_kwh = 0.475`: CO₂ emissions per kilowatt-hour (kg CO₂/kWh), based on an estimated energy mix.
|
||||
|
||||
### 2. Retrieve Device Usage
|
||||
- Calls `get_power_on_hours_from(device)`, which returns the total **power-on hours** for the device.
|
||||
|
||||
### 3. Compute Energy Consumption
|
||||
- Converts power consumption to **kilowatt-hours (kWh)** using:
|
||||
```
|
||||
energy_kwh = (power_on_hours * avg_watts) / 1000
|
||||
```
|
||||
- This accounts for the total energy used over the recorded operational period.
|
||||
|
||||
### 4. Calculate CO₂ Emissions
|
||||
- Multiplies the **energy consumption (kWh)** by the **CO₂ emission factor**:
|
||||
```
|
||||
co2_emissions = energy_kwh * co2_per_kwh
|
||||
```
|
||||
- This provides the estimated **CO₂ emissions in kilograms**.
|
|
@ -0,0 +1,40 @@
|
|||
import os
|
||||
from device.models import Device
|
||||
from ..algorithm_interface import EnvironmentImpactAlgorithm
|
||||
from environmental_impact.models import EnvironmentalImpact
|
||||
from ..docs_renderer import render_docs
|
||||
|
||||
|
||||
class DummyEnvironmentalImpactAlgorithm(EnvironmentImpactAlgorithm):
|
||||
|
||||
def get_device_environmental_impact(self, device: Device) -> EnvironmentalImpact:
|
||||
# TODO Make a constants file / class
|
||||
avg_watts = 40 # Arbitrary laptop average consumption
|
||||
co2_per_kwh = 0.475
|
||||
power_on_hours = self.get_power_on_hours_from(device)
|
||||
|
||||
energy_kwh = (power_on_hours * avg_watts) / 1000
|
||||
co2_emissions = energy_kwh * co2_per_kwh
|
||||
current_dir = os.path.dirname(__file__)
|
||||
docs_path = os.path.join(current_dir, 'docs.md')
|
||||
docs = render_docs(docs_path)
|
||||
return EnvironmentalImpact(co2_emissions=co2_emissions, docs=docs)
|
||||
|
||||
def get_power_on_hours_from(self, device: Device) -> int:
|
||||
# TODO how do I check if the device is a legacy workbench? Is there a better way?
|
||||
is_legacy_workbench = False if device.last_evidence.inxi else True
|
||||
if not is_legacy_workbench:
|
||||
storage_components = next((comp for comp in device.components if comp['type'] == 'Storage'), None)
|
||||
str_time = storage_components.get('time of used', "")
|
||||
else:
|
||||
str_time = ""
|
||||
uptime_in_hours = self.convert_str_time_to_hours(
|
||||
str_time, is_legacy_workbench)
|
||||
return uptime_in_hours
|
||||
|
||||
def convert_str_time_to_hours(self, time_str: str, is_legacy_workbench: bool) -> int:
|
||||
if is_legacy_workbench:
|
||||
return -1 # TODO Power on hours not available in legacy workbench
|
||||
else:
|
||||
multipliers = {'y': 365 * 24, 'd': 24, 'h': 1}
|
||||
return sum(int(part[:-1]) * multipliers[part[-1]] for part in time_str.split())
|
6
environmental_impact/apps.py
Normal file
6
environmental_impact/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class EnvironmentalImpactConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "environmental_impact"
|
0
environmental_impact/migrations/__init__.py
Normal file
0
environmental_impact/migrations/__init__.py
Normal file
9
environmental_impact/models.py
Normal file
9
environmental_impact/models.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
from dataclasses import dataclass
|
||||
from django.db import models
|
||||
|
||||
|
||||
@dataclass
|
||||
class EnvironmentalImpact:
|
||||
carbon_saved: float = 0.0
|
||||
co2_emissions: float = 0.0
|
||||
docs: str = ""
|
0
environmental_impact/tests/__init__.py
Normal file
0
environmental_impact/tests/__init__.py
Normal file
44
environmental_impact/tests/test_dummy_calculator.py
Normal file
44
environmental_impact/tests/test_dummy_calculator.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
from unittest.mock import patch
|
||||
import uuid
|
||||
from django.test import TestCase
|
||||
from device.models import Device
|
||||
from environmental_impact.models import EnvironmentalImpact
|
||||
from environmental_impact.algorithms.dummy_algo.dummy_calculator import DummyEnvironmentalImpactAlgorithm
|
||||
from evidence.models import Evidence
|
||||
|
||||
|
||||
class DummyEnvironmentalImpactAlgorithmTests(TestCase):
|
||||
|
||||
@patch('evidence.models.Evidence.get_doc', return_value={'credentialSubject': {}})
|
||||
@patch('evidence.models.Evidence.get_time', return_value=None)
|
||||
def setUp(self, mock_get_time, mock_get_doc):
|
||||
self.device = Device(id='1')
|
||||
evidence = self.device.last_evidence = Evidence(uuid=uuid.uuid4())
|
||||
evidence.inxi = True
|
||||
evidence.doc = {'credentialSubject': {}}
|
||||
self.algorithm = DummyEnvironmentalImpactAlgorithm()
|
||||
|
||||
def test_get_power_on_hours_from_legacy_device(self):
|
||||
# TODO is there a way to check that?
|
||||
pass
|
||||
|
||||
@patch('evidence.models.Evidence.get_components', return_value=[0, 0, 0, 0, 0, 0, 0, 0, 0, {'time of used': '1y 2d 3h'}])
|
||||
def test_get_power_on_hours_from_inxi_device(self, mock_get_components):
|
||||
hours = self.algorithm.get_power_on_hours_from(self.device)
|
||||
self.assertEqual(
|
||||
hours, 8811, "Inxi-parsed devices should correctly compute power-on hours")
|
||||
|
||||
@patch('evidence.models.Evidence.get_components', return_value=[0, 0, 0, 0, 0, 0, 0, 0, 0, {'time of used': '1y 2d 3h'}])
|
||||
def test_convert_str_time_to_hours(self, mock_get_components):
|
||||
result = self.algorithm.convert_str_time_to_hours('1y 2d 3h', False)
|
||||
self.assertEqual(
|
||||
result, 8811, "String to hours conversion should match expected output")
|
||||
|
||||
@patch('evidence.models.Evidence.get_components', return_value=[0, 0, 0, 0, 0, 0, 0, 0, 0, {'time of used': '1y 2d 3h'}])
|
||||
def test_environmental_impact_calculation(self, mock_get_components):
|
||||
impact = self.algorithm.get_device_environmental_impact(self.device)
|
||||
self.assertIsInstance(impact, EnvironmentalImpact,
|
||||
"Output should be an EnvironmentalImpact instance")
|
||||
expected_co2 = 8811 * 40 * 0.475 / 1000
|
||||
self.assertAlmostEqual(impact.co2_emissions, expected_co2,
|
||||
2, "CO2 emissions calculation should be accurate")
|
|
@ -0,0 +1,17 @@
|
|||
from environmental_impact.algorithms.algorithm_factory import FactoryEnvironmentImpactAlgorithm
|
||||
from django.test import TestCase
|
||||
from environmental_impact.algorithms.dummy_algo.dummy_calculator import DummyEnvironmentalImpactAlgorithm
|
||||
|
||||
|
||||
class FactoryEnvironmentImpactAlgorithmTests(TestCase):
|
||||
|
||||
def test_valid_algorithm_name(self):
|
||||
algorithm = FactoryEnvironmentImpactAlgorithm.run_environmental_impact_calculation(
|
||||
'dummy_calc')
|
||||
self.assertIsInstance(algorithm, DummyEnvironmentalImpactAlgorithm,
|
||||
"Factory should return a DummyEnvironmentalImpactAlgorithm instance")
|
||||
|
||||
def test_invalid_algorithm_name(self):
|
||||
with self.assertRaises(ValueError):
|
||||
FactoryEnvironmentImpactAlgorithm.run_environmental_impact_calculation(
|
||||
'invalid_calc')
|
3
environmental_impact/views.py
Normal file
3
environmental_impact/views.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
|
@ -31,11 +31,11 @@ class UploadForm(forms.Form):
|
|||
try:
|
||||
file_json = json.loads(file_data)
|
||||
snap = Build(file_json, None, check=True)
|
||||
exists_property = SystemProperty.objects.filter(
|
||||
exist_annotation = Annotation.objects.filter(
|
||||
uuid=snap.uuid
|
||||
).first()
|
||||
|
||||
if exists_property:
|
||||
if exist_annotation:
|
||||
raise ValidationError(
|
||||
_("The snapshot already exists"),
|
||||
code="duplicate_snapshot",
|
||||
|
@ -234,7 +234,7 @@ class EraseServerForm(forms.Form):
|
|||
if self.instance:
|
||||
return
|
||||
|
||||
UserProperty.objects.create(
|
||||
Annotation.objects.create(
|
||||
uuid=self.uuid,
|
||||
type=UserProperty.Type.ERASE_SERVER,
|
||||
key='ERASE_SERVER',
|
||||
|
|
|
@ -8,8 +8,7 @@ from django.db import models
|
|||
from django.db.models import Q
|
||||
from utils.constants import STR_EXTEND_SIZE, CHASSIS_DH
|
||||
from evidence.xapian import search
|
||||
from evidence.parse_details import ParseSnapshot
|
||||
from evidence.normal_parse_details import get_inxi, get_inxi_key
|
||||
from evidence.parse_details import ParseSnapshot, get_inxi, get_inxi_key
|
||||
from user.models import User, Institution
|
||||
|
||||
|
||||
|
@ -60,7 +59,7 @@ class Evidence:
|
|||
self.created = None
|
||||
self.dmi = None
|
||||
self.inxi = None
|
||||
self.properties = []
|
||||
self.annotations = []
|
||||
self.components = []
|
||||
self.default = "n/a"
|
||||
|
||||
|
@ -111,7 +110,7 @@ class Evidence:
|
|||
self.inxi = ev["output"]
|
||||
else:
|
||||
dmidecode_raw = self.doc["data"]["dmidecode"]
|
||||
inxi_raw = self.doc.get("data", {}).get("inxi")
|
||||
inxi_raw = self.doc["data"]["inxi"]
|
||||
self.dmi = DMIParse(dmidecode_raw)
|
||||
try:
|
||||
self.inxi = json.loads(inxi_raw)
|
||||
|
@ -160,6 +159,9 @@ class Evidence:
|
|||
if self.inxi:
|
||||
return self.device_manufacturer
|
||||
|
||||
if self.inxi:
|
||||
return self.device_manufacturer
|
||||
|
||||
return self.dmi.manufacturer().strip()
|
||||
|
||||
def get_model(self):
|
||||
|
@ -175,11 +177,14 @@ class Evidence:
|
|||
if self.inxi:
|
||||
return self.device_model
|
||||
|
||||
if self.inxi:
|
||||
return self.device_model
|
||||
|
||||
return self.dmi.model().strip()
|
||||
|
||||
def get_chassis(self):
|
||||
if self.is_legacy():
|
||||
return self.doc.get('device', {}).get('model', '')
|
||||
return self.doc['device']['model']
|
||||
|
||||
if self.inxi:
|
||||
return self.device_chassis
|
||||
|
@ -194,7 +199,7 @@ class Evidence:
|
|||
|
||||
def get_serial_number(self):
|
||||
if self.is_legacy():
|
||||
return self.doc.get('device', {}).get('serialNumber', '')
|
||||
return self.doc['device']['serialNumber']
|
||||
|
||||
if self.inxi:
|
||||
return self.device_serial_number
|
||||
|
|
|
@ -2,14 +2,12 @@ import json
|
|||
import hashlib
|
||||
import logging
|
||||
|
||||
from evidence import legacy_parse
|
||||
from evidence import old_parse
|
||||
from evidence import normal_parse
|
||||
from dmidecode import DMIParse
|
||||
from evidence.parse_details import ParseSnapshot
|
||||
|
||||
from evidence.models import SystemProperty
|
||||
from evidence.models import Annotation
|
||||
from evidence.xapian import index
|
||||
from evidence.normal_parse_details import get_inxi_key, get_inxi
|
||||
from evidence.parse_details import get_inxi_key, get_inxi
|
||||
from django.conf import settings
|
||||
|
||||
if settings.DPP:
|
||||
|
@ -26,31 +24,31 @@ def get_mac(inxi):
|
|||
if get_inxi(n, "port"):
|
||||
return get_inxi(iface, 'mac')
|
||||
|
||||
for n, iface in networks:
|
||||
if get_inxi(n, "port"):
|
||||
return get_inxi(iface, 'mac')
|
||||
|
||||
class Build:
|
||||
def __init__(self, evidence_json, user, check=False):
|
||||
"""
|
||||
This Build do the save in xapian as document, in Annotations and do
|
||||
register in dlt if is configured for that.
|
||||
|
||||
We have 4 cases for parser diferents snapshots than come from workbench.
|
||||
1) worbench 11 is old_parse.
|
||||
2) legacy is the worbench-script when create a snapshot for devicehub-teal
|
||||
3) some snapshots come as a credential. In this case is parsed as normal_parse
|
||||
4) normal snapshot from worbench-script is the most basic and is parsed as normal_parse
|
||||
"""
|
||||
self.evidence = evidence_json.copy()
|
||||
self.uuid = self.evidence.get('uuid')
|
||||
self.user = user
|
||||
self.json = evidence_json.copy()
|
||||
|
||||
if evidence_json.get("credentialSubject"):
|
||||
self.build = normal_parse.Build(evidence_json)
|
||||
self.uuid = evidence_json.get("credentialSubject", {}).get("uuid")
|
||||
elif evidence_json.get("software") != "workbench-script":
|
||||
self.build = old_parse.Build(evidence_json)
|
||||
elif evidence_json.get("data",{}).get("lshw"):
|
||||
self.build = legacy_parse.Build(evidence_json)
|
||||
else:
|
||||
self.build = normal_parse.Build(evidence_json)
|
||||
self.json.update(evidence_json["credentialSubject"])
|
||||
if evidence_json.get("evidence"):
|
||||
self.json["data"] = {}
|
||||
for ev in evidence_json["evidence"]:
|
||||
k = ev.get("operation")
|
||||
if not k:
|
||||
continue
|
||||
self.json["data"][k] = ev.get("output")
|
||||
|
||||
self.uuid = self.json['uuid']
|
||||
self.user = user
|
||||
self.hid = None
|
||||
self.chid = None
|
||||
self.phid = self.get_signature(self.json)
|
||||
self.generate_chids()
|
||||
|
||||
if check:
|
||||
return
|
||||
|
@ -67,6 +65,70 @@ class Build:
|
|||
snap = json.dumps(self.evidence)
|
||||
index(self.user.institution, self.uuid, snap)
|
||||
|
||||
def generate_chids(self):
|
||||
self.algorithms = {
|
||||
'hidalgo1': self.get_hid_14(),
|
||||
'legacy_dpp': self.get_chid_dpp(),
|
||||
}
|
||||
|
||||
def get_hid_14(self):
|
||||
if self.json.get("software") == "workbench-script":
|
||||
hid = self.get_hid(self.json)
|
||||
else:
|
||||
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}"
|
||||
|
||||
self.chid = hashlib.sha3_256(hid.encode()).hexdigest()
|
||||
return self.chid
|
||||
|
||||
def get_chid_dpp(self):
|
||||
if self.json.get("software") == "workbench-script":
|
||||
device = ParseSnapshot(self.json).device
|
||||
else:
|
||||
device = self.json['device']
|
||||
|
||||
hid = self.get_id_hw_dpp(device)
|
||||
self.chid = hashlib.sha3_256(hid.encode("utf-8")).hexdigest()
|
||||
return self.chid
|
||||
|
||||
def get_id_hw_dpp(self, d):
|
||||
manufacturer = d.get("manufacturer", '')
|
||||
model = d.get("model", '')
|
||||
chassis = d.get("chassis", '')
|
||||
serial_number = d.get("serialNumber", '')
|
||||
sku = d.get("sku", '')
|
||||
typ = d.get("type", '')
|
||||
version = d.get("version", '')
|
||||
|
||||
return f"{manufacturer}{model}{chassis}{serial_number}{sku}{typ}{version}"
|
||||
|
||||
def get_phid(self):
|
||||
if self.json.get("software") == "workbench-script":
|
||||
data = ParseSnapshot(self.json)
|
||||
self.device = data.device
|
||||
self.components = data.components
|
||||
else:
|
||||
self.device = self.json.get("device")
|
||||
self.components = self.json.get("components", [])
|
||||
|
||||
self.device.pop("actions", None)
|
||||
for c in self.components:
|
||||
c.pop("actions", None)
|
||||
|
||||
device = self.get_id_hw_dpp(self.device)
|
||||
components = sorted(self.components, key=lambda x: x.get("type"))
|
||||
doc = [("computer", device)]
|
||||
|
||||
for c in components:
|
||||
doc.append((c.get("type"), self.get_id_hw_dpp(c)))
|
||||
|
||||
return doc
|
||||
|
||||
def create_annotations(self):
|
||||
prop = SystemProperty.objects.filter(
|
||||
uuid=self.uuid,
|
||||
|
@ -87,12 +149,39 @@ class Build:
|
|||
value=self.sign(v)
|
||||
)
|
||||
|
||||
def sign(self, doc):
|
||||
return hashlib.sha3_256(doc.encode()).hexdigest()
|
||||
def get_hid(self, snapshot):
|
||||
try:
|
||||
self.inxi = self.json["data"]["inxi"]
|
||||
if isinstance(self.inxi, str):
|
||||
self.inxi = json.loads(self.inxi)
|
||||
except Exception:
|
||||
logger.error("No inxi in snapshot %s", self.uuid)
|
||||
return ""
|
||||
|
||||
machine = get_inxi_key(self.inxi, 'Machine')
|
||||
for m in machine:
|
||||
system = get_inxi(m, "System")
|
||||
if system:
|
||||
manufacturer = system
|
||||
model = get_inxi(m, "product")
|
||||
serial_number = get_inxi(m, "serial")
|
||||
chassis = get_inxi(m, "Type")
|
||||
else:
|
||||
sku = get_inxi(m, "part-nu")
|
||||
|
||||
mac = get_mac(self.inxi) or ""
|
||||
if not mac:
|
||||
txt = "Could not retrieve MAC address in snapshot %s"
|
||||
logger.warning(txt, snapshot['uuid'])
|
||||
return f"{manufacturer}{model}{chassis}{serial_number}{sku}"
|
||||
|
||||
return f"{manufacturer}{model}{chassis}{serial_number}{sku}{mac}"
|
||||
|
||||
def get_signature(self, doc):
|
||||
return hashlib.sha3_256(json.dumps(doc).encode()).hexdigest()
|
||||
|
||||
def register_device_dlt(self):
|
||||
legacy_dpp = self.build.algorithms.get('ereuse22')
|
||||
chid = self.sign(legacy_dpp)
|
||||
phid = self.sign(json.dumps(self.build.get_doc()))
|
||||
chid = self.algorithms.get('legacy_dpp')
|
||||
phid = self.get_signature(self.get_phid())
|
||||
register_device_dlt(chid, phid, self.uuid, self.user)
|
||||
register_passport_dlt(chid, phid, self.uuid, self.user)
|
||||
|
|
|
@ -1,38 +1,406 @@
|
|||
import re
|
||||
import json
|
||||
import logging
|
||||
|
||||
from evidence import (
|
||||
legacy_parse_details,
|
||||
normal_parse_details,
|
||||
old_parse_details
|
||||
)
|
||||
from datetime import datetime
|
||||
from dmidecode import DMIParse
|
||||
|
||||
from utils.constants import CHASSIS_DH, DATASTORAGEINTERFACE
|
||||
|
||||
|
||||
logger = logging.getLogger('django')
|
||||
|
||||
|
||||
def get_inxi_key(inxi, component):
|
||||
for n in inxi:
|
||||
for k, v in n.items():
|
||||
if component in k:
|
||||
return v
|
||||
|
||||
|
||||
def get_inxi(n, name):
|
||||
for k, v in n.items():
|
||||
if f"#{name}" in k:
|
||||
return v
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
class ParseSnapshot:
|
||||
def __init__(self, snapshot, default="n/a"):
|
||||
if snapshot.get("credentialSubject"):
|
||||
self.build = normal_parse_details.ParseSnapshot(
|
||||
snapshot,
|
||||
default=default
|
||||
)
|
||||
elif snapshot.get("software") != "workbench-script":
|
||||
self.build = old_parse_details.ParseSnapshot(
|
||||
snapshot,
|
||||
default=default
|
||||
)
|
||||
elif snapshot.get("data",{}).get("lshw"):
|
||||
self.build = legacy_parse_details.ParseSnapshot(
|
||||
snapshot,
|
||||
default=default
|
||||
)
|
||||
else:
|
||||
self.build = normal_parse_details.ParseSnapshot(
|
||||
snapshot,
|
||||
default=default
|
||||
)
|
||||
self.default = default
|
||||
self.dmidecode_raw = snapshot.get("data", {}).get("dmidecode", "{}")
|
||||
self.smart_raw = snapshot.get("data", {}).get("smartctl", [])
|
||||
self.inxi_raw = snapshot.get("data", {}).get("inxi", "") or ""
|
||||
for ev in snapshot.get("evidence", []):
|
||||
if "dmidecode" == ev.get("operation"):
|
||||
self.dmidecode_raw = ev["output"]
|
||||
if "inxi" == ev.get("operation"):
|
||||
self.inxi_raw = ev["output"]
|
||||
if "smartctl" == ev.get("operation"):
|
||||
self.smart_raw = ev["output"]
|
||||
data = snapshot
|
||||
if snapshot.get("credentialSubject"):
|
||||
data = snapshot["credentialSubject"]
|
||||
|
||||
self.default = default
|
||||
self.device = self.build.snapshot_json.get("device")
|
||||
self.components = self.build.snapshot_json.get("components")
|
||||
self.device = {"actions": []}
|
||||
self.components = []
|
||||
|
||||
self.dmi = DMIParse(self.dmidecode_raw)
|
||||
self.smart = self.loads(self.smart_raw)
|
||||
self.inxi = self.loads(self.inxi_raw)
|
||||
|
||||
self.set_computer()
|
||||
self.set_components()
|
||||
self.snapshot_json = {
|
||||
"type": "Snapshot",
|
||||
"device": self.device,
|
||||
"software": data["software"],
|
||||
"components": self.components,
|
||||
"uuid": data['uuid'],
|
||||
"endTime": data["timestamp"],
|
||||
"elapsed": 1,
|
||||
}
|
||||
|
||||
def set_computer(self):
|
||||
machine = get_inxi_key(self.inxi, 'Machine') or []
|
||||
for m in machine:
|
||||
system = get_inxi(m, "System")
|
||||
if system:
|
||||
self.device['manufacturer'] = system
|
||||
self.device['model'] = get_inxi(m, "product")
|
||||
self.device['serialNumber'] = get_inxi(m, "serial")
|
||||
self.device['type'] = get_inxi(m, "Type")
|
||||
self.device['chassis'] = self.device['type']
|
||||
self.device['version'] = get_inxi(m, "v")
|
||||
else:
|
||||
self.device['system_uuid'] = get_inxi(m, "uuid")
|
||||
self.device['sku'] = get_inxi(m, "part-nu")
|
||||
|
||||
def set_components(self):
|
||||
self.get_mother_board()
|
||||
self.get_cpu()
|
||||
self.get_ram()
|
||||
self.get_graphic()
|
||||
self.get_display()
|
||||
self.get_networks()
|
||||
self.get_sound_card()
|
||||
self.get_data_storage()
|
||||
self.get_battery()
|
||||
|
||||
def get_mother_board(self):
|
||||
machine = get_inxi_key(self.inxi, 'Machine') or []
|
||||
mb = {"type": "Motherboard",}
|
||||
for m in machine:
|
||||
bios_date = get_inxi(m, "date")
|
||||
if not bios_date:
|
||||
continue
|
||||
mb["manufacturer"] = get_inxi(m, "Mobo")
|
||||
mb["model"] = get_inxi(m, "model")
|
||||
mb["serialNumber"] = get_inxi(m, "serial")
|
||||
mb["version"] = get_inxi(m, "v")
|
||||
mb["biosDate"] = bios_date
|
||||
mb["biosVersion"] = self.get_bios_version()
|
||||
mb["firewire"]: self.get_firmware_num()
|
||||
mb["pcmcia"]: self.get_pcmcia_num()
|
||||
mb["serial"]: self.get_serial_num()
|
||||
mb["usb"]: self.get_usb_num()
|
||||
|
||||
self.get_ram_slots(mb)
|
||||
|
||||
self.components.append(mb)
|
||||
|
||||
def get_ram_slots(self, mb):
|
||||
memory = get_inxi_key(self.inxi, 'Memory') or []
|
||||
for m in memory:
|
||||
slots = get_inxi(m, "slots")
|
||||
if not slots:
|
||||
continue
|
||||
mb["slots"] = slots
|
||||
mb["ramSlots"] = get_inxi(m, "modules")
|
||||
mb["ramMaxSize"] = get_inxi(m, "capacity")
|
||||
|
||||
|
||||
def get_cpu(self):
|
||||
cpu = get_inxi_key(self.inxi, 'CPU') or []
|
||||
cp = {"type": "Processor"}
|
||||
vulnerabilities = []
|
||||
for c in cpu:
|
||||
base = get_inxi(c, "model")
|
||||
if base:
|
||||
cp["model"] = get_inxi(c, "model")
|
||||
cp["arch"] = get_inxi(c, "arch")
|
||||
cp["bits"] = get_inxi(c, "bits")
|
||||
cp["gen"] = get_inxi(c, "gen")
|
||||
cp["family"] = get_inxi(c, "family")
|
||||
cp["date"] = get_inxi(c, "built")
|
||||
continue
|
||||
des = get_inxi(c, "L1")
|
||||
if des:
|
||||
cp["L1"] = des
|
||||
cp["L2"] = get_inxi(c, "L2")
|
||||
cp["L3"] = get_inxi(c, "L3")
|
||||
cp["cpus"] = get_inxi(c, "cpus")
|
||||
cp["cores"] = get_inxi(c, "cores")
|
||||
cp["threads"] = get_inxi(c, "threads")
|
||||
continue
|
||||
bogo = get_inxi(c, "bogomips")
|
||||
if bogo:
|
||||
cp["bogomips"] = bogo
|
||||
cp["base/boost"] = get_inxi(c, "base/boost")
|
||||
cp["min/max"] = get_inxi(c, "min/max")
|
||||
cp["ext-clock"] = get_inxi(c, "ext-clock")
|
||||
cp["volts"] = get_inxi(c, "volts")
|
||||
continue
|
||||
ctype = get_inxi(c, "Type")
|
||||
if ctype:
|
||||
v = {"Type": ctype}
|
||||
status = get_inxi(c, "status")
|
||||
if status:
|
||||
v["status"] = status
|
||||
mitigation = get_inxi(c, "mitigation")
|
||||
if mitigation:
|
||||
v["mitigation"] = mitigation
|
||||
vulnerabilities.append(v)
|
||||
|
||||
self.components.append(cp)
|
||||
|
||||
|
||||
def get_ram(self):
|
||||
memory = get_inxi_key(self.inxi, 'Memory') or []
|
||||
mem = {"type": "RamModule"}
|
||||
|
||||
for m in memory:
|
||||
base = get_inxi(m, "System RAM")
|
||||
if base:
|
||||
mem["size"] = get_inxi(m, "total")
|
||||
slot = get_inxi(m, "manufacturer")
|
||||
if slot:
|
||||
mem["manufacturer"] = slot
|
||||
mem["model"] = get_inxi(m, "part-no")
|
||||
mem["serialNumber"] = get_inxi(m, "serial")
|
||||
mem["speed"] = get_inxi(m, "speed")
|
||||
mem["bits"] = get_inxi(m, "data")
|
||||
mem["interface"] = get_inxi(m, "type")
|
||||
module = get_inxi(m, "modules")
|
||||
if module:
|
||||
mem["modules"] = module
|
||||
|
||||
self.components.append(mem)
|
||||
|
||||
def get_graphic(self):
|
||||
graphics = get_inxi_key(self.inxi, 'Graphics') or []
|
||||
|
||||
for c in graphics:
|
||||
if not get_inxi(c, "Device") or not get_inxi(c, "vendor"):
|
||||
continue
|
||||
|
||||
self.components.append(
|
||||
{
|
||||
"type": "GraphicCard",
|
||||
"memory": self.get_memory_video(c),
|
||||
"manufacturer": get_inxi(c, "vendor"),
|
||||
"model": get_inxi(c, "Device"),
|
||||
"arch": get_inxi(c, "arch"),
|
||||
"serialNumber": get_inxi(c, "serial"),
|
||||
"integrated": True if get_inxi(c, "port") else False
|
||||
}
|
||||
)
|
||||
|
||||
def get_battery(self):
|
||||
bats = get_inxi_key(self.inxi, 'Battery') or []
|
||||
for b in bats:
|
||||
self.components.append(
|
||||
{
|
||||
"type": "Battery",
|
||||
"model": get_inxi(b, "model"),
|
||||
"serialNumber": get_inxi(b, "serial"),
|
||||
"condition": get_inxi(b, "condition"),
|
||||
"cycles": get_inxi(b, "cycles"),
|
||||
"volts": get_inxi(b, "volts")
|
||||
}
|
||||
)
|
||||
|
||||
def get_memory_video(self, c):
|
||||
memory = get_inxi_key(self.inxi, 'Memory') or []
|
||||
|
||||
for m in memory:
|
||||
igpu = get_inxi(m, "igpu")
|
||||
agpu = get_inxi(m, "agpu")
|
||||
ngpu = get_inxi(m, "ngpu")
|
||||
gpu = get_inxi(m, "gpu")
|
||||
if igpu or agpu or gpu or ngpu:
|
||||
return igpu or agpu or gpu or ngpu
|
||||
|
||||
return self.default
|
||||
|
||||
def get_data_storage(self):
|
||||
hdds= get_inxi_key(self.inxi, 'Drives') or []
|
||||
for d in hdds:
|
||||
usb = get_inxi(d, "type")
|
||||
if usb == "USB":
|
||||
continue
|
||||
|
||||
serial = get_inxi(d, "serial")
|
||||
if serial:
|
||||
hd = {
|
||||
"type": "Storage",
|
||||
"manufacturer": get_inxi(d, "vendor"),
|
||||
"model": get_inxi(d, "model"),
|
||||
"serialNumber": get_inxi(d, "serial"),
|
||||
"size": get_inxi(d, "size"),
|
||||
"speed": get_inxi(d, "speed"),
|
||||
"interface": get_inxi(d, "tech"),
|
||||
"firmware": get_inxi(d, "fw-rev")
|
||||
}
|
||||
rpm = get_inxi(d, "rpm")
|
||||
if rpm:
|
||||
hd["rpm"] = rpm
|
||||
|
||||
family = get_inxi(d, "family")
|
||||
if family:
|
||||
hd["family"] = family
|
||||
|
||||
sata = get_inxi(d, "sata")
|
||||
if sata:
|
||||
hd["sata"] = sata
|
||||
|
||||
continue
|
||||
|
||||
|
||||
cycles = get_inxi(d, "cycles")
|
||||
if cycles:
|
||||
hd['cycles'] = cycles
|
||||
hd["health"] = get_inxi(d, "health")
|
||||
hd["time of used"] = get_inxi(d, "on")
|
||||
hd["read used"] = get_inxi(d, "read-units")
|
||||
hd["written used"] = get_inxi(d, "written-units")
|
||||
|
||||
self.components.append(hd)
|
||||
continue
|
||||
|
||||
hd = {}
|
||||
|
||||
def sanitize(self, action):
|
||||
return []
|
||||
|
||||
def get_networks(self):
|
||||
nets = get_inxi_key(self.inxi, "Network") or []
|
||||
networks = [(nets[i], nets[i + 1]) for i in range(0, len(nets) - 1, 2)]
|
||||
|
||||
for n, iface in networks:
|
||||
model = get_inxi(n, "Device")
|
||||
if not model:
|
||||
continue
|
||||
|
||||
interface = ''
|
||||
for k in n.keys():
|
||||
if "port" in k:
|
||||
interface = "Integrated"
|
||||
if "pcie" in k:
|
||||
interface = "PciExpress"
|
||||
if get_inxi(n, "type") == "USB":
|
||||
interface = "USB"
|
||||
|
||||
self.components.append(
|
||||
{
|
||||
"type": "NetworkAdapter",
|
||||
"model": model,
|
||||
"manufacturer": get_inxi(n, 'vendor'),
|
||||
"serialNumber": get_inxi(iface, 'mac'),
|
||||
"speed": get_inxi(n, "speed"),
|
||||
"interface": interface,
|
||||
}
|
||||
)
|
||||
|
||||
def get_sound_card(self):
|
||||
audio = get_inxi_key(self.inxi, "Audio") or []
|
||||
|
||||
for c in audio:
|
||||
model = get_inxi(c, "Device")
|
||||
if not model:
|
||||
continue
|
||||
|
||||
self.components.append(
|
||||
{
|
||||
"type": "SoundCard",
|
||||
"model": model,
|
||||
"manufacturer": get_inxi(c, 'vendor'),
|
||||
"serialNumber": get_inxi(c, 'serial'),
|
||||
}
|
||||
)
|
||||
|
||||
def get_display(self):
|
||||
graphics = get_inxi_key(self.inxi, "Graphics") or []
|
||||
for c in graphics:
|
||||
if not get_inxi(c, "Monitor"):
|
||||
continue
|
||||
|
||||
self.components.append(
|
||||
{
|
||||
"type": "Display",
|
||||
"model": get_inxi(c, "model"),
|
||||
"manufacturer": get_inxi(c, "vendor"),
|
||||
"serialNumber": get_inxi(c, "serial"),
|
||||
'size': get_inxi(c, "size"),
|
||||
'diagonal': get_inxi(c, "diag"),
|
||||
'resolution': get_inxi(c, "res"),
|
||||
"date": get_inxi(c, "built"),
|
||||
'ratio': get_inxi(c, "ratio"),
|
||||
}
|
||||
)
|
||||
|
||||
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_version(self):
|
||||
return self.dmi.get("BIOS")[0].get("BIOS Revision", '1')
|
||||
|
||||
def loads(self, x):
|
||||
if isinstance(x, str):
|
||||
try:
|
||||
return json.loads(x)
|
||||
except Exception as ss:
|
||||
logger.warning("%s", ss)
|
||||
return {}
|
||||
return x
|
||||
|
||||
def errors(self, txt=None):
|
||||
if not txt:
|
||||
return self._errors
|
||||
|
||||
logger.error(txt)
|
||||
self._errors.append("%s", txt)
|
||||
|
|
|
@ -11,7 +11,6 @@ xlrd==2.0.1
|
|||
odfpy==1.4.1
|
||||
pytz==2024.2
|
||||
json-repair==0.30.0
|
||||
setuptools==65.5.1
|
||||
setuptools==75.5.0
|
||||
requests==2.32.3
|
||||
wheel==0.45.1
|
||||
|
||||
wheel==0.45.0
|
||||
|
|
|
@ -28,9 +28,19 @@ EREUSE22 = [
|
|||
"version"
|
||||
]
|
||||
|
||||
LEGACY_DPP = [
|
||||
"manufacturer",
|
||||
"model",
|
||||
"chassis",
|
||||
"serialNumber",
|
||||
"sku",
|
||||
"type",
|
||||
"version"
|
||||
]
|
||||
|
||||
ALGOS = {
|
||||
"ereuse24": EREUSE24,
|
||||
"ereuse22": EREUSE22
|
||||
"hidalgo1": HID_ALGO1,
|
||||
"legacy_dpp": LEGACY_DPP
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue