Compare commits
139 commits
feature/st
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
976ce43e6e | ||
|
c540bb7f7f | ||
|
7145e721f1 | ||
|
3db94ee82b | ||
|
5cf51df952 | ||
|
e7d958c550 | ||
|
2e932b9725 | ||
|
a4fb574d07 | ||
|
a52ce5c889 | ||
|
f9b93d4790 | ||
|
00df679156 | ||
|
0259c2be2c | ||
|
a496045f4d | ||
|
73bb3b4751 | ||
|
0db033e2dd | ||
|
b58cfc9ab1 | ||
|
1cb09fb19f | ||
|
9565288eee | ||
|
8b9ea1cf10 | ||
|
ad497e6299 | ||
|
c383e692c3 | ||
|
4da9961eea | ||
|
c0c4e29fdc | ||
|
0a50f75ca4 | ||
|
8136684b91 | ||
|
67d7621509 | ||
|
86a7d9f733 | ||
|
a06cf9f4da | ||
|
a0596f618b | ||
|
afb7cf8d6e | ||
|
df386136ce | ||
|
b1b9f7e100 | ||
|
8f0b8771a7 | ||
|
11813db7f6 | ||
|
86c2a26130 | ||
|
6b7fd09777 | ||
|
643e6c1f45 | ||
|
2837d3e560 | ||
|
3bed441d10 | ||
|
0fb3df0155 | ||
|
3e2a5f03bf | ||
|
1f4515781a | ||
|
a1381f68fa | ||
|
fcc93955c4 | ||
|
455448aea2 | ||
|
afcb6feb22 | ||
|
0ca4a83f97 | ||
|
c93915f285 | ||
|
2b0f0b8d08 | ||
|
18ec5d74c9 | ||
|
63c427e3eb | ||
|
5a5dfc3319 | ||
|
519648226b | ||
|
24f5508462 | ||
|
0d8ec72d7c | ||
|
d75cd75c86 | ||
|
85013a340e | ||
|
24753b1004 | ||
|
2dfe313076 | ||
|
3db4374cd0 | ||
|
cf48000ef4 | ||
|
30826afd45 | ||
|
c530454054 | ||
|
6e88f77c1b | ||
|
70175be472 | ||
|
dbd837b079 | ||
|
7a55501c34 | ||
|
d92dec28bb | ||
|
3467a483e7 | ||
|
5a448755c2 | ||
|
f3f18e0962 | ||
|
080ff4f668 | ||
|
ec9ae644b9 | ||
|
b5c57aa4d2 | ||
|
68d8ff33a7 | ||
|
3cf6af0c25 | ||
|
4d38e75bba | ||
|
5c7db7d60d | ||
|
4045baac9f | ||
|
0e3bba0569 | ||
|
012e25d086 | ||
|
cfa4b9a291 | ||
|
9a9970336b | ||
|
2f23ed7e88 | ||
|
7470dc5de2 | ||
|
57954d66e2 | ||
|
ede5d6a6c5 | ||
|
62ccf46194 | ||
|
3b0735faec | ||
|
3670fda2a3 | ||
|
6d44aa855b | ||
|
4485517221 | ||
|
b9ffd788a1 | ||
|
bad82965e9 | ||
|
eecaef7cff | ||
|
fb6c243ee8 | ||
|
ff9a78ed23 | ||
|
5fcd9ce7ca | ||
|
539f5b5bb7 | ||
|
5e2c8f2328 | ||
|
2451e843ac | ||
|
acbe2f6a75 | ||
|
2bb9e8d035 | ||
|
35622ff9c7 | ||
|
ff928a381b | ||
|
09bed0a904 | ||
|
9807cb56aa | ||
|
e0e4fd862a | ||
|
b4f0909199 | ||
|
db59b099f5 | ||
|
963858263a | ||
|
4bc423a979 | ||
|
4d9f588ad7 | ||
|
42b13eec84 | ||
|
fcefddb5a0 | ||
|
8991faa423 | ||
|
9df611293a | ||
|
9c27a30399 | ||
|
13d325105f | ||
|
09c3f96185 | ||
|
dad8e40ee8 | ||
|
f7cd7bc3f2 | ||
|
1f0a9a60ce | ||
|
01b7267dd8 | ||
|
91c03cb990 | ||
|
9dafc51210 | ||
|
7f16552762 | ||
|
829fb6e2a1 | ||
|
02a69e6994 | ||
|
2ff630f212 | ||
|
d7d6fb7bc6 | ||
|
096704935d | ||
|
0485604512 | ||
|
40b0617a72 | ||
|
b1c4a2cec9 | ||
|
9503a9a8b4 | ||
|
9247f11c27 | ||
|
56d8aadf83 | ||
|
601da538bf |
2
.dockerignore
Normal file
2
.dockerignore
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
db
|
||||||
|
.git
|
17
.editorconfig
Normal file
17
.editorconfig
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# 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
|
57
.env.example
57
.env.example
|
@ -1,5 +1,15 @@
|
||||||
DH_DOMAIN=localhost
|
####
|
||||||
DH_PORT=8000
|
# DEV OPTIONS
|
||||||
|
####
|
||||||
|
|
||||||
|
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
|
||||||
|
@ -16,7 +26,48 @@ 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'
|
||||||
DH_ALLOWED_HOSTS=${DH_DOMAIN},${DH_DOMAIN}:${DH_PORT},127.0.0.1,127.0.0.1:${DH_PORT}
|
DEVICEHUB_ALLOWED_HOSTS=${DEVICEHUB_DOMAIN},${DEVICEHUB_DOMAIN}:${DEVICEHUB_PORT},127.0.0.1,127.0.0.1:${DEVICEHUB_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
5
.gitignore
vendored
|
@ -1,4 +1,7 @@
|
||||||
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
|
||||||
|
|
|
@ -14,7 +14,7 @@ class State(models.Model):
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if not StateDefinition.objects.filter(institution=self.institution, state=self.state).exists():
|
if not StateDefinition.objects.filter(institution=self.institution, state=self.state).exists():
|
||||||
raise ValidationError(f"The state '{self.state}' is not valid for the institution '{self.institution.name}'.")
|
raise ValidationError(f"The state '{self.state}' is not valid for the institution '{self.institution.name}'.")
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.clean()
|
self.clean()
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
@ -22,6 +22,7 @@ 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)
|
||||||
|
|
||||||
|
@ -41,7 +42,7 @@ class StateDefinition(models.Model):
|
||||||
self.order = (max_order or 0) + 1
|
self.order = (max_order or 0) + 1
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
institution = self.institution
|
institution = self.institution
|
||||||
order = self.order
|
order = self.order
|
||||||
|
|
|
@ -2,6 +2,7 @@ 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 _
|
||||||
|
@ -9,7 +10,7 @@ from action.models import State, StateDefinition, Note, DeviceLog
|
||||||
from device.models import Device
|
from device.models import Device
|
||||||
|
|
||||||
|
|
||||||
class ChangeStateView(FormView):
|
class ChangeStateView(LoginRequiredMixin, FormView):
|
||||||
form_class = ChangeStateForm
|
form_class = ChangeStateForm
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
|
@ -42,7 +43,7 @@ class ChangeStateView(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(FormView):
|
class AddNoteView(LoginRequiredMixin, FormView):
|
||||||
form_class = AddNoteForm
|
form_class = AddNoteForm
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
|
@ -73,7 +74,7 @@ class AddNoteView(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(UpdateView):
|
class UpdateNoteView(LoginRequiredMixin, UpdateView):
|
||||||
model = Note
|
model = Note
|
||||||
fields = ['description']
|
fields = ['description']
|
||||||
pk_url_kwarg = 'pk'
|
pk_url_kwarg = 'pk'
|
||||||
|
@ -93,19 +94,19 @@ class UpdateNoteView(UpdateView):
|
||||||
)
|
)
|
||||||
messages.success(self.request, "Note has been updated.")
|
messages.success(self.request, "Note has been updated.")
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
def form_invalid(self, form):
|
def form_invalid(self, form):
|
||||||
new_description = form.cleaned_data.get('description', '').strip()
|
new_description = form.cleaned_data.get('description', '').strip()
|
||||||
if not new_description:
|
if not new_description:
|
||||||
messages.error(self.request, _("Note cannot be empty."))
|
messages.error(self.request, _("Note cannot be empty."))
|
||||||
super().form_invalid(form)
|
super().form_invalid(form)
|
||||||
return redirect(self.get_success_url())
|
return redirect(self.get_success_url())
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return self.request.META.get('HTTP_REFERER', reverse_lazy('device:details'))
|
return self.request.META.get('HTTP_REFERER', reverse_lazy('device:details'))
|
||||||
|
|
||||||
|
|
||||||
class DeleteNoteView(View):
|
class DeleteNoteView(LoginRequiredMixin, View):
|
||||||
model = Note
|
model = Note
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
|
|
|
@ -25,7 +25,7 @@ class AdminView(DashboardView):
|
||||||
response = super().get(*args, **kwargs)
|
response = super().get(*args, **kwargs)
|
||||||
if not self.request.user.is_admin:
|
if not self.request.user.is_admin:
|
||||||
raise Http403
|
raise Http403
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
class PanelView(AdminView, TemplateView):
|
class PanelView(AdminView, TemplateView):
|
||||||
|
@ -111,7 +111,7 @@ class EditUserView(AdminView, UpdateView):
|
||||||
kwargs = super().get_form_kwargs()
|
kwargs = super().get_form_kwargs()
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
class InstitutionView(AdminView, UpdateView):
|
class InstitutionView(AdminView, UpdateView):
|
||||||
template_name = "institution.html"
|
template_name = "institution.html"
|
||||||
title = _("Edit institution")
|
title = _("Edit institution")
|
||||||
|
@ -168,8 +168,9 @@ class AddStateDefinitionView(AdminView, StateDefinitionContextMixin, CreateView)
|
||||||
messages.error(self.request, _("State is already defined."))
|
messages.error(self.request, _("State is already defined."))
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
|
|
||||||
def form_invalid(self, form):
|
def form_invalid(self, form):
|
||||||
return super().form_invalid(form)
|
super().form_invalid(form)
|
||||||
|
return redirect(self.success_url)
|
||||||
|
|
||||||
|
|
||||||
class DeleteStateDefinitionView(AdminView, StateDefinitionContextMixin, SuccessMessageMixin, DeleteView):
|
class DeleteStateDefinitionView(AdminView, StateDefinitionContextMixin, SuccessMessageMixin, DeleteView):
|
||||||
|
@ -218,14 +219,21 @@ 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 get_success_url(self):
|
|
||||||
messages.success(self.request, _("State definition updated successfully."))
|
|
||||||
return reverse_lazy('admin:states_panel')
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
return super().form_valid(form)
|
try:
|
||||||
|
response = super().form_valid(form)
|
||||||
|
messages.success(self.request, _("State definition updated successfully."))
|
||||||
|
return response
|
||||||
|
except IntegrityError:
|
||||||
|
messages.error(self.request, _("State is already defined."))
|
||||||
|
return self.form_invalid(form)
|
||||||
|
|
||||||
|
def form_invalid(self, form):
|
||||||
|
super().form_invalid(form)
|
||||||
|
return redirect(self.get_success_url())
|
||||||
|
|
|
@ -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 not have uuid"
|
txt = "error: the snapshot does not have an 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="hidalgo1",
|
key="ereuse24",
|
||||||
owner=self.tk.owner.institution
|
owner=self.tk.owner.institution
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
|
|
|
@ -9,12 +9,6 @@
|
||||||
<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' %}
|
||||||
|
|
|
@ -71,17 +71,6 @@ 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:
|
||||||
|
@ -160,11 +149,11 @@ 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 = 'hidalgo1' THEN 2
|
WHEN t1.key = 'ereuse24' THEN 2
|
||||||
ELSE 3
|
ELSE 3
|
||||||
END,
|
END,
|
||||||
t1.created DESC
|
t1.created DESC
|
||||||
) AS row_num
|
) AS row_num
|
||||||
FROM evidence_systemproperty AS t1
|
FROM evidence_systemproperty AS t1
|
||||||
LEFT JOIN lot_devicelot AS t2 ON t1.value = t2.device_id
|
LEFT JOIN lot_devicelot AS t2 ON t1.value = t2.device_id
|
||||||
WHERE t2.device_id IS NULL
|
WHERE t2.device_id IS NULL
|
||||||
|
@ -206,7 +195,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 = 'hidalgo1' THEN 2
|
WHEN t1.key = 'ereuse24' THEN 2
|
||||||
ELSE 3
|
ELSE 3
|
||||||
END,
|
END,
|
||||||
t1.created DESC
|
t1.created DESC
|
||||||
|
@ -241,7 +230,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 = 'hidalgo1' THEN 2
|
WHEN t1.key = 'ereuse24' THEN 2
|
||||||
ELSE 3
|
ELSE 3
|
||||||
END,
|
END,
|
||||||
t1.created DESC
|
t1.created DESC
|
||||||
|
@ -305,11 +294,15 @@ 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()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
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()
|
||||||
|
|
|
@ -2,108 +2,6 @@
|
||||||
{% 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">
|
||||||
|
@ -166,13 +64,7 @@
|
||||||
<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="#log" class="nav-link" data-bs-toggle="tab" data-bs-target="#log">{% trans 'Log' %}</a>
|
<a href="#user_properties" class="nav-link" data-bs-toggle="tab" data-bs-target="#user_properties">{% trans 'Properties' %}</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>
|
||||||
|
@ -191,6 +83,9 @@
|
||||||
<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>
|
||||||
|
@ -202,8 +97,6 @@
|
||||||
|
|
||||||
{% 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' %}
|
||||||
|
@ -256,27 +149,5 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
//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 %}
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<a class="btn btn-grey" href="{% url 'dashboard:unassigned_devices' %}">{% translate "Cancel" %}</a>
|
<a class="btn btn-grey" href="{% url 'device:details' pk %}#user_properties">{% 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>
|
||||||
|
|
||||||
|
|
|
@ -1,251 +0,0 @@
|
||||||
{% 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 %}
|
|
|
@ -1,49 +0,0 @@
|
||||||
|
|
||||||
{% 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>
|
|
|
@ -1,19 +1,34 @@
|
||||||
|
|
||||||
{% 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-6">
|
<div class="list-group col">
|
||||||
{% for snap in object.evidences %}
|
<table class="table">
|
||||||
<div class="list-group-item">
|
<thead>
|
||||||
<div class="d-flex w-100 justify-content-between">
|
<tr>
|
||||||
<small class="text-muted">{{ snap.created }}</small>
|
<th scope="col">uuid</th>
|
||||||
</div>
|
<th scope="col">Did Document</th>
|
||||||
<p class="mb-1">
|
<th scope="col">{% trans "Date" %}</th>
|
||||||
<a href="{% url 'evidence:details' snap.uuid %}">{{ snap.uuid }}</a>
|
</tr>
|
||||||
</p>
|
</thead>
|
||||||
</div>
|
<tbody>
|
||||||
{% endfor %}
|
{% for snap in object.evidences %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<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>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
|
@ -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' a.id %}">
|
<form method="post" action="{% url 'device:delete_user_property' object.id 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' a.id %}">
|
<form id="editForm{{ a.id }}" method="post" action="{% url 'device:update_user_property' object.id 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" %}
|
||||||
|
|
|
@ -7,10 +7,11 @@ 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", views.AddUserPropertyView.as_view(), name="add_user_property"),
|
path("<str:pk>/user_property/add",
|
||||||
path("user_property/<int:pk>/delete", views.DeleteUserPropertyView.as_view(), name="delete_user_property"),
|
views.AddUserPropertyView.as_view(), name="add_user_property"),
|
||||||
path("user_property/<int:pk>/update", views.UpdateUserPropertyView.as_view(), name="update_user_property"),
|
path("<str:device_id>/user_property/<int:pk>/delete",
|
||||||
path("<str:pk>/document/add", views.AddDocumentView.as_view(), name="add_document"),
|
views.DeleteUserPropertyView.as_view(), name="delete_user_property"),
|
||||||
|
path("<str:device_id>/user_property/<int:pk>/update",
|
||||||
|
views.UpdateUserPropertyView.as_view(), name="update_user_property"),
|
||||||
path("<str:pk>/public/", views.PublicDeviceWebView.as_view(), name="device_web"),
|
path("<str:pk>/public/", views.PublicDeviceWebView.as_view(), name="device_web"),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
199
device/views.py
199
device/views.py
|
@ -1,7 +1,9 @@
|
||||||
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
|
||||||
|
@ -24,6 +26,16 @@ 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")
|
||||||
|
@ -41,35 +53,6 @@ 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")
|
||||||
|
@ -115,20 +98,24 @@ 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()
|
||||||
uuid=self.object.last_uuid()
|
uuids = self.object.uuids
|
||||||
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": State.objects.filter(snapshot_uuid=uuid).order_by('-date'),
|
"device_states": device_states,
|
||||||
"device_logs": DeviceLog.objects.filter(snapshot_uuid=uuid).order_by('-date'),
|
"device_logs": device_logs,
|
||||||
"device_notes": Note.objects.filter(snapshot_uuid=uuid).order_by('-date'),
|
"device_notes": device_notes,
|
||||||
})
|
})
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
@ -189,7 +176,7 @@ class PublicDeviceWebView(TemplateView):
|
||||||
return JsonResponse(device_data)
|
return JsonResponse(device_data)
|
||||||
|
|
||||||
|
|
||||||
class AddUserPropertyView(DashboardView, CreateView):
|
class AddUserPropertyView(DeviceLogMixin, 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"
|
||||||
|
@ -202,125 +189,107 @@ class AddUserPropertyView(DashboardView, 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
|
||||||
|
|
||||||
message = _("<Created> UserProperty: {}: {}".format(form.instance.key, form.instance.value))
|
try:
|
||||||
DeviceLog.objects.create(
|
response = super().form_valid(form)
|
||||||
snapshot_uuid=form.instance.uuid,
|
messages.success(self.request, _("Property successfully added."))
|
||||||
event=message,
|
log_message = _("<Created> UserProperty: {}: {}".format(
|
||||||
user=self.request.user,
|
form.instance.key,
|
||||||
institution=self.request.user.institution
|
form.instance.value
|
||||||
)
|
))
|
||||||
|
|
||||||
messages.success(self.request, _("User property successfully added."))
|
self.log_registry(form.instance.uuid, log_message)
|
||||||
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 = get_object_or_404(SystemProperty, owner=institution, value=pk)
|
self.property = SystemProperty.objects.filter(
|
||||||
|
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):
|
||||||
return reverse_lazy('device:details', args=[self.kwargs.get('pk')])
|
pk = 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(DashboardView, UpdateView):
|
class UpdateUserPropertyView(DeviceLogMixin, 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_queryset(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
|
||||||
return UserProperty.objects.filter(pk=pk, owner=institution)
|
self.object = get_object_or_404(UserProperty, owner=institution, pk=pk)
|
||||||
|
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']
|
||||||
|
|
||||||
message = _("<Updated> UserProperty: {}: {} to {}: {}".format(old_key, old_value, new_key, new_value))
|
try:
|
||||||
DeviceLog.objects.create(
|
super().form_valid(form)
|
||||||
snapshot_uuid=form.instance.uuid,
|
messages.success(self.request, _("Property updated successfully."))
|
||||||
event=message,
|
log_message = _("<Updated> UserProperty: {}: {} to {}: {}".format(
|
||||||
user=self.request.user,
|
self.old_key,
|
||||||
institution=self.request.user.institution
|
self.old_value,
|
||||||
)
|
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)
|
||||||
|
|
||||||
messages.success(self.request, _("User property updated successfully."))
|
def form_invalid(self, form):
|
||||||
return super().form_valid(form)
|
super().form_invalid(form)
|
||||||
|
return redirect(self.get_success_url())
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return self.request.META.get('HTTP_REFERER', reverse_lazy('device:details', args=[self.object.pk]))
|
pk = self.kwargs.get('device_id')
|
||||||
|
return reverse_lazy('device:details', args=[pk]) + "#user_properties"
|
||||||
|
|
||||||
|
|
||||||
class DeleteUserPropertyView(DashboardView, DeleteView):
|
class DeleteUserPropertyView(DeviceLogMixin, 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 with messages framework
|
#using post() method because delete() method from DeleteView has some issues
|
||||||
|
# with messages framework
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object()
|
pk = self.kwargs.get('pk')
|
||||||
|
institution = self.request.user.institution
|
||||||
|
self.object = get_object_or_404(UserProperty, owner=institution, pk=pk)
|
||||||
self.object.delete()
|
self.object.delete()
|
||||||
|
|
||||||
message = _("<Deleted> User Property: {}:{}".format(self.object.key, self.object.value ))
|
msg = _("<Deleted> User Property: {}:{}".format(
|
||||||
DeviceLog.objects.create(
|
self.object.key,
|
||||||
snapshot_uuid=self.object.uuid,
|
self.object.value
|
||||||
event=message,
|
))
|
||||||
user=self.request.user,
|
self.log_registry(self.object.uuid, msg)
|
||||||
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):
|
||||||
return self.request.META.get('HTTP_REFERER', reverse_lazy('device:details', args=[self.object.pk]))
|
pk = self.kwargs.get('device_id')
|
||||||
|
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
|
|
||||||
|
|
|
@ -78,16 +78,13 @@ INSTALLED_APPS = [
|
||||||
'django_extensions',
|
'django_extensions',
|
||||||
'django_bootstrap5',
|
'django_bootstrap5',
|
||||||
'django_tables2',
|
'django_tables2',
|
||||||
"rest_framework",
|
|
||||||
"login",
|
"login",
|
||||||
"user",
|
"user",
|
||||||
"device",
|
"device",
|
||||||
"evidence",
|
"evidence",
|
||||||
"action",
|
|
||||||
"tag",
|
|
||||||
"lot",
|
"lot",
|
||||||
"documents",
|
|
||||||
"dashboard",
|
"dashboard",
|
||||||
|
"action",
|
||||||
"admin",
|
"admin",
|
||||||
"api",
|
"api",
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,18 +1,59 @@
|
||||||
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=${DH_DOMAIN:-localhost}
|
- DOMAIN=${DEVICEHUB_DOMAIN:-localhost}
|
||||||
- PORT=${DH_PORT:-8000}
|
- PORT=${DEVICEHUB_PORT:-8000}
|
||||||
- ALLOWED_HOSTS=${DH_ALLOWED_HOSTS:-$DH_DOMAIN}
|
- ALLOWED_HOSTS=${DEVICEHUB_ALLOWED_HOSTS:-$DEVICEHUB_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:
|
||||||
- ${DH_PORT}:${DH_PORT}
|
- ${DEVICEHUB_PORT:-8000}:${DEVICEHUB_PORT:-8000}
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
|
@ -19,12 +19,24 @@ 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
|
||||||
docker compose build
|
if [ "${DEV_DOCKER_ALWAYS_BUILD:-}" = 'true' ]; then
|
||||||
|
docker compose pull --ignore-buildable
|
||||||
|
docker compose build
|
||||||
|
else
|
||||||
|
docker compose pull
|
||||||
|
fi
|
||||||
docker compose up ${detach_arg:-}
|
docker compose up ${detach_arg:-}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ RUN apt update && \
|
||||||
python3-xapian \
|
python3-xapian \
|
||||||
git \
|
git \
|
||||||
sqlite3 \
|
sqlite3 \
|
||||||
|
curl \
|
||||||
jq \
|
jq \
|
||||||
time \
|
time \
|
||||||
vim \
|
vim \
|
||||||
|
@ -37,6 +38,7 @@ 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
|
||||||
|
|
|
@ -118,8 +118,53 @@ 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"
|
||||||
if [ ! -f "${init_flagfile}" ]; then
|
if [ ! -f "${init_flagfile}" ]; then
|
||||||
|
|
||||||
|
@ -132,7 +177,7 @@ config_phase() {
|
||||||
# 12, 13, 14
|
# 12, 13, 14
|
||||||
config_dpp_part1
|
config_dpp_part1
|
||||||
|
|
||||||
# cleanup other spnapshots and copy dlt/dpp snapshots
|
# cleanup other snapshots 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/
|
||||||
|
@ -140,7 +185,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
|
||||||
/usr/bin/time ./manage.py up_snapshots example/snapshots/ "${INIT_USER}"
|
run_demo
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# remain next command as the last operation for this if conditional
|
# remain next command as the last operation for this if conditional
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
# Register your models here.
|
|
|
@ -1,6 +0,0 @@
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class DocumentsConfig(AppConfig):
|
|
||||||
default_auto_field = "django.db.models.BigAutoField"
|
|
||||||
name = "documents"
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
# Create your models here.
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.shortcuts import render
|
|
||||||
|
|
||||||
# Create your views here.
|
|
|
@ -30,8 +30,7 @@ class UploadForm(forms.Form):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
file_json = json.loads(file_data)
|
file_json = json.loads(file_data)
|
||||||
build = Build
|
snap = Build(file_json, None, check=True)
|
||||||
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()
|
||||||
|
@ -72,7 +71,6 @@ 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',
|
||||||
|
@ -90,7 +88,6 @@ 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',
|
||||||
|
@ -106,18 +103,15 @@ 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(
|
message =_("<Deleted> Evidence Tag. Old Value: '{}'").format(old_value)
|
||||||
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:
|
||||||
msg = "<Updated> Evidence Tag. Old Value: '{}'. New Value: '{}'"
|
message=_("<Updated> Evidence Tag. Old Value: '{}'. New Value: '{}'").format(old_value, self.tag)
|
||||||
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',
|
||||||
|
@ -125,7 +119,7 @@ class UserTagForm(forms.Form):
|
||||||
owner=self.user.institution,
|
owner=self.user.institution,
|
||||||
user=self.user
|
user=self.user
|
||||||
)
|
)
|
||||||
|
|
||||||
DeviceLog.objects.create(
|
DeviceLog.objects.create(
|
||||||
snapshot_uuid=self.uuid,
|
snapshot_uuid=self.uuid,
|
||||||
event= message,
|
event= message,
|
||||||
|
@ -134,7 +128,6 @@ 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"))
|
||||||
|
|
||||||
|
@ -183,8 +176,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)
|
||||||
prop = create_property(doc, self.user)
|
property = create_property(doc, self.user)
|
||||||
table.append((doc, prop))
|
table.append((doc, property))
|
||||||
|
|
||||||
if commit:
|
if commit:
|
||||||
for doc, cred in table:
|
for doc, cred in table:
|
||||||
|
|
|
@ -4,7 +4,6 @@ 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
|
||||||
|
|
19
evidence/migrations/0005_alter_userproperty_type.py
Normal file
19
evidence/migrations/0005_alter_userproperty_type.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# 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
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -31,11 +31,13 @@ class BuildMix:
|
||||||
return hid
|
return hid
|
||||||
|
|
||||||
def generate_chids(self):
|
def generate_chids(self):
|
||||||
self.algorithms = {
|
self.algorithms = {}
|
||||||
'hidalgo1': self.get_hid('hidalgo1'),
|
for k in ALGOS.keys():
|
||||||
}
|
if not settings.DPP and k == 'ereuse22':
|
||||||
if settings.DPP:
|
continue
|
||||||
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()
|
||||||
|
@ -43,7 +45,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('legacy_dpp')
|
device = self.algorithms.get('ereuse22')
|
||||||
|
|
||||||
doc = [("computer", device)]
|
doc = [("computer", device)]
|
||||||
|
|
||||||
|
@ -51,7 +53,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("legacy_dpp", [])
|
algorithm = ALGOS.get("ereuse22", [])
|
||||||
hid = ""
|
hid = ""
|
||||||
for f in algorithm:
|
for f in algorithm:
|
||||||
hid += d.get(f, '')
|
hid += d.get(f, '')
|
||||||
|
|
|
@ -37,15 +37,20 @@ 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"
|
||||||
DOCUMENT = 2, "Document"
|
ERASE_SERVER = 2, "EraseServer"
|
||||||
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):
|
||||||
|
@ -204,7 +209,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="hidalgo1",
|
key="ereuse24",
|
||||||
).order_by("-created").values_list("uuid", "created").distinct()
|
).order_by("-created").values_list("uuid", "created").distinct()
|
||||||
|
|
||||||
def set_components(self):
|
def set_components(self):
|
||||||
|
@ -218,3 +223,14 @@ 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(":", "/")
|
||||||
|
)
|
||||||
|
|
|
@ -71,7 +71,7 @@ class Build:
|
||||||
)
|
)
|
||||||
|
|
||||||
if prop:
|
if prop:
|
||||||
txt = "Warning: Snapshot %s already registered (property exists)"
|
txt = "Warning: Snapshot %s already registered (annotation 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('legacy_dpp')
|
legacy_dpp = self.build.algorithms.get('ereuse22')
|
||||||
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)
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
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
19
lot/migrations/0005_alter_lotproperty_type.py
Normal file
19
lot/migrations/0005_alter_lotproperty_type.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# 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
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -6,8 +6,8 @@ from utils.constants import (
|
||||||
STR_EXTEND_SIZE,
|
STR_EXTEND_SIZE,
|
||||||
)
|
)
|
||||||
|
|
||||||
from user.models import User, Institution
|
from user.models import User, Institution
|
||||||
from evidence.models import Property
|
from evidence.models import Property
|
||||||
# from device.models import Device
|
# from device.models import Device
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ class Lot(models.Model):
|
||||||
if DeviceLot.objects.filter(lot=self, device_id=v).exists():
|
if DeviceLot.objects.filter(lot=self, device_id=v).exists():
|
||||||
return
|
return
|
||||||
DeviceLot.objects.create(lot=self, device_id=v)
|
DeviceLot.objects.create(lot=self, device_id=v)
|
||||||
|
|
||||||
def remove(self, v):
|
def remove(self, v):
|
||||||
for d in DeviceLot.objects.filter(lot=self, device_id=v):
|
for d in DeviceLot.objects.filter(lot=self, device_id=v):
|
||||||
d.delete()
|
d.delete()
|
||||||
|
@ -51,6 +51,11 @@ 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")
|
||||||
|
]
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
{% 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 %}
|
|
|
@ -36,7 +36,7 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<a class="btn btn-grey" href="{% url 'dashboard:unassigned_devices' %}">{% translate "Cancel" %}</a>
|
<a class="btn btn-grey" href="{% url 'lot:properties' lot_id %}">{% 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>
|
||||||
|
|
||||||
|
|
|
@ -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="btn-group dropdown ml-1 mt-1" uib-dropdown="">
|
<div class="d-flex justify-content-end mt-1 mb-3">
|
||||||
<a href="{% url 'lot:add_property' lot.pk %}" class="btn btn-primary">
|
<a href="{% url 'lot:add_property' lot.pk %}" class="btn btn-green-admin d-flex align-items-center">
|
||||||
|
|
||||||
<i class="bi bi-plus"></i>
|
<i class="bi bi-plus"></i>
|
||||||
Add new lot Property
|
Add new lot Property
|
||||||
|
@ -20,34 +20,35 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h5 class="card-title mt-2">Properties</h5>
|
<h5 class="card-title mt-2">Properties</h5>
|
||||||
<table class="table table-striped">
|
<table class="table table-hover table-bordered table-responsive align-middle">
|
||||||
<thead>
|
<thead class="table-light">
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">Key</th>
|
<th scope="col">{% trans 'Key' %}</th>
|
||||||
<th scope="col">Value</th>
|
<th scope="col">{% trans 'Value' %}</th>
|
||||||
<th scope="col" data-type="date" data-format="YYYY-MM-DD hh:mm">Created on</th>
|
<th scope="col" data-type="date" class="text-end" data-format="YYYY-MM-DD HH:mm">{% trans 'Created on' %}</th>
|
||||||
<th></th>
|
<th scope="col" width="5%" class="text-end"></th>
|
||||||
<th></th>
|
</tr>
|
||||||
</tr>
|
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for a in properties %}
|
{% for a in properties %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ a.key }}</td>
|
<td>{{ a.key }}</td>
|
||||||
<td>{{ a.value }}</td>
|
<td>{{ a.value }}</td>
|
||||||
<td>{{ a.created }}</td>
|
<td class="text-end">{{ a.created }}</td>
|
||||||
<td class="text-center">
|
<td>
|
||||||
<a href="#" class="text-info" data-bs-toggle="modal" data-bs-target="#editPropertyModal{{ a.id }}">
|
<div class="btn-group ">
|
||||||
<i class="bi bi-pencil"></i>
|
<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 }}">
|
||||||
</a>
|
<i class="bi bi-pencil me-1"></i>
|
||||||
</td>
|
{% trans 'Edit' %}
|
||||||
<td class="text-center">
|
</button>
|
||||||
<a href="#" class="text-danger" data-bs-toggle="modal" data-bs-target="#deletePropertyModal{{ a.id }}">
|
<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 }}">
|
||||||
<i class="bi bi-trash"></i>
|
<i class="bi bi-trash me-1"></i>
|
||||||
</a>
|
{% trans 'Delete' %}
|
||||||
</td>
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<div class="modal fade" id="editPropertyModal{{ a.id }}" tabindex="-1" aria-labelledby="editPropertyModalLabel{{ a.id }}" aria-hidden="true">
|
<div class="modal fade" id="editPropertyModal{{ a.id }}" tabindex="-1" aria-labelledby="editPropertyModalLabel{{ a.id }}" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
|
@ -75,7 +76,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal fade" id="deletePropertyModal{{ a.id }}" tabindex="-1" aria-labelledby="deletePropertyModalLabel{{ a.id }}" aria-hidden="true">
|
<div class="modal fade" id="deletePropertyModal{{ a.id }}" tabindex="-1" aria-labelledby="deletePropertyModalLabel{{ a.id }}" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
|
@ -97,7 +98,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,8 +10,6 @@ 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"),
|
||||||
|
|
110
lot/views.py
110
lot/views.py
|
@ -1,3 +1,4 @@
|
||||||
|
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
|
||||||
|
@ -98,7 +99,8 @@ 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(owner=self.request.user.institution)
|
form.fields["lots"].queryset = Lot.objects.filter(
|
||||||
|
owner=self.request.user.institution)
|
||||||
return form
|
return form
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
|
@ -132,7 +134,9 @@ 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(type=tag, closed=show_closed)
|
lots = Lot.objects.filter(owner=self.request.user.institution).filter(
|
||||||
|
type=tag, closed=show_closed
|
||||||
|
)
|
||||||
context.update({
|
context.update({
|
||||||
'lots': lots,
|
'lots': lots,
|
||||||
'title': self.title,
|
'title': self.title,
|
||||||
|
@ -142,53 +146,6 @@ 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")
|
||||||
|
@ -225,8 +182,13 @@ 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
|
||||||
response = super().form_valid(form)
|
try:
|
||||||
return response
|
response = super().form_valid(form)
|
||||||
|
messages.success(self.request, _("Property successfully added."))
|
||||||
|
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')
|
||||||
|
@ -235,6 +197,11 @@ 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"
|
||||||
|
@ -245,31 +212,33 @@ 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(LotProperty, pk=pk, owner=self.request.user.institution)
|
lot_property = get_object_or_404(
|
||||||
|
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):
|
||||||
old_key= self.object.key
|
try:
|
||||||
old_value = self.object.value
|
response = super().form_valid(form)
|
||||||
new_key = form.cleaned_data['key']
|
messages.success(self.request, _("Property updated successfully."))
|
||||||
new_value = form.cleaned_data['value']
|
return response
|
||||||
|
except IntegrityError:
|
||||||
|
messages.error(self.request, _("Property is already defined."))
|
||||||
|
return self.form_invalid(form)
|
||||||
|
|
||||||
form.instance.owner = self.request.user.institution
|
def form_invalid(self, form):
|
||||||
form.instance.user = self.request.user
|
super().form_invalid(form)
|
||||||
form.instance.type = LotProperty.Type.USER
|
return redirect(self.get_success_url())
|
||||||
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):
|
||||||
|
@ -277,18 +246,15 @@ 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
|
||||||
)
|
)
|
||||||
old_value = self.object.key
|
lot_pk = self.object.lot.pk
|
||||||
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(referer)
|
return redirect(self.success_url)
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
# Register your models here.
|
|
|
@ -1,6 +0,0 @@
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class TagConfig(AppConfig):
|
|
||||||
default_auto_field = "django.db.models.BigAutoField"
|
|
||||||
name = "tag"
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
# Create your models here.
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.shortcuts import render
|
|
||||||
|
|
||||||
# Create your views here.
|
|
8
tests/end-to-end/.gitignore
vendored
Normal file
8
tests/end-to-end/.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
node_modules/
|
||||||
|
/test-results/
|
||||||
|
/playwright-report/
|
||||||
|
/blob-report/
|
||||||
|
/playwright/.cache/
|
||||||
|
|
||||||
|
tests-examples
|
||||||
|
example.spec.ts
|
97
tests/end-to-end/package-lock.json
generated
Normal file
97
tests/end-to-end/package-lock.json
generated
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
tests/end-to-end/package.json
Normal file
15
tests/end-to-end/package.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
77
tests/end-to-end/playwright.config.ts
Normal file
77
tests/end-to-end/playwright.config.ts
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
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,
|
||||||
|
// },
|
||||||
|
});
|
21
tests/end-to-end/run.sh
Executable file
21
tests/end-to-end/run.sh
Executable file
|
@ -0,0 +1,21 @@
|
||||||
|
#!/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; -*-
|
237
tests/end-to-end/tests/tests.spec.ts
Normal file
237
tests/end-to-end/tests/tests.spec.ts
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
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
|
||||||
|
//});
|
|
@ -9,7 +9,7 @@ STR_EXTEND_SIZE = 256
|
||||||
|
|
||||||
|
|
||||||
# Algorithms for build hids
|
# Algorithms for build hids
|
||||||
HID_ALGO1 = [
|
EREUSE24 = [
|
||||||
"manufacturer",
|
"manufacturer",
|
||||||
"model",
|
"model",
|
||||||
"chassis",
|
"chassis",
|
||||||
|
@ -17,7 +17,8 @@ HID_ALGO1 = [
|
||||||
"sku"
|
"sku"
|
||||||
]
|
]
|
||||||
|
|
||||||
LEGACY_DPP = [
|
# EREUSE22 is used for build the chid of DPP
|
||||||
|
EREUSE22 = [
|
||||||
"manufacturer",
|
"manufacturer",
|
||||||
"model",
|
"model",
|
||||||
"chassis",
|
"chassis",
|
||||||
|
@ -28,8 +29,8 @@ LEGACY_DPP = [
|
||||||
]
|
]
|
||||||
|
|
||||||
ALGOS = {
|
ALGOS = {
|
||||||
"hidalgo1": HID_ALGO1,
|
"ereuse24": EREUSE24,
|
||||||
"legacy_dpp": LEGACY_DPP
|
"ereuse22": EREUSE22
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue