Compare commits

..

108 commits

Author SHA1 Message Date
Cayo Puigdefabregas ca6d98368d fix annotations in parse 2025-01-23 13:05:09 +01:00
Cayo Puigdefabregas d8b1a632e6 fix base template 2025-01-23 12:36:29 +01:00
Cayo Puigdefabregas 684b20966f split long line 2025-01-23 12:36:29 +01:00
Cayo Puigdefabregas 840795b759 add version 2025-01-23 12:36:29 +01:00
Cayo Puigdefabregas 65d1ad2017 replace waring for yellow in details 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 37786cadae deleting obsolete if statement 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 365f92601e renamed property variable to prop 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 1b90eea6de added missing components tab 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 1ea4c0d2e5 added logging for evidence tag changes 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 79f0d50f7a text size adjustment and displaying none states 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 9d90377e03 more contrast on save note button 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 7aedd5671e states and notes view refactoring 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 5656e6553f userproperties views refactoring 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki ea0d9e7ddc better bootstrap tables 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki e704d99723 view changes 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki b1fcdea493 better representiation of delete/edit notes 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 477f5a06d0 edit update cannot be blank 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 912b27c1d5 adding remove button for notes 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 3efe74c638 notes now can be updated 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 0ed78d997c deleted undo state view 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 4c93a27109 added a sidebar notes display 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 975f2ab126 changes to state defiinitions list 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 41780cc592 better success message and removed devicelog var 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 22ebe3da1e normalized deviceLog messages 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki d8ac5cb203 command for adding default states 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 92419b89d0 erased old logger 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 2605844dd4 minor cosmetic changes 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki d35cdb1fd7 state definition list changes and disabled logging 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 3e04fba3ab default value for no state 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 755ca388eb adding orm migrations 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 966d7d591b log list now shows log table 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki bb7ed1ed5c added logging for states, user properties and notes 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 070004f9ea simpler state change and action input 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 63b8815d1b current state table erased and spacing fix 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki ecf0d5953f deleted unique constraint on userproperty 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 29acd1faab notes and log models added 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki d805d03075 device tag bugfix 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 661e3510e2 lotproperties delete and update added 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki a2b702cc82 added action migration 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki cf50e9c738 now check for state on delete modal 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki dbc37aa530 cosmetic changes to statesdefinitions list 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 623d285691 change state delete to undo last state 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki ed60a8df0d help icon added and new icon on add button 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 2e06a7b699 minor changes to states view 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 2e90a50fd6 statedefinitions update and delete popup changes 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki f009f10afa changes to state definitions list 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 70061bcc78 statedefinitions edit popup added and url changes 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 4f77ce1e7e state button rework and warning if same state 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki e646cdfa56 current state helper function added 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 268666909f Device-details html modularized into several files 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki fdcef2216e statedefinitions list updated 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 97af732df6 updated logging for states 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 316637e3ab statedefinitions delete now uses correct id 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 69f51ccad0 logging for statedefinitions 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 508cd034d1 better delete modal 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 9baae6293f erase_server type now userproperty 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 23b253cb89 deleted obsolete field type for sysproperties 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 9000826d68 updated views for new model structure 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki f872a7ec52 more property model refactoring 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki a83792a838 property models refactor 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 5c4d2bd6a9 added logging for user property creation 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 0d9dc90362 made log var a env variable 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 846166bd9f added loggin for state actions 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 80d282f054 added state delete 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 5c2d59aa1a changed tab to log and added logging 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki fc5532b55b changed state visual and ordering 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki fe171b8d9d minor fix for correct tab handling 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 5b33bf000f added current state to device details 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 6bd822f732 added view and url for new action 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 2f718dfb92 state institution now nullable 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 1634437ae7 condition checking and renaming 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 603d31161a stylish new popup for state change 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 6b5056957f added Sortable js dependency for tables 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 2ee1396eae added form for state definition order update 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 76ada577c0 minor cosmetic changes 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki bc0e7e8f41 changed delete button to icon 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 50c1662a31 send button hidden until changes are made 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 2fd2ed3e36 sortable list now updates order of definitions 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 6301edf4bb added Sortable js for state definitions list 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 97d1900e44 added model level constraints 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 3bb1145a10 modals for state definition deletion 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 127f6fb42f delete view added and some refactorig 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 8b7782a61c added add state definition view 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 5c64c86a57 changed constrain on model 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 1fc3c07816 fixed models confusion 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 7d99ccc4f9 added model constraints 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki ad01b4fefc added admin state definition panel 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 47b721ed6e initial orm state models 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 3a8b686177 centered popup modals 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 945d97afd6 disabled lotPoperty field 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki c3aa13a423 added logging for device operations 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 30e657fd79 renaming lotAnnotations to Lotproperty 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki cdcf39296e renaming of annotation to property 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki fbee2849c9 change edit view to modal popup 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki f4284894fe added userproperty update view 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 142edac285 added user_property 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 5be16a8e05 model constraints changed and moved url to /device 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 56c6bb69ac fixed search and moved delete user property class 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 03ddd9b8d9 renaming to new Property tables 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 1dba811943 details view changed to now use properties 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 23f732a04e fixed user_properties list not working 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki dbff63eed6 renaming to property 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki ade8898fca changed imports 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 279d20b9d0 fixed self inflicted recursion 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki 1612567bf6 renaming annotation to variable 2025-01-23 12:36:29 +01:00
Thomas Nahuel Rusiecki b1ed04e956 variables and function semantic renaming 2025-01-23 12:36:24 +01:00
Thomas Nahuel Rusiecki af5418d2a0 split into system and user properties 2025-01-23 12:33:17 +01:00
Thomas Nahuel Rusiecki 1b6af509e1 annotations renaming on views 2025-01-23 12:33:17 +01:00
64 changed files with 891 additions and 1001 deletions

View file

@ -1,2 +0,0 @@
db
.git

View file

@ -1,17 +0,0 @@
# repository editor configuration normalization
# @see http://editorconfig.org/
# This is the top-most .editorconfig file; do not search in parent directories.
root = true
# All files.
[*]
end_of_line = LF
indent_style = space
indent_size = 4
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.sh]
indent_size = 8

View file

@ -1,15 +1,5 @@
#### DH_DOMAIN=localhost
# DEV OPTIONS DH_PORT=8000
####
DEV_DOCKER_ALWAYS_BUILD=false
####
# DEVICEHUB
####
DEVICEHUB_DOMAIN=localhost
DEVICEHUB_PORT=8001
DEMO=true DEMO=true
# note that with DEBUG=true, logs are more verbose (include tracebacks) # note that with DEBUG=true, logs are more verbose (include tracebacks)
DEBUG=true DEBUG=true
@ -26,48 +16,7 @@ EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend"
EMAIL_FILE_PATH="/tmp/app-messages" EMAIL_FILE_PATH="/tmp/app-messages"
ENABLE_EMAIL=false ENABLE_EMAIL=false
PREDEFINED_TOKEN='5018dd65-9abd-4a62-8896-80f34ac66150' PREDEFINED_TOKEN='5018dd65-9abd-4a62-8896-80f34ac66150'
DEVICEHUB_ALLOWED_HOSTS=${DEVICEHUB_DOMAIN},${DEVICEHUB_DOMAIN}:${DEVICEHUB_PORT},127.0.0.1,127.0.0.1:${DEVICEHUB_PORT} DH_ALLOWED_HOSTS=${DH_DOMAIN},${DH_DOMAIN}:${DH_PORT},127.0.0.1,127.0.0.1:${DH_PORT}
# TODO review these vars # TODO review these vars
#SNAPSHOTS_DIR=/path/to/TODO #SNAPSHOTS_DIR=/path/to/TODO
#EVIDENCES_DIR=/path/to/TODO #EVIDENCES_DIR=/path/to/TODO
#DEMO_IDHUB_DOMAIN='idhub.example.org'
####
# IDHUB
####
IDHUB_ENABLED=false
IDHUB_DOMAIN=localhost
IDHUB_PORT=9001
IDHUB_ALLOWED_HOSTS=${IDHUB_DOMAIN},${IDHUB_DOMAIN}:${IDHUB_PORT},127.0.0.1,127.0.0.1:${IDHUB_PORT}
IDHUB_TIME_ZONE='Europe/Madrid'
#IDHUB_SECRET_KEY='uncomment-it-and-fill-this'
# enable dev flags when DEVELOPMENT deployment
# adapt to your domain in a production/reverse proxy env
IDHUB_CSRF_TRUSTED_ORIGINS='https://idhub.example.org'
# fill this section with your email credentials
IDHUB_DEFAULT_FROM_EMAIL="user@example.org"
IDHUB_EMAIL_HOST="smtp.example.org"
IDHUB_EMAIL_HOST_USER="smtp_user"
IDHUB_EMAIL_HOST_PASSWORD="smtp_passwd"
IDHUB_EMAIL_PORT=25
IDHUB_EMAIL_USE_TLS=True
IDHUB_EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend"
# replace with production data
# this is used when IDHUB_DEPLOYMENT is not equal to DEVELOPMENT
IDHUB_ADMIN_USER='admin'
IDHUB_ADMIN_PASSWD='admin'
IDHUB_ADMIN_EMAIL='admin@example.org'
# this option needs to be set to 'n' to be able to make work idhub in docker
# by default it is set to 'y' to facilitate idhub dev when outside docker
IDHUB_SYNC_ORG_DEV='n'
# TODO that is only for testing
IDHUB_ENABLE_EMAIL=false
IDHUB_ENABLE_2FACTOR_AUTH=false
IDHUB_ENABLE_DOMAIN_CHECKER=false
IDHUB_PREDEFINED_TOKEN='27f944ce-3d58-4f48-b068-e4aa95f97c95'

5
.gitignore vendored
View file

@ -1,7 +1,4 @@
db.sqlite3
env/ env/
__pycache__/ __pycache__/
.env .env
# the following could be autogenerated by devicehub
db.sqlite3
example/snapshots/snapshot_workbench-script_verifiable-credential.json

View file

@ -22,7 +22,6 @@ class State(models.Model):
def __str__(self): def __str__(self):
return f"{self.institution.name} - {self.state} - {self.snapshot_uuid}" return f"{self.institution.name} - {self.state} - {self.snapshot_uuid}"
class StateDefinition(models.Model): class StateDefinition(models.Model):
institution = models.ForeignKey(Institution, on_delete=models.CASCADE) institution = models.ForeignKey(Institution, on_delete=models.CASCADE)

View file

