Compare commits

...

176 commits

Author SHA1 Message Date
Sergio Giménez Antón d4f971bfa3 Merge remote-tracking branch 'refs/remotes/origin/feature/f31-device-enviromental-impact' into feature/f31-device-enviromental-impact 2025-03-18 19:59:09 +01:00
Sergio Giménez Antón fcd6e13bf9 Add UI polishing for the demo 2025-03-18 19:58:32 +01:00
Sergio Giménez Antón e692417c73 Fix get_power_hours_from_components 2025-03-18 19:58:32 +01:00
Sergio Giménez Antón 9600a6064f Improve example docs 2025-03-18 19:58:32 +01:00
Sergio Giménez Antón b83afa6f12 Add algorithm docs 2025-03-18 19:58:32 +01:00
Sergio Giménez Antón c6f63d4d44 Make env algorithms python packages 2025-03-18 19:58:32 +01:00
Sergio Giménez Antón b2bf894338 Update template with new structure 2025-03-18 19:58:32 +01:00
Sergio Giménez Antón fdc375f804 f31: Initial implementation for environmental impact calculator 2025-03-18 19:58:32 +01:00
Sergio Giménez Antón f26935b617 Very initial impleentation of co2 consumption 2025-03-18 19:58:32 +01:00
Sergio Giménez Antón 01bb822aa5 Add both impact and dpp in the view context 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 5c7fc8fd47 fix rebase from main 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas a7c19ac93e add inxi in parsing and show in details of devs 2025-03-18 19:58:32 +01:00
pedro de1f090694 docker entrypoint: bugfix when DPP env var unbound 2025-03-18 19:58:32 +01:00
pedro a876acc814 docker entrypoint: adapt it to DPP env var 2025-03-18 19:58:32 +01:00
pedro a8884ea012 propagate DPP env var to docker 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 4b1fb26c67 activate/deactivate DPP from env 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 03bdd4818b convert jsonld in credentials for dpps 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 1f93c88bc8 drop loggers 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 573603a6ea fix register dpp 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 3d49db9436 fix did dpp 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas d4d0a35e4a debug timestamp 2025-03-18 19:58:32 +01:00
pedro d8dc37ba94 bugfix attempt verifyProof
co-authored with cayo
2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 8e29ef4bf5 more and more debug 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 62006eb4e3 more debug 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas e42f2c3ea3 fix call to proofs 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 911388718d dpp for proofs 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 3d744e7945 dpp for proofs 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 277a7606e2 drop actions for dpp 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas fe1d020618 drop actions for dpp 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas c8ddec6942 debug in proof call 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 886cf20565 fix cors origin 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 871ed179fb fix json call to chid 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas eb796de4d3 fix phid hash list 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 4b9bcb054e fix phid hash list 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 55e018ad51 fix phid 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 782fc4a541 fix 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 85011076e8 new document and out device and components 2025-03-18 19:58:32 +01:00
pedro 0fc50d7187 comment logger trace when DEBUG
is it necessary?
2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 746a692118 remove flask sintax for django sintax 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas a3613732a9 remove flask sintax for django sintax 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 711fb9e171 add_services 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas d44310bcfd add result for dpp and for chid 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 60c618ce09 fix new document json 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 4e69062452 fix get_result 2025-03-18 19:58:32 +01:00
pedro 490144fd86 dhub settings: bugfix wrong DLT TOKEN 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas e6995e74e0 fix get_result for get correct document 2025-03-18 19:58:32 +01:00
pedro 1fe20e11b8 progress on making it work
still fails
2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 1267f388c1 remove pdb 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 369dc83154 get_result for json 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 3d0527edf1 view dpp page 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 0bbc3475c2 fix 2025-03-18 19:58:32 +01:00
pedro 13a74b133d no sudo in docker-reset, all is with user 1000 2025-03-18 19:58:32 +01:00
pedro 4f38cdf5b1 dh-django dockerfile: use uid 1000 (app)
at least temporarily
2025-03-18 19:58:32 +01:00
pedro 6bb31c40ff dh docker: bugfix wrong usage of up_snapshots 2025-03-18 19:58:32 +01:00
pedro 6fd9792d78 dh dockerfile: add time debpkg 2025-03-18 19:58:32 +01:00
pedro c27b2c2263 dh docker: bugfix wrong path in rm prev snapshots 2025-03-18 19:58:32 +01:00
pedro 97c74ca9bb dh docker: create institution before first dlt usr 2025-03-18 19:58:32 +01:00
pedro 51cbd2bf62 bugfix logger 2025-03-18 19:58:32 +01:00
pedro 93a70ed031 logger: bugfix function name changed for highlight 2025-03-18 19:58:32 +01:00
pedro 83885ceb84 dh docker: cleanup other snapshots when dpp/dlt 2025-03-18 19:58:32 +01:00
pedro 79df0100b1 dh docker: first migrate, then config 2025-03-18 19:58:32 +01:00
pedro ccdd292a97 utils/logger: ensure msgs are logged 2025-03-18 19:58:32 +01:00
pedro 518866bbc9 logger: improve error handling 2025-03-18 19:58:32 +01:00
pedro 4533395e3b dpp/dlt: fix typo 2025-03-18 19:58:32 +01:00
pedro 1a30fa75dc dpp/dlt: fix typo 2025-03-18 19:58:32 +01:00
pedro 9a3a5fe638 dpp/dlt: fix typo 2025-03-18 19:58:32 +01:00
pedro d085d448a9 dh docker entrypoint: use appropriate new env vars 2025-03-18 19:58:32 +01:00
pedro 0974e074d2 docker: remove unused vars in django
were used in the flask app devicehub-teal
2025-03-18 19:58:32 +01:00
pedro b707d78595 docker entrypoint: make DB_* optional 2025-03-18 19:58:32 +01:00
pedro 6c35210d4e docker devicehub-django entrypoint 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas d614fd4756 fix 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas cd93e6dafa fix 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas a81172ad8e add memberFederated model 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 73a72a7d15 add did view 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas bdf029abbd add commands for setup to dlt 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 893bf0c14d . 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 96b0a0ad80 register device and dpp in dlt and dpp api 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas eeea0b3879 first base for dpp 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas c30416d038 fix parsing with credentials 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 1757f70963 fix parsing 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas d7bd47554a fix get_hid 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas ba2ddecc11 fix component empty 2025-03-18 19:58:32 +01:00
Cayo Puigdefabregas 59bdab7776 add inxi in parsing and show in details of devs 2025-03-18 19:58:32 +01:00
Sergio Giménez Antón 5b90d7a648 [WIP] Add button for exporting to PDF 2025-03-18 19:58:32 +01:00
sergio_gimenez 9dc7a66ffd Initial view of the enviromental impact without calculations 2025-03-18 19:58:32 +01:00
Sergio Giménez Antón 01dbe005e4 Add UI polishing for the demo 2025-02-26 18:44:50 +01:00
Sergio Giménez Antón 448287248e Fix get_power_hours_from_components 2025-02-26 18:20:32 +01:00
Sergio Giménez Antón e6223420d2 Improve example docs 2025-02-26 17:36:21 +01:00
Sergio Giménez Antón 6ee0184f66 Add algorithm docs 2025-02-25 09:51:04 +01:00
Sergio Giménez Antón 8f206340f3 Make env algorithms python packages 2025-02-25 08:34:46 +01:00
Sergio Giménez Antón 324eaa215c Update template with new structure 2025-02-25 08:26:45 +01:00
Sergio Giménez Antón 0da3e15a03 Merge branch 'main' into feature/f31-device-enviromental-impact 2025-02-25 07:39:45 +01:00
Sergio Giménez Antón bd4f6b7d56 f31: Initial implementation for environmental impact calculator 2025-01-07 08:06:29 +01:00
Sergio Giménez Antón f9c9c9dd7c Very initial impleentation of co2 consumption 2024-12-17 09:58:20 +01:00
Sergio Giménez Antón 60ccbec369 Merge branch 'main' into feature/f31-device-enviromental-impact 2024-12-17 08:03:31 +01:00
Sergio Giménez Antón 3fb0961815 Add both impact and dpp in the view context 2024-12-16 09:01:05 +01:00
Sergio Giménez Antón 447946a576 Merge branch 'inxi' into feature/f31-device-enviromental-impact 2024-12-16 08:55:00 +01:00
Cayo Puigdefabregas 5d190d07a3 fix rebase from main 2024-12-12 17:11:05 +01:00
Cayo Puigdefabregas d1abb206e8 add inxi in parsing and show in details of devs 2024-12-11 17:41:05 +01:00
pedro 85bae67189 docker entrypoint: bugfix when DPP env var unbound 2024-12-11 17:12:58 +01:00
pedro d429485651 docker entrypoint: adapt it to DPP env var 2024-12-11 17:12:58 +01:00
pedro 07c25f4a92 propagate DPP env var to docker 2024-12-11 17:12:58 +01:00
Cayo Puigdefabregas 14277c17cb activate/deactivate DPP from env 2024-12-11 17:12:56 +01:00
Cayo Puigdefabregas f7051c3130 convert jsonld in credentials for dpps 2024-12-11 17:09:25 +01:00
Cayo Puigdefabregas 09be1a2f74 drop loggers 2024-12-11 17:09:25 +01:00
Cayo Puigdefabregas a3dd5d9639 fix register dpp 2024-12-11 17:09:25 +01:00
Cayo Puigdefabregas 3f5460b81f fix did dpp 2024-12-11 17:09:25 +01:00
Cayo Puigdefabregas bf7975bc24 debug timestamp 2024-12-11 17:09:25 +01:00
pedro 8e128557c0 bugfix attempt verifyProof
co-authored with cayo
2024-12-11 17:09:25 +01:00
Cayo Puigdefabregas 25e7e85548 more and more debug 2024-12-11 17:09:25 +01:00
Cayo Puigdefabregas ba126491be more debug 2024-12-11 17:09:25 +01:00
Cayo Puigdefabregas 81e7ba267d fix call to proofs 2024-12-11 17:09:25 +01:00
Cayo Puigdefabregas 1e08f0fc0c dpp for proofs 2024-12-11 17:09:25 +01:00
Cayo Puigdefabregas ebabb6b228 dpp for proofs 2024-12-11 17:09:25 +01:00
Cayo Puigdefabregas 4954199610 drop actions for dpp 2024-12-11 17:09:25 +01:00
Cayo Puigdefabregas e84b72c70b drop actions for dpp 2024-12-11 17:09:25 +01:00
Cayo Puigdefabregas 99435fff85 debug in proof call 2024-12-11 17:09:25 +01:00
Cayo Puigdefabregas 6c0e77891f fix cors origin 2024-12-11 17:09:25 +01:00
Cayo Puigdefabregas a2d859494b fix json call to chid 2024-12-11 17:09:25 +01:00
Cayo Puigdefabregas ea6d990e56 fix phid hash list 2024-12-11 17:09:25 +01:00
Cayo Puigdefabregas 612737d46c fix phid hash list 2024-12-11 17:09:25 +01:00
Cayo Puigdefabregas 30be57ee25 fix phid 2024-12-11 17:09:25 +01:00
Cayo Puigdefabregas 88bdabb64f fix 2024-12-11 17:09:25 +01:00
Cayo Puigdefabregas 96268c8caf new document and out device and components 2024-12-11 17:09:23 +01:00
pedro 7ed05f0932 comment logger trace when DEBUG
is it necessary?
2024-12-11 17:04:20 +01:00
Cayo Puigdefabregas b652d7d452 remove flask sintax for django sintax 2024-12-11 17:04:20 +01:00
Cayo Puigdefabregas 04ecb4f2f1 remove flask sintax for django sintax 2024-12-11 17:04:20 +01:00
Cayo Puigdefabregas 1613eaaa44 add_services 2024-12-11 17:04:20 +01:00
Cayo Puigdefabregas 06264558df add result for dpp and for chid 2024-12-11 17:04:20 +01:00
Cayo Puigdefabregas 80b4c3b4ca fix new document json 2024-12-11 17:04:20 +01:00
Cayo Puigdefabregas e2078c7bde fix get_result 2024-12-11 17:04:20 +01:00
pedro cfae9d4ec9 dhub settings: bugfix wrong DLT TOKEN 2024-12-11 17:04:20 +01:00
Cayo Puigdefabregas 578fa73fe5 fix get_result for get correct document 2024-12-11 17:04:20 +01:00
pedro f1d57ff618 progress on making it work
still fails
2024-12-11 17:04:20 +01:00
Cayo Puigdefabregas 3cf8ceb5d3 remove pdb 2024-12-11 17:04:20 +01:00
Cayo Puigdefabregas b56dc0dfda get_result for json 2024-12-11 17:04:20 +01:00
Cayo Puigdefabregas 1c58bff515 view dpp page 2024-12-11 17:04:20 +01:00
Cayo Puigdefabregas e6c1ede93c fix 2024-12-11 17:04:20 +01:00
pedro 371845971c no sudo in docker-reset, all is with user 1000 2024-12-11 17:04:20 +01:00
pedro b4efcfb171 dh-django dockerfile: use uid 1000 (app)
at least temporarily
2024-12-11 17:04:20 +01:00
pedro ac0d36ea6f dh docker: bugfix wrong usage of up_snapshots 2024-12-11 17:04:20 +01:00
pedro 6a3a2b3a2b dh dockerfile: add time debpkg 2024-12-11 17:04:20 +01:00
pedro 850678fbe4 dh docker: bugfix wrong path in rm prev snapshots 2024-12-11 17:04:20 +01:00
pedro f43aaf6ac6 dh docker: create institution before first dlt usr 2024-12-11 17:04:20 +01:00
pedro 355ed08561 bugfix logger 2024-12-11 17:04:20 +01:00
pedro d0e46aa0b0 logger: bugfix function name changed for highlight 2024-12-11 17:04:20 +01:00
pedro 771b216a31 dh docker: cleanup other snapshots when dpp/dlt 2024-12-11 17:04:20 +01:00
pedro 263eacda99 add dpp 2024-12-11 17:04:20 +01:00
pedro 8fcd20f609 dh docker: first migrate, then config 2024-12-11 17:04:20 +01:00
pedro 15fb5d3739 utils/logger: ensure msgs are logged 2024-12-11 17:04:20 +01:00
pedro d7ff3c2798 logger: improve error handling 2024-12-11 17:04:20 +01:00
pedro 0e0ad400c2 dpp/dlt: fix typo 2024-12-11 17:04:20 +01:00
pedro 367d3a7f87 dpp/dlt: fix typo 2024-12-11 17:04:20 +01:00
pedro c90ed58ea0 dpp/dlt: fix typo 2024-12-11 17:04:20 +01:00
pedro 45629db102 dh docker entrypoint: use appropriate new env vars 2024-12-11 17:04:20 +01:00
pedro 1e29f9562d docker: remove unused vars in django
were used in the flask app devicehub-teal
2024-12-11 17:04:20 +01:00
pedro d0cac9d1d9 docker entrypoint: make DB_* optional 2024-12-11 17:04:20 +01:00
pedro 8b4d1f51f6 docker devicehub-django entrypoint 2024-12-11 17:04:20 +01:00
Cayo Puigdefabregas 34ea4bedfc fix 2024-12-11 17:04:20 +01:00
Cayo Puigdefabregas fe429e7db6 fix 2024-12-11 17:04:20 +01:00
Cayo Puigdefabregas caf2606fd9 add memberFederated model 2024-12-11 17:04:20 +01:00
Cayo Puigdefabregas 73d478f517 add did view 2024-12-11 17:04:20 +01:00
Cayo Puigdefabregas 0f03171076 add commands for setup to dlt 2024-12-11 17:04:20 +01:00
pedro bfdcb33538 docker: add dpp python dep ereuseapitest 2024-12-11 17:04:20 +01:00
Cayo Puigdefabregas 271ac83d71 . 2024-12-11 17:04:20 +01:00
Cayo Puigdefabregas f7b2687ca2 register device and dpp in dlt and dpp api 2024-12-11 17:04:11 +01:00
Cayo Puigdefabregas 1dad22c3d3 first base for dpp 2024-12-11 17:02:26 +01:00
Cayo Puigdefabregas 7de6d69a6c fix parsing with credentials 2024-12-05 19:23:53 +01:00
Sergio Giménez Antón fa5b9eec67 Merge branch 'inxi' into feature/f31-device-enviromental-impact 2024-12-05 09:18:03 +01:00
Cayo Puigdefabregas 7fd42db3e4 fix parsing 2024-12-03 16:37:56 +01:00
Cayo Puigdefabregas bed40d3ee0 fix get_hid 2024-11-20 18:41:59 +01:00
Cayo Puigdefabregas 9553ed6a4c fix component empty 2024-11-20 18:35:27 +01:00
Sergio Giménez Antón f3c9297ffd [WIP] Add button for exporting to PDF 2024-11-19 08:27:44 +01:00
Sergio Giménez cb6c7f6fda Merge branch 'main' into feature/f31-device-enviromental-impact 2024-11-16 16:37:30 +01:00
Cayo Puigdefabregas a0276f439e add inxi in parsing and show in details of devs 2024-11-15 12:47:08 +01:00
sergio_gimenez a4d361ff9b Initial view of the enviromental impact without calculations 2024-11-07 08:15:42 +01:00
37 changed files with 1017 additions and 205 deletions

View file

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

View file

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

View file

@ -75,9 +75,6 @@
{{ dev.model }}
{% endif %}
</td>
<td>
{{ dev.updated }}
</td>
</tr>
</tbody>
{% endfor %}

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

@ -87,6 +87,7 @@ INSTALLED_APPS = [
"action",
"admin",
"api",
"environmental_impact"
]
DPP = config("DPP", default=False, cast=bool)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

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

View 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

View 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

View 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**.

View file

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

View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class EnvironmentalImpactConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "environmental_impact"

View 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 = ""

View file

View 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")

View file

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

View file

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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