@ -2,7 +2,6 @@ from django.views import View
from django.shortcuts import redirect, get_object_or_404 from django.shortcuts import redirect, get_object_or_404
from django.contrib import messages from django.contrib import messages
from action.forms import ChangeStateForm, AddNoteForm from action.forms import ChangeStateForm, AddNoteForm
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import DeleteView, CreateView, UpdateView, FormView from django.views.generic.edit import DeleteView, CreateView, UpdateView, FormView
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -10,7 +9,7 @@ from action.models import State, StateDefinition, Note, DeviceLog
from device.models import Device from device.models import Device
class ChangeStateView(LoginRequiredMixin, FormView): class ChangeStateView(FormView):
form_class = ChangeStateForm form_class = ChangeStateForm
def form_valid(self, form): def form_valid(self, form):
@ -43,7 +42,7 @@ class ChangeStateView(LoginRequiredMixin, FormView):
return self.request.META.get('HTTP_REFERER') or reverse_lazy('device:details') return self.request.META.get('HTTP_REFERER') or reverse_lazy('device:details')
class AddNoteView(LoginRequiredMixin, FormView): class AddNoteView(FormView):
form_class = AddNoteForm form_class = AddNoteForm
def form_valid(self, form): def form_valid(self, form):
@ -74,7 +73,7 @@ class AddNoteView(LoginRequiredMixin, FormView):
return self.request.META.get('HTTP_REFERER') or reverse_lazy('device:details') return self.request.META.get('HTTP_REFERER') or reverse_lazy('device:details')
class UpdateNoteView(LoginRequiredMixin, UpdateView): class UpdateNoteView(UpdateView):
model = Note model = Note
fields = ['description'] fields = ['description']
pk_url_kwarg = 'pk' pk_url_kwarg = 'pk'
@ -106,7 +105,7 @@ class UpdateNoteView(LoginRequiredMixin, UpdateView):
return self.request.META.get('HTTP_REFERER', reverse_lazy('device:details')) return self.request.META.get('HTTP_REFERER', reverse_lazy('device:details'))
class DeleteNoteView(LoginRequiredMixin, View): class DeleteNoteView(View):
model = Note model = Note
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):

View file

@ -169,8 +169,7 @@ class AddStateDefinitionView(AdminView, StateDefinitionContextMixin, CreateView)
return self.form_invalid(form) return self.form_invalid(form)
def form_invalid(self, form): def form_invalid(self, form):
super().form_invalid(form) return super().form_invalid(form)
return redirect(self.success_url)
class DeleteStateDefinitionView(AdminView, StateDefinitionContextMixin, SuccessMessageMixin, DeleteView): class DeleteStateDefinitionView(AdminView, StateDefinitionContextMixin, SuccessMessageMixin, DeleteView):
@ -219,21 +218,14 @@ class UpdateStateDefinitionView(AdminView, UpdateView):
model = StateDefinition model = StateDefinition
template_name = 'states_panel.html' template_name = 'states_panel.html'
fields = ['state'] fields = ['state']
success_url = reverse_lazy('admin:states_panel')
pk_url_kwarg = 'pk' pk_url_kwarg = 'pk'
def get_queryset(self): def get_queryset(self):
return StateDefinition.objects.filter(institution=self.request.user.institution) return StateDefinition.objects.filter(institution=self.request.user.institution)
def form_valid(self, form): def get_success_url(self):
try:
response = super().form_valid(form)
messages.success(self.request, _("State definition updated successfully.")) messages.success(self.request, _("State definition updated successfully."))
return response return reverse_lazy('admin:states_panel')
except IntegrityError:
messages.error(self.request, _("State is already defined."))
return self.form_invalid(form)
def form_invalid(self, form): def form_valid(self, form):
super().form_invalid(form) return super().form_valid(form)
return redirect(self.get_success_url())

View file

@ -90,7 +90,7 @@ class NewSnapshotView(ApiMixing):
ev_uuid = data["credentialSubject"].get("uuid") ev_uuid = data["credentialSubject"].get("uuid")
if not ev_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) logger.error("%s", txt)
return JsonResponse({'status': txt}, status=500) return JsonResponse({'status': txt}, status=500)
@ -118,7 +118,7 @@ class NewSnapshotView(ApiMixing):
prop = SystemProperty.objects.filter( prop = SystemProperty.objects.filter(
uuid=ev_uuid, uuid=ev_uuid,
# TODO this is hardcoded, it should select the user preferred algorithm # TODO this is hardcoded, it should select the user preferred algorithm
key="ereuse24", key="hidalgo1",
owner=self.tk.owner.institution owner=self.tk.owner.institution
).first() ).first()

View file

@ -9,6 +9,12 @@
<h3>{{ subtitle }}</h3> <h3>{{ subtitle }}</h3>
</div> </div>
<div class="col text-center"> <div class="col text-center">
{% if lot %}
<a href="{% url 'lot:documents' object.id %}" type="button" class="btn btn-green-admin">
<i class="bi bi-folder2"></i>
{% trans 'Documents' %}
</a>
{% endif %}
<a href="{# url 'dashboard:exports' object.id #}" type="button" class="btn btn-green-admin"> <a href="{# url 'dashboard:exports' object.id #}" type="button" class="btn btn-green-admin">
<i class="bi bi-reply"></i> <i class="bi bi-reply"></i>
{% trans 'Exports' %} {% trans 'Exports' %}

View file

@ -71,6 +71,17 @@ class Device:
) )
return user_properties return user_properties
def get_user_documents(self):
if not self.uuids:
self.get_uuids()
user_properties = UserProperty.objects.filter(
uuid__in=self.uuids,
owner=self.owner,
type=UserProperty.Type.DOCUMENT
)
return user_properties
def get_uuids(self): def get_uuids(self):
for a in self.get_properties(): for a in self.get_properties():
if a.uuid not in self.uuids: if a.uuid not in self.uuids:
@ -149,7 +160,7 @@ class Device:
ORDER BY ORDER BY
CASE CASE
WHEN t1.key = 'CUSTOM_ID' THEN 1 WHEN t1.key = 'CUSTOM_ID' THEN 1
WHEN t1.key = 'ereuse24' THEN 2 WHEN t1.key = 'hidalgo1' THEN 2
ELSE 3 ELSE 3
END, END,
t1.created DESC t1.created DESC
@ -195,7 +206,7 @@ class Device:
ORDER BY ORDER BY
CASE CASE
WHEN t1.key = 'CUSTOM_ID' THEN 1 WHEN t1.key = 'CUSTOM_ID' THEN 1
WHEN t1.key = 'ereuse24' THEN 2 WHEN t1.key = 'hidalgo1' THEN 2
ELSE 3 ELSE 3
END, END,
t1.created DESC t1.created DESC
@ -230,7 +241,7 @@ class Device:
ORDER BY ORDER BY
CASE CASE
WHEN t1.key = 'CUSTOM_ID' THEN 1 WHEN t1.key = 'CUSTOM_ID' THEN 1
WHEN t1.key = 'ereuse24' THEN 2 WHEN t1.key = 'hidalgo1' THEN 2
ELSE 3 ELSE 3
END, END,
t1.created DESC t1.created DESC
@ -294,6 +305,7 @@ class Device:
@property @property
def version(self): def version(self):
if not self.last_evidence:
self.get_last_evidence() self.get_last_evidence()
return self.last_evidence.get_version() return self.last_evidence.get_version()
@ -301,8 +313,3 @@ class Device:
def components(self): def components(self):
self.get_last_evidence() self.get_last_evidence()
return self.last_evidence.get_components() return self.last_evidence.get_components()
@property
def did_document(self):
self.get_last_evidence()
return self.last_evidence.get_did_document()

View file

@ -2,6 +2,108 @@
{% load i18n %} {% load i18n %}
{% block content %} {% block content %}
<div class="position-fixed" style="bottom: 2rem; right: 2rem; z-index: 9999; display: flex; gap: 0.5rem;">
<button class="btn btn-yellow d-flex align-items-center shadow" type="button"
data-bs-toggle="offcanvas" data-bs-target="#notesOffcanvas" aria-controls="notesOffcanvas"
data-bs-toggle="tooltip" data-bs-placement="left" title="{% trans 'View recent notes' %}">
<i class="bi bi-journal-text me-1"></i>
{% trans "Journal" %}
</button>
</div>
<!-- side panel for latest notes -->
<div class="offcanvas offcanvas-end" tabindex="-1" id="notesOffcanvas" aria-labelledby="notesOffcanvasLabel">
<div class="offcanvas-header">
<h5 id="notesOffcanvasLabel">{% trans "Latest Notes" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body" style="margin-bottom: 5rem;">
{% for note in device_notes|slice:":4" %}
<div class="card mb-3 shadow-sm">
<div class="card-body">
<div>
<small class="text-muted">
{{ note.date|timesince }} {% trans "ago" %}
</small>
{% if user == note.user or user.is_admin %}
<span class="badge bg-yellow text-dark ms-2">{% trans "Editable" %}</span>
</div>
<blockquote
class="blockquote mt-2 p-2 bg-light fst-italic"
contenteditable="true"
style="font-size: 1.2em!important"
data-note-id="{{ note.id }}"
title="{% trans 'Click to edit this note' %}"
oninput="toggleSaveLink(this)">
{% else %}
</div>
<blockquote style="font-size: 1.2em!important" class="blockquote mt-2 p-2 fst-italic">
{% endif %}
<p data-note-id="{{ note.id }}">
{{ note.description }}
</p>
<footer class="blockquote-footer text-end mt-2" contenteditable="false">
<small>{{ note.user.get_full_name|default:note.user.username }}</small>
</footer>
</blockquote>
{% if user == note.user or user.is_admin %}
<div class="d-flex justify-content-end align-items-center">
<!-- update note button -->
<form
id="updateNoteForm{{ note.id }}"
method="post"
action="{% url 'action:update_note' note.id %}"
class="d-inline"
>
{% csrf_token %}
<input type="hidden" name="description" id="descriptionInput{{ note.id }}" value="">
<a
type="submit"
id="saveLink{{ note.id }}"
class="text-muted disabled me-4 border border-light rounded"
style="pointer-events: none;"
title="{% trans 'Save changes' %}"
onclick="submitUpdatedNote('{{ note.id }}'); return false;"
>
<i class="fas fa-save px-1"></i>
</a>
</form>
<!-- delete note button -->
<button type="button" class="btn btn-link btn-outline-danger btn-sm text-danger" id="deleteIcon{{ note.id }}" title="{% trans 'Delete note' %}" data-bs-toggle="collapse" data-bs-target="#confirmDelete{{ note.id }}">
<i class="bi bi-trash"></i>
</button>
</div>
<form class="d-inline" method="post" action="{% url 'action:delete_note' note.id %}">
{% csrf_token %}
<div class="collapse mt-2" id="confirmDelete{{ note.id }}">
<div class="card card-body border border-danger text-center">
<p class="mb-2">{% trans 'Are you sure you want to delete this note?' %}</p>
<a
href="#"
class="btn btn-sm btn-outline-danger"
onclick="submitDeleteForm({{ note.id }}); return false;"
>
{% trans 'Confirm delete' %}
</a>
</div>
</div>
</form>
{% endif %}
</div>
</div>
{% empty %}
<p>{% trans "No notes available." %}</p>
{% endfor %}
</div>
</div>
<!-- Top bar buttons --> <!-- Top bar buttons -->
<div class="row"> <div class="row">
<div class="col"> <div class="col">
@ -64,7 +166,13 @@
<a href="#details" class="nav-link active" data-bs-toggle="tab" data-bs-target="#details">{% trans 'General details' %}</a> <a href="#details" class="nav-link active" data-bs-toggle="tab" data-bs-target="#details">{% trans 'General details' %}</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="#user_properties" class="nav-link" data-bs-toggle="tab" data-bs-target="#user_properties">{% trans 'Properties' %}</a> <a href="#log" class="nav-link" data-bs-toggle="tab" data-bs-target="#log">{% trans 'Log' %}</a>
</li>
<li class="nav-item">
<a href="#user_properties" class="nav-link" data-bs-toggle="tab" data-bs-target="#user_properties">{% trans 'User properties' %}</a>
</li>
<li class="nav-item">
<a href="#documents" class="nav-link" data-bs-toggle="tab" data-bs-target="#documents">{% trans 'Documents' %}</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="#lots" class="nav-link" data-bs-toggle="tab" data-bs-target="#lots">{% trans 'Lots' %}</a> <a href="#lots" class="nav-link" data-bs-toggle="tab" data-bs-target="#lots">{% trans 'Lots' %}</a>
@ -83,9 +191,6 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'device:device_web' object.id %}" target="_blank">Web</a> <a class="nav-link" href="{% url 'device:device_web' object.id %}" target="_blank">Web</a>
</li> </li>
<li class="nav-item">
<a href="#log" class="nav-link" data-bs-toggle="tab" data-bs-target="#log">{% trans 'Log' %}</a>
</li>
</ul> </ul>
</div> </div>
</div> </div>
@ -97,6 +202,8 @@
{% include 'tabs/user_properties.html' %} {% include 'tabs/user_properties.html' %}
{% include 'tabs/documents.html' %}
{% include 'tabs/lots.html' %} {% include 'tabs/lots.html' %}
{% include 'tabs/components.html' %} {% include 'tabs/components.html' %}
@ -149,5 +256,27 @@
} }
} }
}) })
//Enable save button on note if changes are made to it
function toggleSaveLink(blockquoteElem) {
const saveLink = document.getElementById("saveLink" + blockquoteElem.dataset.noteId);
saveLink.classList.remove("disabled", "text-muted", "border-light");
saveLink.classList.add("text-success", "border-success");
saveLink.style.pointerEvents = "auto";
}
//updates note-update-form with new value from blockquote
function submitUpdatedNote(noteId) {
const noteParagraph = document.querySelector('p[data-note-id="' + noteId + '"]');
const newText = noteParagraph.innerText.trim();
const descriptionField = document.getElementById('descriptionInput' + noteId);
descriptionField.value = newText;
document.getElementById('updateNoteForm' + noteId).submit();
}
//simpler are u sure? confirmation message
function submitDeleteForm(noteId) {
document.getElementById('confirmDelete' + noteId).closest('form').submit();
}
</script> </script>
{% endblock %} {% endblock %}

View file

@ -36,7 +36,7 @@
{% endfor %} {% endfor %}
</div> </div>
<div class="container"> <div class="container">
<a class="btn btn-grey" href="{% url 'device:details' pk %}#user_properties">{% translate "Cancel" %}</a> <a class="btn btn-grey" href="{% url 'dashboard:unassigned_devices' %}">{% translate "Cancel" %}</a>
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" /> <input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
</div> </div>

View file

@ -0,0 +1,251 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col">
<h3>{{ object.pk }}</h3>
</div>
</div>
<div class="row">
<div class="col">
<div class="nav nav-tabs nav-tabs-bordered">
<li class="nav-items">
<a class="nav-link" href="{% url 'device:details' device.pk %}">General details</a>
</li>
<li class="nav-items">
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#physicalproperties">Physical properties</button>
</li>
<li class="nav-items">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#documents">Documents</button>
</li>
<li class="nav-items">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#lots">Lots</button>
</li>
<li class="nav-items">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#status">Status</button>
</li>
<li class="nav-items">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#components">Components</button>
</li>
<li class="nav-items">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#traceabiliy">Traceability log</button>
</li>
<li class="nav-items">
<a class="nav-link" href="">Web</a>
</li>
</div>
</div>
</div>
<div class="tab-content pt-2">
<div class="tab-pane fade profile-overview" id="details">
<h5 class="card-title">Details</h5>
<div class="row mb-3">
<div class="col-lg-3 col-md-4 label ">
(<a href="/inventory/device/edit/4W8D3/">Edit Device</a>)
</div>
<div class="col-lg-9 col-md-8">
{%if object.hid %}Snapshot{% else %}Placeholder{% endif %}
</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label ">Phid</div>
<div class="col-lg-9 col-md-8">{{ object.id }}</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label ">Id device internal</div>
<div class="col-lg-9 col-md-8"></div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label ">Type</div>
<div class="col-lg-9 col-md-8">{{ object.type }}</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label">Manufacturer</div>
<div class="col-lg-9 col-md-8">{{ object.manufacturer|default:"" }}</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label">Model</div>
<div class="col-lg-9 col-md-8">{{ object.model|default:"" }}</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label">Part Number</div>
<div class="col-lg-9 col-md-8">{{ object.part_number|default:"" }}</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label">Serial Number</div>
<div class="col-lg-9 col-md-8">{{ object.serial_number|default:"" }}</div>
</div>
</div>
<div class="tab-pane fade show active" id="physicalproperties">
<h5 class="card-title">Physical Properties</h5>
<div class="row mb-3">
<div class="col-lg-3 col-md-4 label ">
(<a href="{% url 'device:physical_edit' object.pk %}">Edit Physical Properties</a>)
</div>
</div>
<div class="row mb-3">
<div class="col-lg-3 col-md-4 label ">
{% load django_bootstrap5 %}
<form role="form" method="post">
{% csrf_token %}
{% if form.errors %}
<div class="alert alert-danger alert-icon alert-icon-border alert-dismissible" role="alert">
<div class="icon"><span class="mdi mdi-close-circle-o"></span></div>
<div class="message">
{% for field, error in form.errors.items %}
{{ error }}<br />
{% endfor %}
<button class="btn-close" type="button" data-dismiss="alert" aria-label="Close"></button>
</div>
</div>
{% endif %}
{% bootstrap_form form %}
<div class="form-actions-no-box">
<a class="btn btn-grey" href="{% url 'device:details' device.pk %}">{% translate "Cancel" %}</a>
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
</div>
</form>
</div>
</div>
</div>
<div class="tab-pane fade profile-overview" id="lots">
<h5 class="card-title">Incoming Lots</h5>
<div class="row">
</div>
<h5 class="card-title">Outgoing Lots</h5>
<div class="row">
</div>
<h5 class="card-title">Temporary Lots</h5>
<div class="row">
</div>
</div>
<div class="tab-pane fade profile-overview" id="documents">
<div class="btn-group dropdown ml-1 mt-1" uib-dropdown="">
<a href="/inventory/device/4W8D3/document/add/" class="btn btn-primary">
<i class="bi bi-plus"></i>
Add new document
<span class="caret"></span>
</a>
</div>
<h5 class="card-title">Documents</h5>
<table class="table">
<thead>
<tr>
<th scope="col">File</th>
<th scope="col">Type</th>
<th scope="col">Description</th>
<th scope="col" data-type="date" data-format="YYYY-MM-DD hh:mm">Uploaded on</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div class="tab-pane fade profile-overview" id="status">
<h5 class="card-title">Status Details</h5>
<div class="row">
<div class="col-lg-3 col-md-4 label">Physical State</div>
<div class="col-lg-9 col-md-8">
</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label">Lifecycle State</div>
<div class="col-lg-9 col-md-8">
</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label">Allocated State</div>
<div class="col-lg-9 col-md-8">
</div>
</div>
</div>
<div class="tab-pane fade profile-overview" id="traceability">
<h5 class="card-title">Traceability log Details</h5>
<div class="list-group col-6">
<div class="list-group-item d-flex justify-content-between align-items-center">
Snapshot ✓
<small class="text-muted">14:07 23-06-2024</small>
</div>
<div class="list-group-item d-flex justify-content-between align-items-center">
EraseCrypto ✓
<small class="text-muted">14:07 23-06-2024</small>
</div>
<div class="list-group-item d-flex justify-content-between align-items-center">
EraseCrypto ✓
<small class="text-muted">14:07 23-06-2024</small>
</div>
</div>
</div>
<div class="tab-pane fade profile-overview" id="components">
<h5 class="card-title">Components Snapshot</h5>
<div class="list-group col-6">
<div class="list-group-item">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">Motherboard</h5>
<small class="text-muted">14:07 23-06-2024</small>
</div>
<p class="mb-1">
hp<br />
890e<br />
</p>
<small class="text-muted">
</small>
</div>
<div class="list-group-item">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">NetworkAdapter</h5>
<small class="text-muted">14:07 23-06-2024</small>
</div>
<p class="mb-1">
realtek semiconductor co., ltd.<br />
rtl8852ae 802.11ax pcie wireless network adapter<br />
</p>
<small class="text-muted">
</small>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,49 @@
{% load i18n %}
<div class="tab-pane fade" id="documents">
<div class="btn-group mt-1 mb-3">
<a href="{% url 'device:add_document' object.pk %}" class="btn btn-primary">
<i class="bi bi-plus">
</i>
{% trans 'Add new document' %}
</a>
</div>
<h5 class="card-title">{% trans 'Documents' %}
</h5>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">
{% trans 'Key' %}
</th>
<th scope="col">
{% trans 'Value' %}
</th>
<th scope="col" data-type="date" data-format="YYYY-MM-DD HH:mm">
{% trans 'Created on' %}
</th>
<th>
</th>
<th>
</th>
</tr>
</thead>
<tbody>
{% for a in object.get_user_documents %}
<tr>
<td>{{ a.key }}
</td>
<td>{{ a.value }}
</td>
<td>{{ a.created }}
</td>
<td>
</td>
<td>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>

View file

@ -1,34 +1,19 @@
{% load i18n %} {% load i18n %}
<div class="tab-pane fade" id="evidences"> <div class="tab-pane fade" id="evidences">
<h5 class="card-title">{% trans 'List of evidences' %}</h5> <h5 class="card-title">{% trans 'List of evidences' %}</h5>
<div class="list-group col"> <div class="list-group col-6">
<table class="table">
<thead>
<tr>
<th scope="col">uuid</th>
<th scope="col">Did Document</th>
<th scope="col">{% trans "Date" %}</th>
</tr>
</thead>
<tbody>
{% for snap in object.evidences %} {% for snap in object.evidences %}
<tr> <div class="list-group-item">
<td> <div class="d-flex w-100 justify-content-between">
<a href="{% url 'evidence:details' snap.uuid %}">{{ snap.uuid }}</a>
</td>
<td>
{% if snap.did_document %}
<a href="{{ snap.did_document }}" target="_blank">DID</a>
{% endif %}
</td>
<td>
<small class="text-muted">{{ snap.created }}</small> <small class="text-muted">{{ snap.created }}</small>
</td> </div>
</tr> <p class="mb-1">
<a href="{% url 'evidence:details' snap.uuid %}">{{ snap.uuid }}</a>
</p>
</div>
{% endfor %} {% endfor %}
</tbody> </div>
</table>
</div> </div>
</div> </div>

View file

@ -82,7 +82,7 @@
<div class="modal-footer justify-content-center"> <div class="modal-footer justify-content-center">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %} <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}
</button> </button>
<form method="post" action="{% url 'device:delete_user_property' object.id a.id %}"> <form method="post" action="{% url 'device:delete_user_property' a.id %}">
{% csrf_token %} {% csrf_token %}
<button type="submit" class="btn btn-danger">{% trans "Delete" %} <button type="submit" class="btn btn-danger">{% trans "Delete" %}
</button> </button>
@ -105,7 +105,7 @@
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form id="editForm{{ a.id }}" method="post" action="{% url 'device:update_user_property' object.id a.id %}"> <form id="editForm{{ a.id }}" method="post" action="{% url 'device:update_user_property' a.id %}">
{% csrf_token %} {% csrf_token %}
<div class="mb-3"> <div class="mb-3">
<label for="key" class="form-label">{% trans "Key" %} <label for="key" class="form-label">{% trans "Key" %}

View file

@ -7,11 +7,10 @@ urlpatterns = [
path("add/", views.NewDeviceView.as_view(), name="add"), path("add/", views.NewDeviceView.as_view(), name="add"),
path("edit/<str:pk>/", views.EditDeviceView.as_view(), name="edit"), path("edit/<str:pk>/", views.EditDeviceView.as_view(), name="edit"),
path("<str:pk>/", views.DetailsView.as_view(), name="details"), path("<str:pk>/", views.DetailsView.as_view(), name="details"),
path("<str:pk>/user_property/add", path("<str:pk>/user_property/add", views.AddUserPropertyView.as_view(), name="add_user_property"),
views.AddUserPropertyView.as_view(), name="add_user_property"), path("user_property/<int:pk>/delete", views.DeleteUserPropertyView.as_view(), name="delete_user_property"),
path("<str:device_id>/user_property/<int:pk>/delete", path("user_property/<int:pk>/update", views.UpdateUserPropertyView.as_view(), name="update_user_property"),
views.DeleteUserPropertyView.as_view(), name="delete_user_property"), path("<str:pk>/document/add", views.AddDocumentView.as_view(), name="add_document"),
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>/public/", views.PublicDeviceWebView.as_view(), name="device_web"),
] ]

View file

@ -1,9 +1,7 @@
import json import json
import logging import logging
from django.http import JsonResponse from django.http import JsonResponse
from django.conf import settings from django.conf import settings
from django.db import IntegrityError
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.contrib import messages from django.contrib import messages
from django.shortcuts import get_object_or_404, redirect, Http404 from django.shortcuts import get_object_or_404, redirect, Http404
@ -26,16 +24,6 @@ if settings.DPP:
from dpp.api_dlt import PROOF_TYPE from dpp.api_dlt import PROOF_TYPE
class DeviceLogMixin(DashboardView):
def log_registry(self, _uuid, msg):
DeviceLog.objects.create(
snapshot_uuid=_uuid,
event=msg,
user=self.request.user,
institution=self.request.user.institution
)
class NewDeviceView(DashboardView, FormView): class NewDeviceView(DashboardView, FormView):
template_name = "new_device.html" template_name = "new_device.html"
title = _("New Device") title = _("New Device")
@ -53,6 +41,35 @@ class NewDeviceView(DashboardView, FormView):
return response return response
# class AddToLotView(DashboardView, FormView):
# template_name = "list_lots.html"
# title = _("Add to lots")
# breadcrumb = "lot / add to lots"
# success_url = reverse_lazy('dashboard:unassigned_devices')
# form_class = LotsForm
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# lots = Lot.objects.filter(owner=self.request.user)
# lot_tags = LotTag.objects.filter(owner=self.request.user)
# context.update({
# 'lots': lots,
# 'lot_tags':lot_tags,
# })
# return context
# def get_form(self):
# form = super().get_form()
# form.fields["lots"].queryset = Lot.objects.filter(owner=self.request.user)
# return form
# def form_valid(self, form):
# form.devices = self.get_session_devices()
# form.save()
# response = super().form_valid(form)
# return response
class EditDeviceView(DashboardView, UpdateView): class EditDeviceView(DashboardView, UpdateView):
template_name = "new_device.html" template_name = "new_device.html"
title = _("Update Device") title = _("Update Device")
@ -98,24 +115,20 @@ class DetailsView(DashboardView, TemplateView):
uuid__in=self.object.uuids, uuid__in=self.object.uuids,
type=PROOF_TYPE["IssueDPP"] type=PROOF_TYPE["IssueDPP"]
) )
last_evidence = self.object.get_last_evidence() last_evidence= self.object.get_last_evidence(),
uuids = self.object.uuids uuid=self.object.last_uuid()
state_definitions = StateDefinition.objects.filter( state_definitions = StateDefinition.objects.filter(
institution=self.request.user.institution institution=self.request.user.institution
).order_by('order') ).order_by('order')
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')
context.update({ context.update({
'object': self.object, 'object': self.object,
'snapshot': last_evidence, 'snapshot': last_evidence,
'lot_tags': lot_tags, 'lot_tags': lot_tags,
'dpps': dpps, 'dpps': dpps,
"state_definitions": state_definitions, "state_definitions": state_definitions,
"device_states": device_states, "device_states": State.objects.filter(snapshot_uuid=uuid).order_by('-date'),
"device_logs": device_logs, "device_logs": DeviceLog.objects.filter(snapshot_uuid=uuid).order_by('-date'),
"device_notes": device_notes, "device_notes": Note.objects.filter(snapshot_uuid=uuid).order_by('-date'),
}) })
return context return context
@ -176,7 +189,7 @@ class PublicDeviceWebView(TemplateView):
return JsonResponse(device_data) return JsonResponse(device_data)
class AddUserPropertyView(DeviceLogMixin, CreateView): class AddUserPropertyView(DashboardView, CreateView):
template_name = "new_user_property.html" template_name = "new_user_property.html"
title = _("New User Property") title = _("New User Property")
breadcrumb = "Device / New Property" breadcrumb = "Device / New Property"
@ -189,107 +202,125 @@ class AddUserPropertyView(DeviceLogMixin, CreateView):
form.instance.uuid = self.property.uuid form.instance.uuid = self.property.uuid
form.instance.type = UserProperty.Type.USER form.instance.type = UserProperty.Type.USER
try: message = _("<Created> UserProperty: {}: {}".format(form.instance.key, form.instance.value))
response = super().form_valid(form) DeviceLog.objects.create(
messages.success(self.request, _("Property successfully added.")) snapshot_uuid=form.instance.uuid,
log_message = _("<Created> UserProperty: {}: {}".format( event=message,
form.instance.key, user=self.request.user,
form.instance.value institution=self.request.user.institution
)) )
self.log_registry(form.instance.uuid, log_message) messages.success(self.request, _("User property successfully added."))
response = super().form_valid(form)
return response return response
except IntegrityError:
messages.error(self.request, _("Property is already defined."))
return self.form_invalid(form)
def get_form_kwargs(self): def get_form_kwargs(self):
pk = self.kwargs.get('pk') pk = self.kwargs.get('pk')
institution = self.request.user.institution institution = self.request.user.institution
self.property = SystemProperty.objects.filter( self.property = get_object_or_404(SystemProperty, owner=institution, value=pk)
owner=institution, value=pk).first()
if not self.property:
raise Http404
return super().get_form_kwargs() return super().get_form_kwargs()
def get_success_url(self): def get_success_url(self):
pk = self.kwargs.get('pk') return reverse_lazy('device:details', args=[self.kwargs.get('pk')])
return reverse_lazy('device:details', args=[pk]) + "#user_properties"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['pk'] = self.kwargs.get('pk')
return context
class UpdateUserPropertyView(DeviceLogMixin, UpdateView): class UpdateUserPropertyView(DashboardView, UpdateView):
template_name = "new_user_property.html" template_name = "new_user_property.html"
title = _("Update User Property") title = _("Update User Property")
breadcrumb = "Device / Update Property" breadcrumb = "Device / Update Property"
model = UserProperty model = UserProperty
fields = ("key", "value") fields = ("key", "value")
def get_form_kwargs(self): def get_queryset(self):
pk = self.kwargs.get('pk') pk = self.kwargs.get('pk')
institution = self.request.user.institution institution = self.request.user.institution
self.object = get_object_or_404(UserProperty, owner=institution, pk=pk) return UserProperty.objects.filter(pk=pk, owner=institution)
self.old_key = self.object.key
self.old_value = self.object.value
return super().get_form_kwargs()
def form_valid(self, form): def form_valid(self, form):
old_instance = self.get_object()
old_key = old_instance.key
old_value = old_instance.value
form.instance.owner = self.request.user.institution
form.instance.user = self.request.user
form.instance.type = UserProperty.Type.USER
new_key = form.cleaned_data['key'] new_key = form.cleaned_data['key']
new_value = form.cleaned_data['value'] new_value = form.cleaned_data['value']
try: message = _("<Updated> UserProperty: {}: {} to {}: {}".format(old_key, old_value, new_key, new_value))
super().form_valid(form) DeviceLog.objects.create(
messages.success(self.request, _("Property updated successfully.")) snapshot_uuid=form.instance.uuid,
log_message = _("<Updated> UserProperty: {}: {} to {}: {}".format( event=message,
self.old_key, user=self.request.user,
self.old_value, institution=self.request.user.institution
new_key, )
new_value
))
self.log_registry(form.instance.uuid, log_message)
# return response
return redirect(self.get_success_url())
except IntegrityError:
messages.error(self.request, _("Property is already defined."))
return self.form_invalid(form)
def form_invalid(self, form): messages.success(self.request, _("User property updated successfully."))
super().form_invalid(form) return super().form_valid(form)
return redirect(self.get_success_url())
def get_success_url(self): def get_success_url(self):
pk = self.kwargs.get('device_id') return self.request.META.get('HTTP_REFERER', reverse_lazy('device:details', args=[self.object.pk]))
return reverse_lazy('device:details', args=[pk]) + "#user_properties"
class DeleteUserPropertyView(DeviceLogMixin, DeleteView): class DeleteUserPropertyView(DashboardView, DeleteView):
model = UserProperty model = UserProperty
def get_queryset(self): def get_queryset(self):
return UserProperty.objects.filter(owner=self.request.user.institution) 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
# with messages framework
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
pk = self.kwargs.get('pk') self.object = self.get_object()
institution = self.request.user.institution
self.object = get_object_or_404(UserProperty, owner=institution, pk=pk)
self.object.delete() self.object.delete()
msg = _("<Deleted> User Property: {}:{}".format( message = _("<Deleted> User Property: {}:{}".format(self.object.key, self.object.value ))
self.object.key, DeviceLog.objects.create(
self.object.value snapshot_uuid=self.object.uuid,
)) event=message,
self.log_registry(self.object.uuid, msg) user=self.request.user,
institution=self.request.user.institution
)
messages.info(self.request, _("User property deleted successfully.")) messages.info(self.request, _("User property deleted successfully."))
return self.handle_success()
def handle_success(self):
return redirect(self.get_success_url()) return redirect(self.get_success_url())
def get_success_url(self): def get_success_url(self):
pk = self.kwargs.get('device_id') return self.request.META.get('HTTP_REFERER', reverse_lazy('device:details', args=[self.object.pk]))
return reverse_lazy('device:details', args=[pk]) + "#user_properties"
class AddDocumentView(DashboardView, CreateView):
template_name = "new_user_property.html"
title = _("New Document")
breadcrumb = "Device / New document"
success_url = reverse_lazy('dashboard:unassigned_devices')
model = UserProperty
fields = ("key", "value")
def form_valid(self, form):
form.instance.owner = self.request.user.institution
form.instance.user = self.request.user
form.instance.uuid = self.property.uuid
form.instance.type = UserProperty.Type.DOCUMENT
response = super().form_valid(form)
return response
def get_form_kwargs(self):
pk = self.kwargs.get('pk')
institution = self.request.user.institution
self.property = SystemProperty.objects.filter(
owner=institution,
value=pk,
).first()
if not self.property:
raise Http404
self.success_url = reverse_lazy('device:details', args=[pk])
kwargs = super().get_form_kwargs()
return kwargs

View file

@ -78,13 +78,16 @@ INSTALLED_APPS = [
'django_extensions', 'django_extensions',
'django_bootstrap5', 'django_bootstrap5',
'django_tables2', 'django_tables2',
"rest_framework",
"login", "login",
"user", "user",
"device", "device",
"evidence", "evidence",
"lot",
"dashboard",
"action", "action",
"tag",
"lot",
"documents",
"dashboard",
"admin", "admin",
"api", "api",
] ]

View file

@ -1,59 +1,18 @@
services: services:
devicehub-django: devicehub-django:
init: true init: true
image: farga.pangea.org/ereuse/devicehub-django/latest
build: build:
context: .
dockerfile: docker/devicehub-django.Dockerfile dockerfile: docker/devicehub-django.Dockerfile
environment: environment:
- DEBUG=${DEBUG:-false} - DEBUG=${DEBUG:-false}
- DOMAIN=${DEVICEHUB_DOMAIN:-localhost} - DOMAIN=${DH_DOMAIN:-localhost}
- PORT=${DEVICEHUB_PORT:-8000} - PORT=${DH_PORT:-8000}
- ALLOWED_HOSTS=${DEVICEHUB_ALLOWED_HOSTS:-$DEVICEHUB_DOMAIN} - ALLOWED_HOSTS=${DH_ALLOWED_HOSTS:-$DH_DOMAIN}
- DEMO=${DEMO:-false} - DEMO=${DEMO:-false}
- DEMO_IDHUB_DOMAIN=${DEMO_IDHUB_DOMAIN:-}
- DEMO_IDHUB_PREDEFINED_TOKEN=${IDHUB_PREDEFINED_TOKEN:-}
- PREDEFINED_TOKEN=${PREDEFINED_TOKEN:-} - PREDEFINED_TOKEN=${PREDEFINED_TOKEN:-}
- DPP=${DPP:-false} - DPP=${DPP:-false}
# TODO manage volumes dev vs prod
volumes: volumes:
- .:/opt/devicehub-django - .:/opt/devicehub-django
ports: ports:
- ${DEVICEHUB_PORT:-8000}:${DEVICEHUB_PORT:-8000} - ${DH_PORT}:${DH_PORT}
# TODO add database service for idhub, meanwhile sqlite
idhub:
# https://docs.docker.com/compose/how-tos/profiles/
profiles: [idhub]
init: true
image: farga.pangea.org/ereuse/idhub/latest
environment:
- DOMAIN=${IDHUB_DOMAIN:-localhost}
- ALLOWED_HOSTS=${IDHUB_ALLOWED_HOSTS:-$IDHUB_DOMAIN}
- DEBUG=true
- DEMO=${DEMO:-false}
- INITIAL_ADMIN_EMAIL=${IDHUB_ADMIN_EMAIL}
- INITIAL_ADMIN_PASSWORD=${IDHUB_ADMIN_PASSWD}
- CREATE_TEST_USERS=true
- ENABLE_EMAIL=${IDHUB_ENABLE_EMAIL:-true}
- ENABLE_2FACTOR_AUTH=${IDHUB_ENABLE_2FACTOR_AUTH:-true}
- ENABLE_DOMAIN_CHECKER=${IDHUB_ENABLE_DOMAIN_CHECKER:-true}
- PREDEFINED_TOKEN=${IDHUB_PREDEFINED_TOKEN:-}
- SECRET_KEY=${IDHUB_SECRET_KEY:-publicsecretisnotsecureVtmKBfxpVV47PpBCF2Nzz2H6qnbd}
- STATIC_ROOT=${IDHUB_STATIC_ROOT:-/static/}
- MEDIA_ROOT=${IDHUB_MEDIA_ROOT:-/media/}
- PORT=${IDHUB_PORT:-9001}
- DEFAULT_FROM_EMAIL=${IDHUB_DEFAULT_FROM_EMAIL}
- EMAIL_HOST=${IDHUB_EMAIL_HOST}
- EMAIL_HOST_USER=${IDHUB_EMAIL_HOST_USER}
- EMAIL_HOST_PASSWORD=${IDHUB_EMAIL_HOST_PASSWORD}
- EMAIL_PORT=${IDHUB_EMAIL_PORT}
- EMAIL_USE_TLS=${IDHUB_EMAIL_USE_TLS}
- EMAIL_BACKEND=${IDHUB_EMAIL_BACKEND}
- SUPPORTED_CREDENTIALS=['Snapshot']
- SYNC_ORG_DEV=${IDHUB_SYNC_ORG_DEV}
ports:
- 9001:9001
# TODO add database service for idhub, meanwhile sqlite

View file

@ -19,24 +19,12 @@ main() {
cp -v .env.example .env cp -v .env.example .env
echo "WARNING: .env was not there, .env.example was copied, this only happens once" echo "WARNING: .env was not there, .env.example was copied, this only happens once"
fi fi
# load vars
. ./.env
if [ "${IDHUB_ENABLED:-}" = 'true' ]; then
export COMPOSE_PROFILES='idhub'
fi
# remove old database # remove old database
rm -vfr ./db/* rm -vfr ./db/*
# deactivate configured flag # deactivate configured flag
rm -vfr ./already_configured rm -vfr ./already_configured
docker compose down -v docker compose down -v
if [ "${DEV_DOCKER_ALWAYS_BUILD:-}" = 'true' ]; then
docker compose pull --ignore-buildable
docker compose build docker compose build
else
docker compose pull
fi
docker compose up ${detach_arg:-} docker compose up ${detach_arg:-}
} }

View file

@ -6,7 +6,6 @@ RUN apt update && \
python3-xapian \ python3-xapian \
git \ git \
sqlite3 \ sqlite3 \
curl \
jq \ jq \
time \ time \
vim \ vim \
@ -38,7 +37,6 @@ RUN pip install -i https://test.pypi.org/simple/ ereuseapitest==0.0.14
# Set PYTHONPATH to include the directory with the xapian module # Set PYTHONPATH to include the directory with the xapian module
ENV PYTHONPATH="${PYTHONPATH}:/usr/lib/python3/dist-packages" ENV PYTHONPATH="${PYTHONPATH}:/usr/lib/python3/dist-packages"
COPY . .
COPY docker/devicehub-django.entrypoint.sh / COPY docker/devicehub-django.entrypoint.sh /
RUN chown -R app:app /opt/devicehub-django RUN chown -R app:app /opt/devicehub-django

View file

@ -118,51 +118,6 @@ END
./manage.py dlt_register_user "${DATASET_FILE}" ./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
/usr/bin/time ./manage.py up_snapshots example/snapshots/ "${INIT_USER}"
}
config_phase() { config_phase() {
# TODO review this flag file # TODO review this flag file
init_flagfile="${program_dir}/already_configured" init_flagfile="${program_dir}/already_configured"
@ -177,7 +132,7 @@ config_phase() {
# 12, 13, 14 # 12, 13, 14
config_dpp_part1 config_dpp_part1
# cleanup other snapshots and copy dlt/dpp snapshots # cleanup other spnapshots and copy dlt/dpp snapshots
# TODO make this better # TODO make this better
rm example/snapshots/* rm example/snapshots/*
cp example/dpp-snapshots/*.json example/snapshots/ cp example/dpp-snapshots/*.json example/snapshots/
@ -185,7 +140,7 @@ config_phase() {
# # 15. Add inventory snapshots for user "${INIT_USER}". # # 15. Add inventory snapshots for user "${INIT_USER}".
if [ "${DEMO:-}" = 'true' ]; then if [ "${DEMO:-}" = 'true' ]; then
run_demo /usr/bin/time ./manage.py up_snapshots example/snapshots/ "${INIT_USER}"
fi fi
# remain next command as the last operation for this if conditional # remain next command as the last operation for this if conditional

0
documents/__init__.py Normal file
View file

3
documents/admin.py Normal file
View file

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

6
documents/apps.py Normal file
View file

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

View file

3
documents/models.py Normal file
View file

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

3
documents/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
documents/views.py Normal file
View file

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

View file

@ -30,7 +30,8 @@ class UploadForm(forms.Form):
try: try:
file_json = json.loads(file_data) file_json = json.loads(file_data)
snap = Build(file_json, None, check=True) build = Build
snap = build(file_json, None, check=True)
exists_property = SystemProperty.objects.filter( exists_property = SystemProperty.objects.filter(
uuid=snap.uuid uuid=snap.uuid
).first() ).first()
@ -71,6 +72,7 @@ class UserTagForm(forms.Form):
self.pk = None self.pk = None
self.uuid = kwargs.pop('uuid', None) self.uuid = kwargs.pop('uuid', None)
self.user = kwargs.pop('user') self.user = kwargs.pop('user')
instance = SystemProperty.objects.filter( instance = SystemProperty.objects.filter(
uuid=self.uuid, uuid=self.uuid,
key='CUSTOM_ID', key='CUSTOM_ID',
@ -88,6 +90,7 @@ class UserTagForm(forms.Form):
if not data: if not data:
return False return False
self.tag = data self.tag = data
self.instance = SystemProperty.objects.filter( self.instance = SystemProperty.objects.filter(
uuid=self.uuid, uuid=self.uuid,
key='CUSTOM_ID', key='CUSTOM_ID',
@ -103,15 +106,18 @@ class UserTagForm(forms.Form):
if self.instance: if self.instance:
old_value = self.instance.value old_value = self.instance.value
if not self.tag: if not self.tag:
message =_("<Deleted> Evidence Tag. Old Value: '{}'").format(old_value) message = _("<Deleted> Evidence Tag. Old Value: '{}'").format(
old_value
)
self.instance.delete() self.instance.delete()
else: else:
self.instance.value = self.tag self.instance.value = self.tag
self.instance.save() self.instance.save()
if old_value != self.tag: if old_value != self.tag:
message=_("<Updated> Evidence Tag. Old Value: '{}'. New Value: '{}'").format(old_value, self.tag) msg = "<Updated> Evidence Tag. Old Value: '{}'. New Value: '{}'"
message=_(msg).format(old_value, self.tag)
else: else:
message =_("<Created> Evidence Tag. Value: '{}'").format(self.tag) message = _("<Created> Evidence Tag. Value: '{}'").format(self.tag)
SystemProperty.objects.create( SystemProperty.objects.create(
uuid=self.uuid, uuid=self.uuid,
key='CUSTOM_ID', key='CUSTOM_ID',
@ -128,6 +134,7 @@ class UserTagForm(forms.Form):
) )
class ImportForm(forms.Form): class ImportForm(forms.Form):
file_import = forms.FileField(label=_("File to import")) file_import = forms.FileField(label=_("File to import"))
@ -176,8 +183,8 @@ class ImportForm(forms.Form):
table = [] table = []
for row in self.rows: for row in self.rows:
doc = create_doc(row) doc = create_doc(row)
property = create_property(doc, self.user) prop = create_property(doc, self.user)
table.append((doc, property)) table.append((doc, prop))
if commit: if commit:
for doc, cred in table: for doc, cred in table:

View file

@ -4,6 +4,7 @@ import logging
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.conf import settings
from utils.save_snapshots import move_json, save_in_disk from utils.save_snapshots import move_json, save_in_disk
from evidence.parse import Build from evidence.parse import Build

View file

@ -1,19 +0,0 @@
# Generated by Django 5.0.6 on 2025-01-29 11:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("evidence", "0004_remove_userproperty_user_unique_type_key_uuid"),
]
operations = [
migrations.AlterField(
model_name="userproperty",
name="type",
field=models.SmallIntegerField(
choices=[(1, "User"), (2, "EraseServer")], default=1
),
),
]

View file

@ -1,20 +0,0 @@
# Generated by Django 5.0.6 on 2025-01-30 17:52
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evidence', '0005_alter_userproperty_type'),
('user', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddConstraint(
model_name='userproperty',
constraint=models.UniqueConstraint(fields=('key', 'uuid'), name='userproperty_unique_type_key_uuid'),
),
]

View file

@ -31,13 +31,11 @@ class BuildMix:
return hid return hid
def generate_chids(self): def generate_chids(self):
self.algorithms = {} self.algorithms = {
for k in ALGOS.keys(): 'hidalgo1': self.get_hid('hidalgo1'),
if not settings.DPP and k == 'ereuse22': }
continue if settings.DPP:
self.algorithms["legacy_dpp"] = self.get_hid("legacy_dpp")
self.algorithms[k] = self.get_hid(k)
def get_doc(self): def get_doc(self):
self._get_components() self._get_components()
@ -45,7 +43,7 @@ class BuildMix:
c.pop("actions", None) c.pop("actions", None)
components = sorted(self.components, key=lambda x: x.get("type")) components = sorted(self.components, key=lambda x: x.get("type"))
device = self.algorithms.get('ereuse22') device = self.algorithms.get('legacy_dpp')
doc = [("computer", device)] doc = [("computer", device)]
@ -53,7 +51,7 @@ class BuildMix:
doc.append((c.get("type"), self.get_id_hw_dpp(c))) doc.append((c.get("type"), self.get_id_hw_dpp(c)))
def get_id_hw_dpp(self, d): def get_id_hw_dpp(self, d):
algorithm = ALGOS.get("ereuse22", []) algorithm = ALGOS.get("legacy_dpp", [])
hid = "" hid = ""
for f in algorithm: for f in algorithm:
hid += d.get(f, '') hid += d.get(f, '')

View file

@ -37,20 +37,15 @@ class SystemProperty(Property):
class UserProperty(Property): class UserProperty(Property):
uuid = models.UUIDField()
class Type(models.IntegerChoices): class Type(models.IntegerChoices):
USER = 1, "User" USER = 1, "User"
ERASE_SERVER = 2, "EraseServer" DOCUMENT = 2, "Document"
ERASE_SERVER = 3, "EraseServer"
uuid = models.UUIDField()
type = models.SmallIntegerField(choices=Type, default=Type.USER) type = models.SmallIntegerField(choices=Type, default=Type.USER)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["key", "uuid"], name="userproperty_unique_type_key_uuid")
]
class Evidence: class Evidence:
def __init__(self, uuid): def __init__(self, uuid):
@ -209,7 +204,7 @@ class Evidence:
def get_all(cls, user): def get_all(cls, user):
return SystemProperty.objects.filter( return SystemProperty.objects.filter(
owner=user.institution, owner=user.institution,
key="ereuse24", key="hidalgo1",
).order_by("-created").values_list("uuid", "created").distinct() ).order_by("-created").values_list("uuid", "created").distinct()
def set_components(self): def set_components(self):
@ -223,14 +218,3 @@ class Evidence:
def is_web_snapshot(self): def is_web_snapshot(self):
return self.doc.get("type") == "WebSnapshot" return self.doc.get("type") == "WebSnapshot"
def did_document(self):
if not self.doc.get("credentialSubject"):
return ''
did = self.doc.get('issuer')
if not "did:web" in did:
return ''
return "https://{}/did.json".format(
did.split("did:web:")[1].replace(":", "/")
)

View file

@ -71,7 +71,7 @@ class Build:
) )
if prop: if prop:
txt = "Warning: Snapshot %s already registered (annotation exists)" txt = "Warning: Snapshot %s already registered (property exists)"
logger.warning(txt, self.uuid) logger.warning(txt, self.uuid)
return return
@ -88,7 +88,7 @@ class Build:
return hashlib.sha3_256(doc.encode()).hexdigest() return hashlib.sha3_256(doc.encode()).hexdigest()
def register_device_dlt(self): def register_device_dlt(self):
legacy_dpp = self.build.algorithms.get('ereuse22') legacy_dpp = self.build.algorithms.get('legacy_dpp')
chid = self.sign(legacy_dpp) chid = self.sign(legacy_dpp)
phid = self.sign(json.dumps(self.build.get_doc())) phid = self.sign(json.dumps(self.build.get_doc()))
register_device_dlt(chid, phid, self.uuid, self.user) register_device_dlt(chid, phid, self.uuid, self.user)

43
evidence/serializers.py Normal file
View file

@ -0,0 +1,43 @@
from rest_framework import serializers
from evidence.models import EvidenceJson
import json
from django.views.decorators.csrf import csrf_exempt
from django.http import JsonResponse
from evidence.parse import Parse
class EvidenceSerializer(serializers.ModelSerializer):
class Meta:
model = EvidenceJson
fields = ['id', 'title', 'content']
@csrf_exempt
def webhook_verify(request):
if request.method == 'POST':
auth_header = request.headers.get('Authorization')
if not auth_header or not auth_header.startswith('Bearer '):
return JsonResponse({'error': 'Invalid authorization'}, status=401)
token = auth_header.split(' ')[1]
tk = Token.objects.filter(token=token).first()
if not tk:
return JsonResponse({'error': 'Invalid authorization'}, status=401)
try:
data = json.loads(request.body)
except json.JSONDecodeError:
return JsonResponse({'error': 'Invalid JSON'}, status=400)
try:
device = Parse(data)
except Exception:
return JsonResponse({'error': 'Invalid JSON'}, status=400)
if not device:
return JsonResponse({'error': 'Invalid JSON'}, status=400)
return JsonResponse({"result": "Ok"}, status=200)
return JsonResponse({'error': 'Invalid request method'}, status=400)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,19 +0,0 @@
# Generated by Django 5.0.6 on 2025-01-29 11:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("lot", "0004_remove_lotproperty_lot_unique_type_key_lot_and_more"),
]
operations = [
migrations.AlterField(
model_name="lotproperty",
name="type",
field=models.SmallIntegerField(
choices=[(0, "System"), (1, "User")], default=1
),
),
]

View file

@ -1,20 +0,0 @@
# Generated by Django 5.0.6 on 2025-01-31 10:33
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('lot', '0005_alter_lotproperty_type'),
('user', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddConstraint(
model_name='lotproperty',
constraint=models.UniqueConstraint(fields=('key', 'lot'), name='property_unique_type_key_lot'),
),
]

View file

@ -51,11 +51,6 @@ class LotProperty (Property):
class Type(models.IntegerChoices): class Type(models.IntegerChoices):
SYSTEM = 0, "System" SYSTEM = 0, "System"
USER = 1, "User" USER = 1, "User"
DOCUMENT = 2, "Document"
type = models.SmallIntegerField(choices=Type.choices, default=Type.USER) type = models.SmallIntegerField(choices=Type.choices, default=Type.USER)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["key", "lot"], name="property_unique_type_key_lot")
]

View file

@ -0,0 +1,48 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col">
<h3>Lot {{ lot.name }}</h3>
</div>
</div>
<div class="row">
<div class="tab-pane fade show active" id="details">
<div class="btn-group dropdown ml-1 mt-1" uib-dropdown="">
<a href="{% url 'lot:add_document' lot.pk %}" class="btn btn-primary">
<i class="bi bi-plus"></i>
Add new document
<span class="caret"></span>
</a>
</div>
<h5 class="card-title mt-2">Documents</h5>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">Key</th>
<th scope="col">Value</th>
<th scope="col" data-type="date" data-format="YYYY-MM-DD hh:mm">Created on</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{% for a in documents %}
<tr>
<td>{{ a.key }}</td>
<td>{{ a.value }}</td>
<td>{{ a.created }}</td>
<td></td>
<td></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}

View file

@ -36,7 +36,7 @@
{% endfor %} {% endfor %}
</div> </div>
<div class="container"> <div class="container">
<a class="btn btn-grey" href="{% url 'lot:properties' lot_id %}">{% translate "Cancel" %}</a> <a class="btn btn-grey" href="{% url 'dashboard:unassigned_devices' %}">{% translate "Cancel" %}</a>
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" /> <input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
</div> </div>

View file

@ -10,8 +10,8 @@
<div class="row"> <div class="row">
<div class="tab-pane fade show active" id="details"> <div class="tab-pane fade show active" id="details">
<div class="d-flex justify-content-end mt-1 mb-3"> <div class="btn-group dropdown ml-1 mt-1" uib-dropdown="">
<a href="{% url 'lot:add_property' lot.pk %}" class="btn btn-green-admin d-flex align-items-center"> <a href="{% url 'lot:add_property' lot.pk %}" class="btn btn-primary">
<i class="bi bi-plus"></i> <i class="bi bi-plus"></i>
Add new lot Property Add new lot Property
@ -20,13 +20,14 @@
</div> </div>
<h5 class="card-title mt-2">Properties</h5> <h5 class="card-title mt-2">Properties</h5>
<table class="table table-hover table-bordered table-responsive align-middle"> <table class="table table-striped">
<thead class="table-light"> <thead>
<tr> <tr>
<th scope="col">{% trans 'Key' %}</th> <th scope="col">Key</th>
<th scope="col">{% trans 'Value' %}</th> <th scope="col">Value</th>
<th scope="col" data-type="date" class="text-end" data-format="YYYY-MM-DD HH:mm">{% trans 'Created on' %}</th> <th scope="col" data-type="date" data-format="YYYY-MM-DD hh:mm">Created on</th>
<th scope="col" width="5%" class="text-end"></th> <th></th>
<th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -34,18 +35,16 @@
<tr> <tr>
<td>{{ a.key }}</td> <td>{{ a.key }}</td>
<td>{{ a.value }}</td> <td>{{ a.value }}</td>
<td class="text-end">{{ a.created }}</td> <td>{{ a.created }}</td>
<td> <td class="text-center">
<div class="btn-group "> <a href="#" class="text-info" data-bs-toggle="modal" data-bs-target="#editPropertyModal{{ a.id }}">
<button type="button" class="btn btn-sm btn-outline-info d-flex align-items-center" data-bs-toggle="modal" data-bs-target="#editPropertyModal{{ a.id }}"> <i class="bi bi-pencil"></i>
<i class="bi bi-pencil me-1"></i> </a>
{% trans 'Edit' %} </td>
</button> <td class="text-center">
<button type="button" class="btn btn-sm btn-outline-danger d-flex align-items-center" data-bs-toggle="modal" data-bs-target="#deletePropertyModal{{ a.id }}"> <a href="#" class="text-danger" data-bs-toggle="modal" data-bs-target="#deletePropertyModal{{ a.id }}">
<i class="bi bi-trash me-1"></i> <i class="bi bi-trash"></i>
{% trans 'Delete' %} </a>
</button>
</div>
</td> </td>
</tr> </tr>

View file

@ -10,6 +10,8 @@ urlpatterns = [
path("add/devices/", views.AddToLotView.as_view(), name="add_devices"), path("add/devices/", views.AddToLotView.as_view(), name="add_devices"),
path("del/devices/", views.DelToLotView.as_view(), name="del_devices"), path("del/devices/", views.DelToLotView.as_view(), name="del_devices"),
path("tag/<int:pk>/", views.LotsTagsView.as_view(), name="tag"), path("tag/<int:pk>/", views.LotsTagsView.as_view(), name="tag"),
path("<int:pk>/document/", views.LotDocumentsView.as_view(), name="documents"),
path("<int:pk>/document/add", views.LotAddDocumentView.as_view(), name="add_document"),
path("<int:pk>/property", views.LotPropertiesView.as_view(), name="properties"), path("<int:pk>/property", views.LotPropertiesView.as_view(), name="properties"),
path("<int:pk>/property/add", views.AddLotPropertyView.as_view(), name="add_property"), path("<int:pk>/property/add", views.AddLotPropertyView.as_view(), name="add_property"),
path("<int:pk>/property/update", views.UpdateLotPropertyView.as_view(), name="update_property"), path("<int:pk>/property/update", views.UpdateLotPropertyView.as_view(), name="update_property"),

View file

@ -1,4 +1,3 @@
from django.db import IntegrityError
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.shortcuts import get_object_or_404, redirect, Http404 from django.shortcuts import get_object_or_404, redirect, Http404
from django.contrib import messages from django.contrib import messages
@ -99,8 +98,7 @@ class AddToLotView(DashboardView, FormView):
def get_form(self): def get_form(self):
form = super().get_form() form = super().get_form()
form.fields["lots"].queryset = Lot.objects.filter( form.fields["lots"].queryset = Lot.objects.filter(owner=self.request.user.institution)
owner=self.request.user.institution)
return form return form
def form_valid(self, form): def form_valid(self, form):
@ -134,9 +132,7 @@ class LotsTagsView(DashboardView, TemplateView):
self.title += " {}".format(tag.name) self.title += " {}".format(tag.name)
self.breadcrumb += " {}".format(tag.name) self.breadcrumb += " {}".format(tag.name)
show_closed = self.request.GET.get('show_closed', 'false') == 'true' show_closed = self.request.GET.get('show_closed', 'false') == 'true'
lots = Lot.objects.filter(owner=self.request.user.institution).filter( lots = Lot.objects.filter(owner=self.request.user.institution).filter(type=tag, closed=show_closed)
type=tag, closed=show_closed
)
context.update({ context.update({
'lots': lots, 'lots': lots,
'title': self.title, 'title': self.title,
@ -146,6 +142,53 @@ class LotsTagsView(DashboardView, TemplateView):
return context return context
class LotAddDocumentView(DashboardView, CreateView):
template_name = "new_property.html"
title = _("New Document")
breadcrumb = "Device / New document"
success_url = reverse_lazy('dashboard:unassigned_devices')
model = LotProperty
fields = ("key", "value")
def form_valid(self, form):
form.instance.owner = self.request.user.institution
form.instance.user = self.request.user
form.instance.lot = self.lot
form.instance.type = LotProperty.Type.DOCUMENT
response = super().form_valid(form)
return response
def get_form_kwargs(self):
pk = self.kwargs.get('pk')
self.lot = get_object_or_404(Lot, pk=pk, owner=self.request.user.institution)
self.success_url = reverse_lazy('lot:documents', args=[pk])
kwargs = super().get_form_kwargs()
return kwargs
class LotDocumentsView(DashboardView, TemplateView):
template_name = "documents.html"
title = _("New Document")
breadcrumb = "Devicce / New document"
def get_context_data(self, **kwargs):
self.pk = kwargs.get('pk')
context = super().get_context_data(**kwargs)
lot = get_object_or_404(Lot, owner=self.request.user.institution, id=self.pk)
documents = LotProperty.objects.filter(
lot=lot,
owner=self.request.user.institution,
type=LotProperty.Type.DOCUMENT,
)
context.update({
'lot': lot,
'documents': documents,
'title': self.title,
'breadcrumb': self.breadcrumb
})
return context
class LotPropertiesView(DashboardView, TemplateView): class LotPropertiesView(DashboardView, TemplateView):
template_name = "properties.html" template_name = "properties.html"
title = _("New Lot Property") title = _("New Lot Property")
@ -182,13 +225,8 @@ class AddLotPropertyView(DashboardView, CreateView):
form.instance.user = self.request.user form.instance.user = self.request.user
form.instance.lot = self.lot form.instance.lot = self.lot
form.instance.type = LotProperty.Type.USER form.instance.type = LotProperty.Type.USER
try:
response = super().form_valid(form) response = super().form_valid(form)
messages.success(self.request, _("Property successfully added."))
return response return response
except IntegrityError:
messages.error(self.request, _("Property is already defined."))
return self.form_invalid(form)
def get_form_kwargs(self): def get_form_kwargs(self):
pk = self.kwargs.get('pk') pk = self.kwargs.get('pk')
@ -197,11 +235,6 @@ class AddLotPropertyView(DashboardView, CreateView):
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
return kwargs return kwargs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['lot_id'] = self.lot.id
return context
class UpdateLotPropertyView(DashboardView, UpdateView): class UpdateLotPropertyView(DashboardView, UpdateView):
template_name = "properties.html" template_name = "properties.html"
@ -212,33 +245,31 @@ class UpdateLotPropertyView(DashboardView, UpdateView):
def get_form_kwargs(self): def get_form_kwargs(self):
pk = self.kwargs.get('pk') pk = self.kwargs.get('pk')
lot_property = get_object_or_404( lot_property = get_object_or_404(LotProperty, pk=pk, owner=self.request.user.institution)
LotProperty,
pk=pk,
owner=self.request.user.institution
)
if not lot_property: if not lot_property:
raise Http404 raise Http404
lot_pk = lot_property.lot.pk
self.success_url = reverse_lazy('lot:properties', args=[lot_pk])
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
kwargs['instance'] = lot_property kwargs['instance'] = lot_property
return kwargs return kwargs
def form_valid(self, form): def form_valid(self, form):
try: old_key= self.object.key
response = super().form_valid(form) old_value = self.object.value
messages.success(self.request, _("Property updated successfully.")) new_key = form.cleaned_data['key']
return response new_value = form.cleaned_data['value']
except IntegrityError:
messages.error(self.request, _("Property is already defined."))
return self.form_invalid(form)
def form_invalid(self, form): form.instance.owner = self.request.user.institution
super().form_invalid(form) form.instance.user = self.request.user
return redirect(self.get_success_url()) form.instance.type = LotProperty.Type.USER
response = super().form_valid(form)
messages.success(self.request, _("Lot property updated successfully."))
return response
def get_success_url(self):
return self.request.META.get('HTTP_REFERER', reverse_lazy('device:details', args=[self.object.pk]))
class DeleteLotPropertyView(DashboardView, DeleteView): class DeleteLotPropertyView(DashboardView, DeleteView):
@ -246,15 +277,18 @@ class DeleteLotPropertyView(DashboardView, DeleteView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
self.pk = kwargs['pk'] self.pk = kwargs['pk']
referer = request.META.get('HTTP_REFERER')
if not referer:
raise Http404("No referer header found")
self.object = get_object_or_404( self.object = get_object_or_404(
self.model, self.model,
pk=self.pk, pk=self.pk,
owner=self.request.user.institution owner=self.request.user.institution
) )
lot_pk = self.object.lot.pk old_value = self.object.key
self.object.delete() self.object.delete()
messages.success(self.request, _("Lot property deleted successfully.")) messages.success(self.request, _("Lot property deleted successfully."))
self.success_url = reverse_lazy('lot:properties', args=[lot_pk])
# Redirect back to the original URL # Redirect back to the original URL
return redirect(self.success_url) return redirect(referer)

0
tag/__init__.py Normal file
View file

3
tag/admin.py Normal file
View file

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

6
tag/apps.py Normal file
View file

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

View file

3
tag/models.py Normal file
View file

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

3
tag/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
tag/views.py Normal file
View file

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

View file

@ -1,8 +0,0 @@
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
tests-examples
example.spec.ts

View file

@ -1,97 +0,0 @@
{
"name": "end-to-end",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "end-to-end",
"version": "1.0.0",
"license": "ISC",
"devDependencies": {
"@playwright/test": "^1.49.1",
"@types/node": "^22.10.7"
}
},
"node_modules/@playwright/test": {
"version": "1.49.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz",
"integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.49.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@types/node": {
"version": "22.10.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz",
"integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/playwright": {
"version": "1.49.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz",
"integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.49.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.49.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz",
"integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"dev": true,
"license": "MIT"
}
}
}

View file

@ -1,15 +0,0 @@
{
"name": "end-to-end",
"version": "1.0.0",
"main": "index.js",
"scripts": {},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"description": "",
"devDependencies": {
"@playwright/test": "^1.49.1",
"@types/node": "^22.10.7"
}
}

View file

@ -1,77 +0,0 @@
import { defineConfig, devices } from '@playwright/test';
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://127.0.0.1:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// url: 'http://127.0.0.1:3000',
// reuseExistingServer: !process.env.CI,
// },
});

View file

@ -1,21 +0,0 @@
#!/bin/sh
# SPDX-License-Identifier: AGPL-3.0-or-later
set -e
set -u
# DEBUG
set -x
main() {
cd "$(dirname "${0}")"
browser="${browser:-firefox}"
project="${project:-firefox}"
headed="${headed:---headed}"
npx playwright test --project "${project}" "${headed}"
}
main "${@}"
# written in emacs
# -*- mode: shell-script; -*-

View file

@ -1,237 +0,0 @@
import { test, expect } from '@playwright/test';
// TODO after the tests, put again demo.ereuse.org as default
const TEST_SITE = process.env.TEST_SITE || 'https://lab1.ereuse.org'
const TEST_USER = process.env.TEST_USER || 'user@example.org'
const TEST_PASSWD = process.env.TEST_PASSWD || '1234'
async function login(page, date, time) {
await page.goto(TEST_SITE);
await page.getByPlaceholder('Email address').click();
await page.getByPlaceholder('Email address').fill(TEST_USER);
await page.getByPlaceholder('Password').fill(TEST_PASSWD);
await page.getByPlaceholder('Password').press('Enter');
}
// when introducing a new test, use only temporarily to just enable that test
//
//test.only('NEW example', async ({ page }) => {
// await login(page);
// test.setTimeout(0)
// await page.pause();
//});
test('Evidence: create and destroy tag (custom id)', async ({ page }) => {
await login(page);
await page.goto(`${TEST_SITE}/evidence/`);
await page.locator('table a').first().click();
await page.getByRole('link', { name: 'Tag' }).click();
// create tag
await page.getByPlaceholder('Tag').click();
await page.getByPlaceholder('Tag').fill('test');
await page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByRole('alert')).toContainText('Tag mytag has been added.');
// delete tag
await page.getByRole('link', { name: 'Tag' }).click();
await page.getByRole('link', { name: 'Delete' }).click();
await expect(page.getByRole('alert')).toContainText('Tag mytag has been deleted.');
});
test('Property: create key-value, edit key, edit value, delete property property', async ({ page }) => {
const last_log = '#log tr:nth-child(1) td:nth-child(2)'
await login(page);
// assuming after login, we are in devices page, and there, there is a table with devices
await page.locator('table a').first().click();
// new property; key: init1, value: 1
await page.getByRole('link', { name: 'User properties' }).click();
await page.getByRole('link', { name: ' New user property' }).click();
await page.getByPlaceholder('Key').click();
await page.getByPlaceholder('Key').fill('init1');
await page.getByPlaceholder('Key').press('Tab');
await page.getByPlaceholder('Value').fill('1');
await page.getByRole('button', { name: 'Save' }).click();
// TODO uncomment
//await expect(page.getByRole('alert')).toContainText('User property init1 has been added.');
await page.getByRole('link', { name: 'Log' }).click();
await expect(page.locator(last_log)).toContainText('<Created> UserProperty: init1: 1');
// edit property; key: init2, value: 1
await page.getByRole('link', { name: 'User properties' }).click();
await page.getByRole('button', { name: ' Edit' }).first().click();
await page.getByLabel('Key').click();
await page.getByLabel('Key').fill('init2');
await page.getByRole('button', { name: 'Save changes' }).click();
// TODO uncomment
//await expect(page.getByRole('alert')).toContainText('User property init2 has been updated.');
await page.getByRole('link', { name: 'Log' }).click();
await expect(page.locator(last_log)).toContainText('<Updated> UserProperty: init1: 1 to init2: 1');
// edit property; key: init2, value: 2
await page.getByRole('link', { name: 'User properties' }).click();
await page.getByRole('button', { name: ' Edit' }).first().click();
await page.getByLabel('Value').fill('2');
await page.getByRole('button', { name: 'Save changes' }).click();
// TODO uncomment
//await expect(page.getByRole('alert')).toContainText('User property init2 has been updated.');
await page.getByRole('link', { name: 'Log' }).click();
await expect(page.locator(last_log)).toContainText('<Updated> UserProperty: init2: 1 to init2: 2');
// delete property; key: init2, value: 2
await page.getByRole('link', { name: 'User properties' }).click();
await page.getByRole('button', { name: ' Delete' }).click();
await page.getByRole('button', { name: 'Delete', exact: true }).click();
// TODO uncomment
//await expect(page.getByRole('alert')).toContainText('User property init2 has been updated.');
await page.getByRole('link', { name: 'Log' }).click();
await expect(page.locator(last_log)).toContainText('<Deleted> User Property: init2:2');
});
test('Property: duplication tests', async ({ page }) => {
await login(page);
// assuming after login, we are in devices page, and there, there is a table with devices
await page.locator('table a').first().click();
// new property; key: uniq1, value: 1
await page.getByRole('link', { name: 'User properties' }).click();
await page.getByRole('link', { name: ' New user property' }).click();
await page.getByPlaceholder('Key').click();
await page.getByPlaceholder('Key').fill('uniq1');
await page.getByPlaceholder('Key').press('Tab');
await page.getByPlaceholder('Value').fill('1');
await page.getByRole('button', { name: 'Save' }).click();
// TODO uncomment
//await expect(page.getByRole('alert')).toContainText('User property uniq1 has been added.');
// new property (duplicate); key: uniq1, value: 1
await page.getByRole('link', { name: 'User properties' }).click();
await page.getByRole('link', { name: ' New user property' }).click();
await page.getByPlaceholder('Key').click();
await page.getByPlaceholder('Key').fill('uniq1');
await page.getByPlaceholder('Key').press('Tab');
await page.getByPlaceholder('Value').fill('1');
await page.getByRole('button', { name: 'Save' }).click();
// TODO uncomment
//await expect(page.getByRole('alert')).toContainText('User property uniq1 already exists.');
// delete property; key: uniq1, value: 1
await page.getByRole('link', { name: 'User properties' }).click();
await page.getByRole('button', { name: ' Delete' }).first().click();
// TODO uncomment
//await expect(page.getByRole('alert')).toContainText('User property uniq1 deleted has been.');
});
test.only('States: duplication tests', async ({ page }) => {
await login(page);
await page.getByRole('link', { name: ' Admin' }).click();
await page.getByRole('link', { name: 'States' }).click();
// create state: TEST_STATE
await page.getByRole('button', { name: 'Add' }).click();
await page.getByRole('textbox', { name: 'State' }).click();
await page.getByRole('textbox', { name: 'State' }).fill('TEST_STATE');
await page.getByRole('button', { name: 'Add state definition' }).click();
// TODO uncomment
//await expect(page.getByRole('alert')).toContainText('State definition TEST_STATE has been added.');
// create state (duplicate): TEST_STATE
await page.getByRole('button', { name: 'Add' }).click();
await page.getByRole('textbox', { name: 'State' }).click();
await page.getByRole('textbox', { name: 'State' }).fill('TEST_STATE');
await page.getByRole('button', { name: 'Add state definition' }).click();
// TODO uncomment
//await expect(page.getByRole('alert')).toContainText('State definition TEST_STATE is already defined.');
// edit state: TEST_STATE -> TEST_STATE_EDIT
await page.getByRole('row', { name: 'TEST_STATE  Edit  Delete' }).getByRole('button').first().click();
await page.getByRole('textbox', { name: 'State' }).fill('TEST_STATE_EDIT');
await page.getByRole('button', { name: 'Save Changes' }).click();
// create state: TEST_STATE
await page.getByRole('button', { name: 'Add' }).click();
await page.getByRole('textbox', { name: 'State' }).click();
await page.getByRole('textbox', { name: 'State' }).fill('TEST_STATE');
await page.getByRole('button', { name: 'Add state definition' }).click();
// you edit state, and target name already exists
// TODO uncomment. "Cannot create key that already exists (UNIQUE constraint)"
// edit state (duplicated during edit): TEST_STATE_EDIT -> TEST_STATE
//await page.getByRole('row', { name: 'TEST_STATE  Edit  Delete' }).getByRole('button').first().click();
//await page.getByRole('textbox', { name: 'State' }).fill('TEST_STATE_EDIT');
//await page.getByRole('button', { name: 'Save Changes' }).click();
// delete state: TEST_STATE_EDIT
await page.getByRole('row', { name: 'TEST_STATE_EDIT  Edit  Delete' }).getByRole('button').nth(1).click();
await page.getByRole('button', { name: 'Delete', exact: true }).click();
// TODO uncomment
//await expect(page.getByRole('alert')).toContainText('State definition TEST_STATE has been deleted.');
// delete state: TEST_STATE
await page.getByRole('row', { name: 'TEST_STATE  Edit  Delete' }).getByRole('button').nth(1).click();
await page.getByRole('button', { name: 'Delete', exact: true }).click();
// TODO uncomment
//await expect(page.getByRole('alert')).toContainText('State definition TEST_STATE has been deleted.');
});
test('Lot: duplication tests', async ({ page }) => {
await login(page);
// add lot
await page.getByRole('link', { name: ' Lots' }).click();
await page.getByRole('link', { name: 'Entrada' }).click();
await page.getByRole('link', { name: ' Add new lot' }).click();
await page.getByLabel('Type').selectOption('1');
await page.getByPlaceholder('Name').click();
await page.getByPlaceholder('Name').fill('testlot');
await page.getByPlaceholder('Name').press('Tab');
await page.getByPlaceholder('Code').fill('testlot');
await page.getByPlaceholder('Code').press('Tab');
await page.getByPlaceholder('Description').fill('testlot');
await page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByRole('alert')).toContainText('Lot testlot has been added.');
// add (duplicate) lot
await page.getByRole('link', { name: ' Lots' }).click();
await page.getByRole('link', { name: 'Entrada' }).click();
await page.getByRole('link', { name: ' Add new lot' }).click();
await page.getByLabel('Type').selectOption('1');
await page.getByPlaceholder('Name').click();
await page.getByPlaceholder('Name').fill('testlot');
await page.getByPlaceholder('Name').press('Tab');
await page.getByPlaceholder('Code').fill('testlot');
await page.getByPlaceholder('Code').press('Tab');
await page.getByPlaceholder('Description').fill('testlot');
await page.getByPlaceholder('Description').press('Enter');
await page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByRole('alert')).toContainText('Lot testlot is already defined.');
// delete lot
await page.getByRole('link', { name: ' Lots' }).click();
await page.getByRole('link', { name: 'Entrada' }).click();
await page.getByRole('link', { name: '' }).nth(3).click();
await page.getByRole('link', { name: 'Cancel' }).click();
await page.getByRole('link', { name: ' Lots' }).click();
await page.getByRole('link', { name: 'Entrada' }).click();
await page.getByRole('link', { name: '' }).first().click();
await page.getByRole('button', { name: 'Delete' }).click();
await expect(page.getByRole('alert')).toContainText('Lot testlot has been deleted.');
});
// TODO falta probar la parte de notas
// falta vista https://lab1.ereuse.org/dashboard/ con columna de state actual; si no hay None pero con un estilo diferente (cursiva y gris?)
//test('Bug 4: Missing logs for actions', async ({ page }) => {
// await login(page);
// await page.goto(`${TEST_SITE}/device/7b769bd6e9191d5ff163fa4a206b9220dad10c47b45d210d3d4d31d586f6a4b6/#log`);
// // Add your assertions and steps to test if logs are missing
//});
//
//test('Bug 6: Log note is not visible', async ({ page }) => {
// await login(page);
// // Add the specific URL or steps for testing log note visibility
//});

View file

@ -9,7 +9,7 @@ STR_EXTEND_SIZE = 256
# Algorithms for build hids # Algorithms for build hids
EREUSE24 = [ HID_ALGO1 = [
"manufacturer", "manufacturer",
"model", "model",
"chassis", "chassis",
@ -17,8 +17,7 @@ EREUSE24 = [
"sku" "sku"
] ]
# EREUSE22 is used for build the chid of DPP LEGACY_DPP = [
EREUSE22 = [
"manufacturer", "manufacturer",
"model", "model",
"chassis", "chassis",
@ -29,8 +28,8 @@ EREUSE22 = [
] ]
ALGOS = { ALGOS = {
"ereuse24": EREUSE24, "hidalgo1": HID_ALGO1,
"ereuse22": EREUSE22 "legacy_dpp": LEGACY_DPP
} }