Localization and several UI changes #51

Open
rskthomas wants to merge 55 commits from ux-changes_rebase into main
46 changed files with 3640 additions and 631 deletions

View file

@ -11,17 +11,22 @@ class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('institution_name', type=str, help='The name of the institution')
parser.add_argument(
'language_code',
type=str,
help='The language code to set (e.g., "es", "en", "ca")',
)
def handle(self, *args, **kwargs):
default_states = [
_("INBOX"),
_("VISUAL INSPECTION"),
_("REPAIR"),
_("INSTALL"),
_("TEST"),
_("PACKAGING"),
_("DONATION"),
_("DISMANTLE")
"INBOX",
"VISUAL INSPECTION",
"REPAIR",
"INSTALL",
"TEST",
"PACKAGING",
"DONATION",
"DISMANTLE"
]
institution_name = kwargs['institution_name']
@ -32,6 +37,37 @@ class Command(BaseCommand):
logger.error(txt, institution.name)
return
# If using djangos localization framework for initial states, then we would need institution-wide languange preferences
lang_code = kwargs['language_code']
match lang_code:
case "en":
pass
case "es":
default_states = [
"ENTRADA",
"INSPECCION VISUAL",
"REPARACIÓN",
"INSTALADO",
"PRUEBAS",
"EMPAQUETADO",
"DONACION",
"DESMANTELADO"
]
case "ca":
default_states = [
"ENTRADA",
"INSPECCIÓ VISUAL",
"REPARACIÓ",
"INSTAL·LAT",
"PROVES",
"EMPAQUETAT",
"DONACIÓ",
"DESMANTELLAT"
]
case _:
logger.error("Language not supported %s", lang_code)
return
for state in default_states:
state_def, created = StateDefinition.objects.get_or_create(
institution=institution,

View file

@ -1,19 +1,30 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col">
<h3>{{ subtitle }}</h3>
</div>
</div>
<div class="container-fluid">
<div class="row">
<div class="col">
<a href="{% url 'admin:institution' user.institution.pk %}" class="btn btn-green-admin">
{% translate "Institution" %}
</a>
<div class="row">
<div class="col-md-6">
<div class="card shadow-sm border-0">
<div class="card-body d-flex flex-column">
<h5 class="card-title fw-bold mb-3">
<i class="bi bi-building me-2"></i> {% translate "Institution" %}
</h5>
<p class="card-text text-muted flex-grow-1">
{% translate "Edit and manage the institution's details and settings." %}
</p>
<div class="text-end">
<a href="{% url 'admin:institution' user.institution.pk %}" class="btn btn-green-admin d-inline-flex align-items-center">
<span class="me-2">{% translate "Edit Institution" %}</span>
<i class="bi bi-arrow-right"></i>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -1,36 +1,40 @@
{% extends "base.html" %}
{% load i18n %}
{% block actions %}
<a href="{% url 'admin:new_user' %}" class="btn btn-green-admin">
<i class="bi bi-person-add"></i>
{% translate "New user" %}
</a>
{% endblock %}
{% block content %}
<div class="row">
<div class="col">
<h3>{{ subtitle }}</h3>
</div>
<div class="col-2">
<a href="{% url 'admin:new_user' %}" class="btn btn-green-admin">{% translate "Add new user" %}</a>
</div>
</div>
<h3>{{ subtitle }}</h3>
<div class="row">
<div class="col">
<table class="table">
<thead>
<tr>
<th scope="col">Email</th>
<th>is Admin</th>
<th></th>
<th></th>
</tr>
<table class="table table-hover table-bordered">
<thead class="table-light">
<tr>
<th scope="col">{% trans "Email" %}</th>
<th scope="col">{% trans "Admin" %}</th>
<th scope="col" class="text-center">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
<tr>
{% for u in users %}
<tr>
<td>{{ u.email }}</td>
<td>{{ u.is_admin }}</td>
<td><a href="{% url 'admin:edit_user' u.pk %}"><i class="bi bi-eye"></i></td>
<td><a href="{% url 'admin:delete_user' u.pk %}" class="text-danger" title="Remove"><i class="bi bi-trash"></i></td>
</tr>
{% endfor %}
<td>{% if u.is_admin %}{% trans "Yes" %}{% else %}{% trans "No" %}{% endif %}</td>
<td class="text-center">
<a href="{% url 'admin:edit_user' u.pk %}"><i class="bi bi-pencil me-4"></i></a>
<a href="{% url 'admin:delete_user' u.pk %}" class="text-danger" title="Remove"><i class="bi bi-trash"></i> </a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>

View file

@ -1,19 +1,24 @@
{% extends "base.html" %}
{% load i18n %}
{% load django_bootstrap5 %}
{% block content %}
<div class="row">
<div class="col">
<h3>{{ subtitle }}</h3>
</div>
</div>
{% load django_bootstrap5 %}
<div class="row mb-3">
<div class="col">
Are you sure than want remove the lot {{ object.name }} with {{ object.devices.count }} devices.
<div class="row mb-4">
<div class="col">
<div class="alert alert-warning" role="alert">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
{% blocktranslate %}
Are you sure you want to remove the lot <strong>{{ object.name }}</strong> with <strong>{{ object.devices.count }}</strong> devices?
{% endblocktranslate %}
</div>
</div>
</div>
</div>
<form role="form" method="post">
{% csrf_token %}
@ -31,7 +36,10 @@
{% bootstrap_form form %}
<div class="form-actions-no-box">
<a class="btn btn-grey" href="{% url 'admin:users' %}">{% translate "Cancel" %}</a>
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Delete' %}" />
<button type="submit" class="btn btn-danger">
<i class="bi bi-trash me-1"></i>
{% translate "Delete" %}
</button>
</div>
</form>

View file

@ -1,32 +1,49 @@
{% extends "base.html" %}
{% load i18n %}
{% load i18n django_bootstrap5 %}
{% block content %}
<div class="row">
<div class="col">
<h3>{{ subtitle }}</h3>
<div class="ms-3 mt-4">
<div class="row mb-3">
<div class="col">
<h3>{{ subtitle }}</h3>
</div>
</div>
</div>
{% load django_bootstrap5 %}
<form role="form" method="post">
{% csrf_token %}
{% if form.errors %}
<div class="alert alert-danger alert-icon alert-icon-border alert-dismissible" role="alert">
<div class="icon"><span class="mdi mdi-close-circle-o"></span></div>
<div class="message">
{% for field, error in form.errors.items %}
{{ error }}<br />
{% endfor %}
<button class="btn-close" type="button" data-dismiss="alert" aria-label="Close"></button>
</div>
</div>
{% endif %}
{% bootstrap_form form %}
<div class="form-actions-no-box">
<a class="btn btn-grey" href="{% url 'admin:panel' %}">{% translate "Cancel" %}</a>
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
</div>
<form role="form" method="post" enctype="multipart/form-data">
{% csrf_token %}
</form>
{% if form.errors %}
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<strong>{% translate "Please fix the following errors:" %}</strong>
<ul>
{% for field, errors in form.errors.items %}
<li>{{ field|title }}: {{ errors|join:", " }}</li>
{% endfor %}
</ul>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
<div class="row">
<div class="col-md-6">
{% bootstrap_field form.name addon_before='<i class="bi bi-building"></i>' %}
{% bootstrap_field form.location addon_before='<i class="bi bi-geo-alt-fill"></i>' %}
</div>
<div class="col-md-6">
{% bootstrap_field form.responsable_person addon_before='<i class="bi bi-person-fill"></i>' %}
{% bootstrap_field form.supervisor_person addon_before='<i class="bi bi-person-check-fill"></i>' %}
{% bootstrap_field form.logo addon_before='<i class="bi bi-image-fill"></i>' %}
</div>
</div>
<div class="form-actions mt-5 ms-2">
<a class="btn btn-secondary" href="{% url 'admin:panel' %}">
{% translate "Cancel" %}
</a>
<button type="submit" class="btn btn-green-admin ms-3">
<i class="bi bi-floppy me-2"></i>{% translate "Save" %}
</button>
</div>
</form>
</div>
{% endblock %}

View file

@ -1,18 +1,17 @@
{% extends "base.html" %}
{% load i18n django_bootstrap5 %}
{% block content %}
<div class="row">
<div class="col">
<h3>{{ subtitle }}</h3>
</div>
<div class="col text-end">
{% block actions %}
<button type="button" class="btn btn-green-admin" data-bs-toggle="modal" data-bs-target="#addStateModal">
{% trans "Add" %}
<i class="bi bi-plus"></i>
{% trans "New State" %}
</button>
</div>
</div>
<div class="row mt-4">
{% endblock %}
{% block content %}
<h3>{{ subtitle }}</h3>
<div class="row">
<div class="col">
{% if state_definitions %}
<table class="table table-hover table-bordered align-middle">

View file

@ -31,7 +31,7 @@ class AdminView(DashboardView):
class PanelView(AdminView, TemplateView):
template_name = "admin_panel.html"
title = _("Admin")
breadcrumb = _("admin") + " /"
breadcrumb = _("Admin") + " / " + _("Panel") + " /"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@ -41,7 +41,7 @@ class PanelView(AdminView, TemplateView):
class UsersView(AdminView, TemplateView):
template_name = "admin_users.html"
title = _("Users")
breadcrumb = _("admin / Users") + " /"
breadcrumb = _("Admin") + " / " + _("Panel") + " / " + _("Users")
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@ -54,7 +54,7 @@ class UsersView(AdminView, TemplateView):
class CreateUserView(AdminView, NotifyActivateUserByEmail, CreateView):
template_name = "user.html"
title = _("User")
breadcrumb = _("admin / User") + " /"
breadcrumb = _("Admin") + " / " + _("Users") + " / " +_("New")
success_url = reverse_lazy('admin:users')
model = User
fields = (
@ -79,7 +79,7 @@ class CreateUserView(AdminView, NotifyActivateUserByEmail, CreateView):
class DeleteUserView(AdminView, DeleteView):
template_name = "delete_user.html"
title = _("Delete user")
breadcrumb = "admin / Delete user"
breadcrumb = _("Admin") + " / " + _("Users") + " / " +_("Delete")
success_url = reverse_lazy('admin:users')
model = User
fields = (
@ -96,7 +96,7 @@ class DeleteUserView(AdminView, DeleteView):
class EditUserView(AdminView, UpdateView):
template_name = "user.html"
title = _("Edit user")
breadcrumb = "admin / Edit user"
breadcrumb = _("Admin") + " / " + _("Users") + " / " +_("Edit")
success_url = reverse_lazy('admin:users')
model = User
fields = (
@ -116,6 +116,7 @@ class InstitutionView(AdminView, UpdateView):
template_name = "institution.html"
title = _("Edit institution")
section = "admin"
breadcrumb = _("Admin") + " / " + _("Institution") + " / "
subtitle = _('Edit institution')
model = Institution
success_url = reverse_lazy('admin:panel')
@ -127,6 +128,16 @@ class InstitutionView(AdminView, UpdateView):
"supervisor_person"
)
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.fields["name"].help_text = _("Full name of the institution.")
form.fields["logo"].help_text = _("URL to the institution's logo.")
form.fields["location"].help_text = _("The address or city of the institution.")
form.fields["responsable_person"].help_text = _("Name of the institution's responsable person.")
form.fields["supervisor_person"].help_text = _("The supervisor's full name.")
return form
def get_form_kwargs(self):
self.object = self.request.user.institution
kwargs = super().get_form_kwargs()
@ -146,7 +157,7 @@ class StateDefinitionContextMixin(ContextMixin):
class StatesPanelView(AdminView, StateDefinitionContextMixin, TemplateView):
template_name = "states_panel.html"
title = _("States Panel")
breadcrumb = _("admin / States Panel") + " /"
breadcrumb = _("Admin") + " / " + _("States") + " / "
class AddStateDefinitionView(AdminView, StateDefinitionContextMixin, CreateView):

View file

@ -122,7 +122,6 @@ class NewSnapshotView(ApiMixing):
owner=self.tk.owner.institution
).first()
if not prop:
logger.error("Error: No property for uuid: %s", ev_uuid)
return JsonResponse({'status': 'fail'}, status=500)

View file

@ -174,3 +174,15 @@ h3 {
.btn-orange {
background-color: #f5b587;
}
/* Clase para botones con funcionalidad no implementados */
.btn-todo {
background-color: #6c757d;
color: #fff; /* texto en blanco*/
opacity: 0.65;
cursor: not-allowed;
.btn-todo:disabled {
pointer-events: none;
}
}

View file

@ -1,4 +1,4 @@
{% load i18n static %}
{% load i18n static language_code %}
<!doctype html>
<html lang="en">
@ -7,7 +7,7 @@
{% block meta %}
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="robots" content="NONE,NOARCHIVE" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="viewp ort" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="Pangea">
{% endblock %}
@ -64,17 +64,24 @@
<div class="navbar-nav navbar-sub-brand">
{{ user.institution.name|upper }}
</div>
<div class="navbar-nav">
<div class="nav-item text-nowrap">
<i id="user-avatar" class="bi bi-person-circle"></i>
<a class="navbar-sub-brand px-3" href="{% url 'user:panel' %}">{{ user.email }}</a>
<a class="logout" href="{% url 'login:logout' %}">
<i class="fa-solid fa-arrow-right-from-bracket"></i>
</a>
</div>
</div>
</header>
<nav class="navbar navbar-expand-sm">
<div class="container-fluid d-flex justify-content-end">
<div class="nav-item dropdown">
<a class="nav-link dropdown-toggle navbar-sub-brand px-3" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
{{ user.email }}
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="{% url 'user:panel' %}"><i class="bi bi-person"></i> {% trans 'Profile' %}</a></li>
<li><a class="dropdown-item" href="{% url 'api:tokens' %}"><i class="bi bi-key"></i> {% trans 'Token' %}</a></li>
<li><a class="dropdown-item" href="{% url 'user:settings' %}"><i class="bi bi-gear"></i> {% trans 'Settings File' %}</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="{% url 'login:logout' %}"><i class="fa-solid fa-arrow-right-from-bracket"></i> {% trans 'Logout' %} </a></li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container-fluid">
<div class="row">
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
@ -113,7 +120,7 @@
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if path == 'unassigned_devices' %}expanded{% else %}collapse{% endif %}" id="ul_devices" data-bs-parent="#sidebarMenu">
<li class="nav-item">
<a class="nav-link{% if path == 'unassigned_devices' %} active2{% endif %}" href="{% url 'dashboard:unassigned_devices' %}">
{% trans 'Unassigned devices' %}
{% trans 'Unassigned' %}
</a>
</li>
</ul>
@ -141,7 +148,7 @@
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if path in 'upload list' %}expanded{% else %}collapse{% endif %}" id="ul_evidences" data-bs-parent="#sidebarMenu">
<li class="nav-item">
<a class="nav-link{% if path == 'upload' %} active2{% endif %}" href="{% url 'evidence:upload' %}">
{% trans 'Upload one' %}
{% trans 'Upload' %}
</a>
</li>
<li class="nav-item">
@ -159,12 +166,12 @@
<ul class="flex-column mb-2 ul_sidebar accordion-collapse {% if path in 'import add' %}expanded{% else %}collapse{% endif %}" id="ul_placeholders" data-bs-parent="#sidebarMenu">
<li class="nav-item">
<a class="nav-link{% if path == 'import' %} active2{% endif %}" href="{% url 'evidence:import' %}">
{% trans 'Upload Spreadsheet' %}
{% trans 'Import from spreadsheet' %}
</a>
</li>
<li class="nav-item">
<a class="nav-link{% if path == 'add' %} active2{% endif %}" href="{% url 'device:add' %}">
{% trans 'Create one' %}
{% trans 'Add device' %}
</a>
</li>
</ul>
@ -195,7 +202,7 @@
<form method="post" action="{% url 'dashboard:search' %}">
{% csrf_token %}
<div class="input-group rounded">
<input type="search" name="search" class="form-control rounded" placeholder="Search your device..." aria-label="Search" aria-describedby="search-addon" />
<input type="search" name="search" class="form-control rounded" placeholder="{% trans 'Search your device' %}" aria-label="Search" aria-describedby="search-addon" />
<span class="input-group-text border-0" id="search-addon">
<i class="fas fa-search"></i>
</span>
@ -212,24 +219,41 @@
</div>
</div>
<div class="d-flex flex-wrap gap-2 justify-content-end m-2 mb-4">
{% block actions %}
{% endblock %}
</div>
<div class= "mx-2">
{% block content %}
{% endblock content %}
</div>
</main>
</div>
</div>
<!-- Footer -->
<footer class="footer text-center mt-auto py-3">
<div class="container">
<span class="text-muted">{{ commit_id }}</span>
<footer class="footer mt-auto py-3" style="width: 100%;">
<div class="container-fluid">
<div class="d-flex justify-content-between align-items-center">
<span class="text-muted mx-auto">{{ commit_id }}</span>
{% include "language_picker.html" %}
</div>
</footer>
</div>
</footer>
{% block script %}
{% block script %}
<script src="{% static "js/jquery-3.3.1.slim.min.js" %}"></script>
<script src="{% static "js/popper.min.js" %}"></script>
<script src="{% static "js/bootstrap.min.js" %}"></script>
<script>
// initialize bootstrap tooltips for those objects with data-bs-toggle="tooltip"
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
</script>
{% block extrascript %}{% endblock %}
{% endblock %}
</body>

View file

@ -0,0 +1,20 @@
{% load i18n language_code %}
<div class="dropdown">
<form action="{% url 'set_language' %}" method="post">
{% csrf_token %}
<button class="btn btn-tertiary dropdown-toggle" type="button" id="languageDropdown" data-bs-toggle="dropdown" aria-expanded="false">
{% get_current_language as LANGUAGE_CODE %}
{% get_language_info_list for LANGUAGES as languages %}
{{ LANGUAGE_CODE|get_language_code:languages }}
</button>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="languageDropdown">
{% for lang in languages %}
<li>
<button class="dropdown-item" type="submit" name="language" value="{{ lang.code }}">{{ lang.name }}</button>
</li>
{% endfor %}
</ul>
</form>
</div>

View file

@ -2,83 +2,151 @@
{% load i18n %}
{% load paginacion %}
{% block actions %}
{% block content %}
<div class="row">
<div class="col">
<h3>{{ subtitle }}</h3>
</div>
<div class="col text-center">
<a href="{# url 'dashboard:exports' object.id #}" type="button" class="btn btn-green-admin">
{% if lot %}
<a href="{#% url 'lot:documents' object.id %#}" type="button" class="btn btn-green-admin btn-todo" data-bs-toggle="tooltip" title="{% trans " NOT IMPLEMENTED. Menu for adding documents for the lot" %} ">
<i class="bi bi-folder2"></i>
{% trans 'Documents' %}
</a>
{% endif %}
{% if lot %}
<a href="{% url 'lot:properties' object.id %}" type="button" class="btn btn-green-admin" >
<i class="bi bi-tag"></i>
{% trans 'Properties' %}
</a>
{% endif %}
<a href="{# url 'dashboard:exports' object.id #}" type="button" class="btn btn-todo" data-bs-toggle="tooltip" title=" {% trans "NOT IMPLEMENTED. This action tries to emulate what devicehub-teal did, which was related to opening a dialog where you can select different options for export the devices as csv for all selected devices" %}" >
<i class="bi bi-reply"></i>
{% trans 'Exports' %}
</a>
{% if lot %}
<a href="{% url 'lot:properties' object.id %}" type="button" class="btn btn-green-admin">
<i class="bi bi-tag"></i>
{% trans 'properties' %}
</a>
{% endif %}
</div>
</div>
<div class="dataTable-container">
{% endblock%}
{% block content %}
{% if lot.name %}
<h3 class="text-muted"> <i class="bi bi-folder2-open me-2"></i>{% trans "Lot" %} {{ lot.name }}</h3>
{% endif %}
<div class="dataTable-container mt-4">
<form method="post">
{% csrf_token %}
<table class="table">
<thead>
<tr>
<th scope="col" data-sortable="">
select
</th>
<th scope="col" data-sortable="">
shortid
</th>
<th scope="col" data-sortable="">
type
</th>
<th scope="col" data-sortable="">
manufacturer
</th>
<th scope="col" data-sortable="">
model
</th>
</tr>
</thead>
{% for dev in devices %}
<tbody>
<tr>
<td>
<input type="checkbox" name="devices" value="{{ dev.id }}" />
</td>
<td>
<a href="{% url 'device:details' dev.id %}">
{{ dev.shortid }}
</a>
</td>
<td>
{{ dev.type }}
</td>
<td>
{% csrf_token %}
<div class="d-flex justify-content-end m-2 mb-4">
<button id="remove-button" class="btn btn btn-danger me-2" type="submit" value="{% url 'lot:del_devices' %}" name="url" disabled>
<i class="bi bi-folder-minus pe-2"></i>
{% trans 'Unassign' %}
</button>
<button class="btn btn-green-user" type="submit" name="url" value="{% url 'lot:add_devices' %}">
<i class="bi bi-folder-symlink"></i>
{% trans 'Assign to lot' %}
</button>
</div>
<table class="table table-hover table-bordered">
<thead class="table-light">
<tr>
<th scope="col" class="text-center">
<input type="checkbox" id="select-all" />
</th>
<th scope="col" class="text-center">
{% trans "Short ID" %}
</th>
<th scope="col" class="text-center">
{% trans "Type" %}
</th>
<th scope="col" class="text-center">
{% trans "Manufacturer" %}
</th>
<th scope="col" class="text-center">
{% trans "Model" %}
</th>
<th scope="col" class="text-center">
{% trans "Current State" %}
</th>
<th scope="col" data-type="date" class="text-center" data-format="YYYY-MM-DD HH:mm">
{% trans "Evidence last updated" %}
</th>
</tr>
</thead>
<tbody>
{% for dev in devices %}
<tr>
<td class="text-center">
<input type="checkbox" name="devices" value="{{ dev.id }}" />
</td>
<td class="text-center">
<a href="{% url 'device:details' dev.id %}">
{{ dev.shortid }}
</a>
</td>
<td class="text-center">
{% if dev.type == "Laptop" or dev.type == "Netbook" %}
<i class="bi bi-laptop"></i>
{% elif dev.type == "Desktop" or dev.type == "Server" %}
<i class="bi bi-pc-display"></i>
{% elif dev.type == "Motherboard" %}
<i class="bi bi-motherboard"></i>
{% elif dev.type == "GraphicCard" %}
<i class="bi bi-gpu-card"></i>
{% elif dev.type == "HardDrive" %}
<i class="bi bi-hdd"></i>
{% elif dev.type == "SolidStateDrive" %}
<i class="bi bi-device-ssd"></i>
{% elif dev.type == "NetworkAdapter" %}
<i class="bi bi-pci-card-network"></i>
{% elif dev.type == "Processor" %}
<i class="bi bi-cpu"></i>
{% elif dev.type == "RamModule" %}
<i class="bi bi-memory"></i>
{% elif dev.type == "SoundCard" %}
<i class="bi bi-speaker"></i>
{% elif dev.type == "Display" %}
<i class="bi bi-display"></i>
{% elif dev.type == "Battery" %}
<i class="bi bi-battery"></i>
{% elif dev.type == "Camera" %}
<i class="bi bi-camera"></i>
{% endif %}
{{dev.type}}
</td>
<td class="text-center">
{{ dev.manufacturer }}
</td>
<td>
{% if dev.version %}
{{dev.version}} {{ dev.model }}
{% else %}
{{ dev.model }}
{% endif %}
</td>
</tr>
</tbody>
</td>
<td class="text-center">
{% if dev.version %}
{{dev.version}} {{ dev.model }}
{% else %}
{{ dev.model }}
{% endif %}
</td>
<td class="text-center">
{{ dev.get_current_state.state|default:"N/A" }}
</td>
<td class="text-center">
{{ dev.last_evidence.created }}
</td>
</tr>
</tbody>
{% endfor %}
</table>
<button class="btn btn-green-admin" type="submit" value="{% url 'lot:del_devices' %}" name="url">Remove</button> <button class="btn btn-green-admin" type="submit" name="url" value="{% url 'lot:add_devices' %}">add</button>
</form>
</div>
<div class="row mt-3">
<div class="col">
<div class="mt-5 d-flex align-items-center justify-content-center">
{% render_pagination page total_pages limit %}
</div>
</div>
<script>
// Placeholder check-all js
document.getElementById('select-all').onclick = function() {
var checkboxes = document.querySelectorAll('input[type="checkbox"]');
for (var checkbox of checkboxes) {
checkbox.checked = this.checked;
}
}
</script>
{% endblock %}

View file

@ -0,0 +1,11 @@
from django import template
from django.utils.translation import get_language_info
register = template.Library()
@register.filter
def get_language_code(language_code, languages):
for lang in languages:
if lang['code'] == language_code:
return lang['name_local'].lower()
return language_code.lower()

View file

@ -16,7 +16,7 @@ class UnassignedDevicesView(InventaryMixin):
template_name = "unassigned_devices.html"
section = "Unassigned"
title = _("Unassigned Devices")
breadcrumb = "Devices / Unassigned Devices"
breadcrumb = _("Devices") + " / " + _("Unassigned") + " / "
def get_devices(self, user, offset, limit):
return Device.get_unassigned(self.request.user.institution, offset, limit)
@ -26,7 +26,7 @@ class LotDashboardView(InventaryMixin, DetailsMixin):
template_name = "unassigned_devices.html"
section = "dashboard_lot"
title = _("Lot Devices")
breadcrumb = "Lot / Devices"
breadcrumb = _("Lot") + " / " + _("Devices") + " / "
model = Lot
def get_context_data(self, **kwargs):
@ -50,7 +50,7 @@ class SearchView(InventaryMixin):
template_name = "unassigned_devices.html"
section = "Search"
title = _("Search Devices")
breadcrumb = "Devices / Search Devices"
breadcrumb = _("Devices") + " / " + _("Search") + " / "
def get_devices(self, user, offset, limit):
post = dict(self.request.POST)

View file

@ -1,8 +1,8 @@
from django import forms
from utils.device import create_property, create_doc, create_index
from utils.save_snapshots import move_json, save_in_disk
from django.utils.translation import gettext_lazy as _
#TODO: translate device types
DEVICE_TYPES = [
("Desktop", "Desktop"),
("Laptop", "Laptop"),
@ -22,11 +22,11 @@ DEVICE_TYPES = [
class DeviceForm(forms.Form):
type = forms.ChoiceField(choices = DEVICE_TYPES, required=False)
amount = forms.IntegerField(required=False, initial=1)
custom_id = forms.CharField(required=False)
name = forms.CharField(required=False)
value = forms.CharField(required=False)
type = forms.ChoiceField(choices = DEVICE_TYPES, required=False, label= _(u"Type"))
amount = forms.IntegerField(required=False, initial=1, label= _(u"Amount"))
custom_id = forms.CharField(required=False, label=_(u"Custom id"))
name = forms.CharField(required=False, label= _(u"Name"))
value = forms.CharField(required=False, label=_(u"Value"))
class BaseDeviceFormSet(forms.BaseFormSet):

View file

@ -125,10 +125,14 @@ class Device:
def last_uuid(self):
if self.uuid:
return self.uuid
if not self.uuids:
self.get_uuids()
return self.uuids[0]
def get_current_state(self):
uuid = self.last_uuid
uuid = self.last_uuid()
return State.objects.filter(snapshot_uuid=uuid).order_by('-date').first()

View file

@ -1,14 +1,7 @@
{% extends 'base.html' %}
{% load i18n %}
{% block content %}
<!-- Top bar buttons -->
<div class="row">
<div class="col">
<h3>{{ object.shortid }}</h3>
</div>
<div class="col text-end">
<div class="btn-group" role="group" aria-label="Actions">
{% block actions %}
<!-- Top bar buttons -->
<!-- change state button -->
{% if state_definitions %}
@ -52,9 +45,10 @@
<i class="bi bi-sticky"></i> {% trans "Add a note" %}
</button>
</div>
</div>
</div>
{% endblock %}
{% block content %}
<h3>{{ object.shortid }}</h3>
<div class="row">
@ -127,6 +121,25 @@
</div>
</div>
</div>
{% if dpps %}
<div class="tab-pane fade" id="dpps">
<h5 class="card-title">{% trans 'List of dpps' %}</h5>
<div class="list-group col">
{% for d in dpps %}
<div class="list-group-item">
<div class="d-flex w-100 justify-content-between">
<small class="text-muted">{{ d.timestamp }}</small>
<span>{{ d.type }}</span>
</div>
<p class="mb-1">
<a href="{% url 'did:device_web' d.signature %}">{{ d.signature }}</a>
</p>
</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>
{% endblock %}

View file

@ -28,33 +28,30 @@
<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 class="alert alert-danger alert-dismissible fade show" role="alert">
<div class="d-flex align-items-center">
<i class="bi bi-exclamation-circle me-2"></i>
<div>
{% for field, error in form.errors.items %}
{{ error }}<br />
{% endfor %}
</div>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
</div>
{% endif %}
{{ form.management_form }}
<div class="container" id="formset-container">
<div class="row mb-2">
<div class="col">
<div class="row mb-3">
<div class="col-md-4">
{% bootstrap_field form.0.type %}
</div>
</div>
<div class="row mb-2">
<div class="col">
{% bootstrap_field form.0.amount %}
</div>
</div>
<div class="row mb-2">
<div class="col">
<div class="col-md-4">
{% bootstrap_field form.0.custom_id %}
</div>
</div>
<div class="col-md-4">
{% bootstrap_field form.0.amount %}
</div> </div>
<div class="row mb-2">
<div class="col-10">
<span class="fw-bold">{% trans 'Component details' %}</span>

View file

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

View file

@ -16,7 +16,7 @@
{% for snap in object.evidences %}
<tr>
<td>
<a href="{% url 'evidence:details' snap.uuid %}">{{ snap.uuid }}</a>
<a href="{% url 'evidence:details' snap.uuid %}">{{ snap.uuid|truncatechars:7|upper }}</a>
</td>
<td>
{% if snap.did_document %}

View file

@ -1,5 +1,6 @@
{% load i18n %}
{% load paginacion %}
<div class="tab-pane fade" id="log">
<div class="table-responsive">
<table class="table table-striped table-hover table-bordered bg-gradient">
@ -12,17 +13,19 @@
</thead>
<tbody>
{% for log in device_logs %}
<tr>
<td width="13%">{{ log.date|date:"M j, Y, H:i" }}</td>
<td class="fst-italic">{{ log.event }}</td>
<td>{{ log.user.get_full_name|default:log.user.username }}</td>
</tr>
<tr>
<td width="13%">{{ log.date|date:"M j, Y, H:i" }}</td>
<td class="fst-italic">{{ log.event }}</td>
<td>{{ log.user.get_full_name|default:log.user.username }}</td>
</tr>
{% empty %}
<tr>
<td colspan="3" class="text-center">{% trans 'No logs recorded.' %}</td>
</tr>
<tr>
<td colspan="3" class="text-center">{% trans 'No logs recorded.' %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% render_pagination device_logs.number device_logs.paginator.num_pages 10 %}
</div>

View file

@ -2,130 +2,109 @@
{% load i18n %}
<div class="tab-pane fade" id="user_properties">
<div class="d-flex justify-content-end mt-1 mb-3">
<a href="{% url 'device:add_user_property' object.pk %}"
class="btn btn-green-admin d-flex align-items-center">
<i class="bi bi-plus me-1"></i>
{% trans 'New user property' %}
</a>
<div class="d-flex justify-content-end mt-3 mb-4">
<a href="{% url 'device:add_user_property' object.pk %}"
class="btn btn-green-user d-flex align-items-center">
<i class="bi bi-plus me-2"></i>
{% trans 'New user property' %}
</a>
</div>
<h5 class="card-title mb-4">{% trans 'User properties' %}</h5>
<h5 class="card-title">{% trans 'User properties' %}</h5>
<table class="table table-hover table-bordered table-responsive align-middle">
<thead class="table-light">
<tr>
<th scope="col">{% trans 'Key' %}</th>
<th scope="col">{% trans 'Value' %}</th>
<th scope="col" data-type="date" class="text-end" data-format="YYYY-MM-DD HH:mm">{% trans 'Created on' %}</th>
<th scope="col" width="5%" class="text-end" title="{% trans 'Actions' %}"></th>
</tr>
</thead>
<tbody>
{% for a in object.get_user_properties %}
<tr>
<td>{{ a.key }}
</td>
<td>{{ a.value }}
</td>
<td class="text-end">{{ a.created }}
</td>
<td>
<div class="btn-group ">
<button
type="button"
class="btn btn-sm btn-outline-info d-flex align-items-center" data-bs-toggle="modal"
data-bs-target="#editModal{{ a.id }}" >
<i class="bi bi-pencil me-1"></i>
{% trans 'Edit' %}
</button>
<button
type="button"
class="btn btn-sm btn-outline-danger d-flex align-items-center"
data-bs-toggle="modal" data-bs-target="#deleteModal{{ a.id }}">
<i class="bi bi-trash me-1"></i>
{% trans 'Delete' %}
</button>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="table-responsive">
<table class="table table-hover table-bordered align-middle">
<thead class="table-light">
<tr>
<th scope="col">{% trans 'Key' %}</th>
<th scope="col">{% trans 'Value' %}</th>
<th scope="col" data-type="date" class="text-end" data-format="YYYY-MM-DD HH:mm">{% trans 'Created on' %}</th>
<th scope="col" width="10%" class="text-end">{% trans 'Actions' %}</th>
</tr>
</thead>
<tbody>
{% for a in object.get_user_properties %}
<tr>
<td>{{ a.key }}</td>
<td>{{ a.value }}</td>
<td class="text-end">{{ a.created }}</td>
<td class="text-end">
<div class="btn-group">
<button type="button"
class="btn btn-sm btn-outline-primary d-flex align-items-center me-2"
data-bs-toggle="modal"
data-bs-target="#editModal{{ a.id }}">
<i class="bi bi-pencil me-1"></i>
{% trans 'Edit' %}
</button>
<button type="button"
class="btn btn-sm btn-outline-danger d-flex align-items-center"
data-bs-toggle="modal"
data-bs-target="#deleteModal{{ a.id }}">
<i class="bi bi-trash me-1"></i>
{% trans 'Delete' %}
</button>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- pop up modal for delete confirmation -->
{% for a in object.get_user_properties %}
<div class="modal fade" id="deleteModal{{ a.id }}" tabindex="-1" aria-labelledby="deleteModalLabel{{ a.id }}" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="deleteModalLabel{{ a.id }}">{% trans "Confirm Deletion" %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">
</button>
</div>
<div class="modal-body">
<p>
<strong>{% trans "Key:" %}
</strong> {{ a.key }}
</p>
<p>
<strong>{% trans "Value:" %}
</strong> {{ a.value }}
</p>
<p>
<strong>{% trans "Created on:" %}
</strong> {{ a.created }}
</p>
</div>
<div class="modal-footer justify-content-center">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}
</button>
<form method="post" action="{% url 'device:delete_user_property' object.id a.id %}">
{% csrf_token %}
<button type="submit" class="btn btn-danger">{% trans "Delete" %}
</button>
</form>
</div>
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-light">
<h5 class="modal-title" id="deleteModalLabel{{ a.id }}">{% trans "Confirm Deletion" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p><strong>{% trans "Key:" %}</strong> {{ a.key }}</p>
<p><strong>{% trans "Value:" %}</strong> {{ a.value }}</p>
<p><strong>{% trans "Created on:" %}</strong> {{ a.created }}</p>
</div>
<div class="modal-footer justify-content-center">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
<form method="post" action="{% url 'device:delete_user_property' object.id a.id %}">
{% csrf_token %}
<button type="submit" class="btn btn-danger">{% trans "Delete" %}</button>
</form>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
<!-- popup modals for edit button -->
{% for a in object.get_user_properties %}
<div class="modal fade" id="editModal{{ a.id }}" tabindex="-1" aria-labelledby="editModalLabel{{ a.id }}" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editModalLabel{{ a.id }}">{% trans "Edit User Property" %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">
</button>
</div>
<div class="modal-body">
<form id="editForm{{ a.id }}" method="post" action="{% url 'device:update_user_property' object.id a.id %}">
{% csrf_token %}
<div class="mb-3">
<label for="key" class="form-label">{% trans "Key" %}
</label>
<input type="text" class="form-control" id="key" name="key" value="{{ a.key }}">
</div>
<div class="mb-3">
<label for="value" class="form-label">{% trans "Value" %}
</label>
<input type="text" class="form-control" id="value" name="value" value="{{ a.value }}">
</div>
<div class="modal-footer justify-content-center">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}
</button>
<button type="submit" class="btn btn-primary">{% trans "Save changes" %}
</button>
</div>
</form>
</div>
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-light">
<h5 class="modal-title" id="editModalLabel{{ a.id }}">{% trans "Edit User Property" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="editForm{{ a.id }}" method="post" action="{% url 'device:update_user_property' object.id a.id %}">
{% csrf_token %}
<div class="mb-3">
<label for="key" class="form-label">{% trans "Key" %}</label>
<input type="text" class="form-control" id="key" name="key" value="{{ a.key }}">
</div>
<div class="mb-3">
<label for="value" class="form-label">{% trans "Value" %}</label>
<input type="text" class="form-control" id="value" name="value" value="{{ a.value }}">
</div>
<div class="modal-footer justify-content-center">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
<button type="submit" class="btn btn-primary">{% trans "Save changes" %}</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endfor %}

View file

@ -5,6 +5,7 @@ from django.http import JsonResponse
from django.conf import settings
from django.db import IntegrityError
from django.urls import reverse_lazy
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.contrib import messages
from django.shortcuts import get_object_or_404, redirect, Http404
from django.utils.translation import gettext_lazy as _
@ -39,7 +40,7 @@ class DeviceLogMixin(DashboardView):
class NewDeviceView(DashboardView, FormView):
template_name = "new_device.html"
title = _("New Device")
breadcrumb = "Device / New Device"
breadcrumb = _("Device") + " / " + _("New") + " / "
success_url = reverse_lazy('dashboard:unassigned_devices')
form_class = DeviceFormSet
@ -56,7 +57,7 @@ class NewDeviceView(DashboardView, FormView):
class EditDeviceView(DashboardView, UpdateView):
template_name = "new_device.html"
title = _("Update Device")
breadcrumb = "Device / Update Device"
breadcrumb = _("Device") + " / " + _("Update") + " / "
success_url = reverse_lazy('dashboard:unassigned_devices')
model = SystemProperty
@ -75,7 +76,7 @@ class EditDeviceView(DashboardView, UpdateView):
class DetailsView(DashboardView, TemplateView):
template_name = "details.html"
title = _("Device")
breadcrumb = "Device / Details"
breadcrumb = _("Device") + " / " + _("Details") + " / "
model = SystemProperty
def get(self, request, *args, **kwargs):
@ -104,9 +105,21 @@ class DetailsView(DashboardView, TemplateView):
institution=self.request.user.institution
).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')
# Paginate device_logs
# TODO: when clicking pagination button, it reloads the page on the details tab, instead of the log one
device_logs_list = DeviceLog.objects.filter(snapshot_uuid__in=uuids).order_by('-date')
paginator = Paginator(device_logs_list, 10) # Show last 10 logs
page = self.request.GET.get('page')
try:
device_logs = paginator.page(page)
except PageNotAnInteger:
device_logs = paginator.page(1)
except EmptyPage:
device_logs = paginator.page(paginator.num_pages)
context.update({
'object': self.object,
'snapshot': last_evidence,
@ -179,7 +192,7 @@ class PublicDeviceWebView(TemplateView):
class AddUserPropertyView(DeviceLogMixin, CreateView):
template_name = "new_user_property.html"
title = _("New User Property")
breadcrumb = "Device / New Property"
breadcrumb = _("Device") + " / " + _("Property") + " / " + _("New")
model = UserProperty
fields = ("key", "value")
@ -226,7 +239,7 @@ class AddUserPropertyView(DeviceLogMixin, CreateView):
class UpdateUserPropertyView(DeviceLogMixin, UpdateView):
template_name = "new_user_property.html"
title = _("Update User Property")
breadcrumb = "Device / Update Property"
breadcrumb = _("Device") + " / " + _("Property") + " / " + _("Update")
model = UserProperty
fields = ("key", "value")

View file

@ -65,7 +65,9 @@ ENABLE_EMAIL = config("ENABLE_EMAIL", default=True, cast=bool)
EVIDENCES_DIR = config("EVIDENCES_DIR", default=os.path.join(BASE_DIR, "db"))
LOCALE_PATHS = [
os.path.join(BASE_DIR, 'locale'),
]
# Application definition
INSTALLED_APPS = [
@ -119,7 +121,13 @@ TEMPLATES = [
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
"django.template.context_processors.i18n",
],
'libraries':{
'get_language_code': 'dashboard.templatetags.language_code',
}
},
},
]
@ -172,8 +180,9 @@ if TIME_ZONE == "UTC":
USE_L10N = True
LANGUAGES = [
('es', 'Spanish'),
('en', 'English'),
('es', 'español'),
('en', 'english'),
('ca', 'català'),
]
# Static files (CSS, JavaScript, Images)

View file

@ -16,6 +16,10 @@ Including another URLconf
"""
from django.conf import settings
from django.urls import path, include
from django.conf.urls.i18n import i18n_patterns
from django.conf import settings
from django.conf.urls.static import static
from django.views.i18n import set_language
urlpatterns = [
# path('api/', include('snapshot.urls')),
@ -35,3 +39,11 @@ if settings.DPP:
path('dpp/', include('dpp.urls')),
path('did/', include('did.urls')),
])
urlpatterns += i18n_patterns(
path("language/", set_language, name='set_language'),
)
if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View file

@ -5,6 +5,7 @@ RUN apt update && \
apt-get install -y \
python3-xapian \
git \
gettext \
sqlite3 \
curl \
jq \

View file

@ -71,6 +71,7 @@ class UserTagForm(forms.Form):
self.pk = None
self.uuid = kwargs.pop('uuid', None)
self.user = kwargs.pop('user')
instance = SystemProperty.objects.filter(
uuid=self.uuid,
key='CUSTOM_ID',
@ -88,6 +89,7 @@ class UserTagForm(forms.Form):
if not data:
return False
self.tag = data
self.instance = SystemProperty.objects.filter(
uuid=self.uuid,
key='CUSTOM_ID',
@ -103,15 +105,18 @@ class UserTagForm(forms.Form):
if self.instance:
old_value = self.instance.value
if not self.tag:
message =_("<Deleted> Evidence Tag. Old Value: '{}'").format(old_value)
message = _("<Deleted> Evidence Tag. Old Value: '{}'").format(
old_value
)
self.instance.delete()
else:
self.instance.value = self.tag
self.instance.save()
if old_value != self.tag:
message=_("<Updated> Evidence Tag. Old Value: '{}'. New Value: '{}'").format(old_value, self.tag)
msg = "<Updated> Evidence Tag. Old Value: '{}'. New Value: '{}'"
message=_(msg).format(old_value, self.tag)
else:
message =_("<Created> Evidence Tag. Value: '{}'").format(self.tag)
message = _("<Created> Evidence Tag. Value: '{}'").format(self.tag)
SystemProperty.objects.create(
uuid=self.uuid,
key='CUSTOM_ID',
@ -119,7 +124,7 @@ class UserTagForm(forms.Form):
owner=self.user.institution,
user=self.user
)
DeviceLog.objects.create(
snapshot_uuid=self.uuid,
event= message,
@ -127,7 +132,6 @@ class UserTagForm(forms.Form):
institution=self.user.institution
)
class ImportForm(forms.Form):
file_import = forms.FileField(label=_("File to import"))
@ -176,8 +180,8 @@ class ImportForm(forms.Form):
table = []
for row in self.rows:
doc = create_doc(row)
property = create_property(doc, self.user)
table.append((doc, property))
prop = create_property(doc, self.user)
table.append((doc, prop))
if commit:
for doc, cred in table:

View file

@ -1,27 +1,88 @@
{% extends "base.html" %}
{% load i18n %}
{% block actions %}
<a href="{% url 'evidence:download' object.uuid %}" class="btn btn-green-admin">
<i class="bi bi-file-earmark-arrow-down"></i>
{% trans "Download File" %}
</a>
{% endblock %}
{% block content %}
<div class="row">
<div class="col">
<h3>{{ object.id }}</h3>
</div>
<div class="col-md-12 mb-4">
<div class="card d-flex flex-wrap">
<div class="card-header">
<strong class="card-title">Evidence
<span class="text-muted" id="uuid">{{ object.uuid }}</span>
<button class="btn btn-sm btn-outline-secondary ms-1" onclick="copyToClipboard()">
<i class="bi bi-clipboard"></i>
</button>
</strong>
</div>
<div class="card-body">
<!-- Erase server checkbox -->
{% load django_bootstrap5 %}
<form role="form" method="post" class="p-2 rounded d-flex d-inline-flex align-items-center gap-3" style="max-width: 400px; {% if form2.erase_server.value %}background-color: #f5faf7!important;{% endif %}">
{% csrf_token %}
{% if form2.errors %}
<div class="alert alert-danger d-flex align-items-center alert-dismissible fade show w-100" role="alert">
<i class="mdi mdi-alert-circle me-2"></i>
<div>
{% for field, error in form2.errors.items %}
<strong>{{ field|title }}:</strong> {{ error }}<br />
{% endfor %}
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
<!-- submit button -->
<button class="btn btn-sm btn btn-outline-success" d-flex align-items-end gap-1" type="submit" name="submit_form2">
<i class="bi bi-floppy2-fill"></i> {% translate 'Save' %}
</button>
<!-- erase server switch -->
<div class="form-check form-switch m-0">
<input
class="form-check-input"
type="checkbox"
id="{{ form2.erase_server.id_for_label }}"
name="{{ form2.erase_server.name }}"
onchange="toggleSaveButton(this)"
{% if form2.erase_server.value %}
checked
{% endif %}>
<label class="form-check-label ps-1" for="{{ form2.erase_server.id_for_label }}">
{% if form2.erase_server.value %}
<i class="pe-1 bi bi-eraser"></i>
{% translate "It is an erase server" %}
{% else %}
<i class="pe-1 bi bi-x-circle"></i>
{% translate "It is not an erase server" %}
{% endif %}
</label>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<ul class="nav nav-tabs nav-tabs-bordered">
<li class="nav-items">
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#device">{% trans "Devices" %}</button>
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#device">{% trans "Device" %}</button>
</li>
<li class="nav-items">
<a href="#tag" class="nav-link" data-bs-toggle="tab" data-bs-target="#tag">{% trans "Tag" %}</a>
</li>
<li class="nav-items">
<a href="{% url 'evidence:erase_server' object.uuid %}" class="nav-link">{% trans "Erase Server" %}</a>
</li>
<li class="nav-items">
<a href="{% url 'evidence:download' object.uuid %}" class="nav-link">{% trans "Download File" %}</a>
<a href="#tag" class="nav-link" data-bs-toggle="tab" data-bs-target="#tag">{% trans "Tag" %}</a>
</li>
</ul>
</div>
@ -35,13 +96,13 @@
<thead>
<tr>
<th scope="col" data-sortable="">
{% trans "Type" %}
{% trans "Algorithm" %}
</th>
<th scope="col" data-sortable="">
{% trans "Identificator" %}
{% trans "Device ID" %}
</th>
<th scope="col" data-sortable="">
{% trans "Data" %}
{% trans "Date" %}
</th>
</tr>
</thead>
@ -53,7 +114,8 @@
</td>
<td>
<small class="text-muted">
<a href="{% url 'device:details' snap.value %}">{{ snap.value }}</a>
<a href="{% url 'device:details' snap.value %}">{{ snap.value|truncatechars:7|upper }}</a>
{{ dev.shortid }}
</small>
</td>
<td>
@ -67,6 +129,7 @@
</table>
</div>
</div>
<div class="tab-pane fade" id="tag">
{% load django_bootstrap5 %}
<div class="list-group col-6">
@ -88,7 +151,7 @@
<div class="row">
<div class="col">
<a class="btn btn-grey" href="">{% 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_form1" value="{% translate 'Save' %}" />
</div>
{% if form.tag.value %}
<div class="col-1">
@ -101,10 +164,24 @@
</div>
</div>
</div>
{% endblock %}
{% block extrascript %}
<script>
function toggleSaveButton(checkbox) {
const saveButton = document.getElementById('saveButton');
// Enable the button if the checkbox state changes
saveButton.disabled = false;
}
function copyToClipboard() {
const uuid = document.getElementById('uuid').innerText;
navigator.clipboard.writeText(uuid).then(() => {
alert('UUID copied to clipboard!');
}).catch(() => {
alert('Failed to copy UUID.');
});
}
document.addEventListener("DOMContentLoaded", function() {
// Obtener el hash de la URL (ejemplo: #components)
const hash = window.location.hash;

View file

@ -1,61 +0,0 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col">
<h3>{{ object.id }}</h3>
</div>
</div>
<div class="row">
<div class="col">
<ul class="nav nav-tabs nav-tabs-bordered">
<li class="nav-items">
<a href="{% url 'evidence:details' object.uuid %}" class="nav-link">{% trans "Devices" %}</a>
</li>
<li class="nav-items">
<a href="{% url 'evidence:details' object.uuid %}#tag" class="nav-link">{% trans "Tag" %}</a>
</li>
<li class="nav-items">
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#erase_server">{% trans "Erase Server" %}</button>
</li>
<li class="nav-items">
<a href="{% url 'evidence:download' object.uuid %}" class="nav-link">{% trans "Download File" %}</a>
</li>
</ul>
</div>
</div>
<div class="tab-content pt-2">
<div class="tab-pane fade show active" id="erase_server">
{% load django_bootstrap5 %}
<div class="list-group col-6">
<form role="form" method="post">
{% csrf_token %}
{% if form.errors %}
<div class="alert alert-danger alert-icon alert-icon-border alert-dismissible" role="alert">
<div class="icon"><span class="mdi mdi-close-circle-o"></span></div>
<div class="message">
{% for field, error in form.errors.items %}
{{ error }}<br />
{% endfor %}
<button class="btn-close" type="button" data-dismiss="alert" aria-label="Close"></button>
</div>
</div>
{% endif %}
{% bootstrap_form form %}
<div class="container">
<div class="row">
<div class="col">
<a class="btn btn-grey" href="">{% translate "Cancel" %}</a>
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
</div>
</div>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View file

@ -8,23 +8,119 @@
</div>
</div>
<!-- override invalid-feedback class -->
<style>
.invalid-feedback {
color: #670000;
font-size: 1rem;
.drop-area {
width: 320px;
padding: 1.5rem;
background: white;
border: 2px dashed #ccc;
border-radius: 12px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease-in-out;
}
.drop-area:hover, .drop-area.dragover {
border-color: #007bff;
background: #f8f9fa;
}
.drop-area.dragover {
transform: scale(1.05);
}
</style>
{% load django_bootstrap5 %}
<form role="form" method="post" enctype="multipart/form-data">
{% csrf_token %}
<form role="form" method="post" enctype="multipart/form-data" class="row d-flex justify-content-center ">
{% csrf_token %}
{% bootstrap_form form alert_error_type="none" error_css_class="alert alert-danger alert-icon alert-icon-border" %}
<div class="form-actions-no-box">
<a class="btn btn-grey" href="{% url 'dashboard:unassigned_devices' %}">{% translate "Cancel" %}</a>
<input class="btn btn-green-admin" type="submit" name="submit" value="{% translate 'Save' %}" />
</div>
<!-- Drag n drop -->
<div id="drop-zone" class="drop-area rounded p-5 text-center">
<i class="bi bi-upload fs-1 mb-3"></i>
<p class="text-muted mb-0">{% translate "Drag and drop here, or click to select manually" %}</p>
{# support for both evidence file and spreadsheet forms #}
{% if form.evidence_file %}
{% bootstrap_form form exclude="evidence_file" alert_error_type="none" error_css_class="alert alert-danger" %}
<input type="file" id="file-input" name="{{ form.evidence_file.html_name }}" class="visually-hidden" {% if form.evidence_file.field.widget.attrs.multiple %}multiple required{% endif %}>
{% elif form.file_import %}
{% bootstrap_form form exclude="file_import" alert_error_type="none" error_css_class="alert alert-danger" %}
<input type="file" id="file-input" name="{{ form.file_import.html_name }}" class="visually-hidden">
{% endif %}
</div>
<!-- File Preview -->
<div id="file-preview" class="mt-3 text-center ">
<ul id="file-list" class="list-unstyled"></ul>
{% if form.evidence_file.errors or form.file_import.errors %}
<div class="text-center mt-2">
<ul class="list-unstyled text-danger mb-0">
{% for error in form.evidence_file.errors %}
<li class="font-weight-bold" >{{ error }}</li>
{% endfor %}
{% for error in form.file_import.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
</div>
<div class="d-flex justify-content-center gap-3 mt-4">
<a class="btn btn-outline-secondary" href="{% url 'dashboard:unassigned_devices' %}">{% translate "Cancel" %}</a>
<button type="submit" class="btn btn-success">
<i class="bi bi-upload me-2"></i>{% translate "Upload" %}
</button>
</div>
</form>
<script>
document.addEventListener("DOMContentLoaded", () => {
const dropZone = document.getElementById("drop-zone");
const fileInput = document.getElementById("file-input");
const filePreview = document.getElementById("file-preview");
const fileList = document.getElementById("file-list");
dropZone.addEventListener("click", () => fileInput.click());
fileInput.addEventListener("change", (event) => {
updateFilePreview(event.target.files);
});
// Handle drag and drop events
dropZone.addEventListener("dragover", (event) => {
event.preventDefault();
dropZone.classList.add("border-primary", "bg-light");
});
dropZone.addEventListener("dragleave", () => {
dropZone.classList.remove("border-primary", "bg-light");
});
dropZone.addEventListener("drop", (event) => {
event.preventDefault();
dropZone.classList.remove("border-primary", "bg-light");
fileInput.files = event.dataTransfer.files;
updateFilePreview(event.dataTransfer.files);
});
// Function to update the file preview
function updateFilePreview(files) {
fileList.innerHTML = "";
if (files.length > 0) {
filePreview.classList.remove("d-none");
Array.from(files).forEach((file) => {
const listItem = document.createElement("li");
listItem.textContent = file.name;
fileList.appendChild(listItem);
});
} else {
filePreview.classList.add("d-none");
}
}
});
</script>
{% endblock %}

View file

@ -18,7 +18,6 @@ urlpatterns = [
path("upload", views.UploadView.as_view(), name="upload"),
path("import", views.ImportView.as_view(), name="import"),
path("<uuid:pk>", views.EvidenceView.as_view(), name="details"),
path("<uuid:pk>/eraseserver", views.EraseServerView.as_view(), name="erase_server"),
path("<uuid:pk>/download", views.DownloadEvidenceView.as_view(), name="download"),
path("tag/<str:pk>/delete", views.DeleteEvidenceTagView.as_view(), name="delete_tag"),
]

View file

@ -27,7 +27,7 @@ class ListEvidencesView(DashboardView, TemplateView):
template_name = "evidences.html"
section = "evidences"
title = _("Evidences")
breadcrumb = "Evidences"
breadcrumb = _("Evidence") + " / " + _("All") + " / "
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@ -43,10 +43,18 @@ class UploadView(DashboardView, FormView):
template_name = "upload.html"
section = "evidences"
title = _("Upload Evidence")
breadcrumb = "Evidences / Upload"
breadcrumb = _("Evidence") + " / " + _("Upload") + " / "
success_url = reverse_lazy('evidence:list')
form_class = UploadForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
"help_text": _('Upload the snapshots generated by Workbench.'),
})
return context
def form_valid(self, form):
form.save(self.request.user)
messages.success(self.request, _("Evidence uploaded successfully."))
@ -62,7 +70,7 @@ class ImportView(DashboardView, FormView):
template_name = "upload.html"
section = "evidences"
title = _("Import Evidence")
breadcrumb = "Evidences / Import"
breadcrumb = _("Evidence") + " / " + _("Import") + " / "
success_url = reverse_lazy('evidence:list')
form_class = ImportForm
@ -86,7 +94,7 @@ class EvidenceView(DashboardView, FormView):
template_name = "ev_details.html"
section = "evidences"
title = _("Evidences")
breadcrumb = "Evidences / Details"
breadcrumb = _("Evidence") + " / " + _("Details") + " / "
success_url = reverse_lazy('evidence:list')
form_class = UserTagForm
@ -103,6 +111,7 @@ class EvidenceView(DashboardView, FormView):
context = super().get_context_data(**kwargs)
context.update({
'object': self.object,
'form2': EraseServerForm(**self.get_form_kwargs(), data=self.request.POST or None),
})
return context
@ -113,6 +122,23 @@ class EvidenceView(DashboardView, FormView):
kwargs['user'] = self.request.user
return kwargs
def post(self, request, *args, **kwargs):
form1 = self.get_form()
#Empty param @initial makes it work, but i doubt it is the correct logic
form2 = EraseServerForm(request.POST, user=self.request.user, initial={}, uuid=self.kwargs.get('pk'))
if "submit_form1" in request.POST and form1.is_valid():
return self.form_valid(form1)
elif "submit_form2" in request.POST and form2.is_valid():
return self.form2_valid(form2)
return self.form_invalid(form1, form2)
def form2_valid(self, form):
form.save(self.request.user)
response = super().form_valid(form)
return response
def form_valid(self, form):
form.save(self.request.user)
response = super().form_valid(form)
@ -142,51 +168,6 @@ class DownloadEvidenceView(DashboardView, TemplateView):
return response
class EraseServerView(DashboardView, FormView):
template_name = "ev_eraseserver.html"
section = "evidences"
title = _("Evidences")
breadcrumb = "Evidences / Details"
success_url = reverse_lazy('evidence:list')
form_class = EraseServerForm
def get(self, request, *args, **kwargs):
self.pk = kwargs['pk']
self.object = Evidence(self.pk)
if self.object.owner != self.request.user.institution:
raise Http403
self.object.get_properties()
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'object': self.object,
})
return context
def get_form_kwargs(self):
self.pk = self.kwargs.get('pk')
kwargs = super().get_form_kwargs()
kwargs['uuid'] = self.pk
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
form.save(self.request.user)
response = super().form_valid(form)
return response
def form_invalid(self, form):
response = super().form_invalid(form)
return response
def get_success_url(self):
success_url = reverse_lazy('evidence:details', args=[self.pk])
return success_url
class DeleteEvidenceTagView(DashboardView, DeleteView):
model = SystemProperty
@ -197,14 +178,14 @@ class DeleteEvidenceTagView(DashboardView, DeleteView):
def get(self, request, *args, **kwargs):
self.object = self.get_object()
message = _("<Deleted> Evidence Tag: {}").format(self.object.value)
message = _("<Deleted> Evidence Tag: {}").format(self.object.value)
DeviceLog.objects.create(
snapshot_uuid=self.object.uuid,
event=message,
user=self.request.user,
institution=self.request.user.institution
)
self.object.delete()
self.object.delete()
messages.info(self.request, _("Evicende Tag deleted successfully."))
return self.handle_success()
@ -214,6 +195,6 @@ class DeleteEvidenceTagView(DashboardView, DeleteView):
def get_success_url(self):
return self.request.META.get(
'HTTP_REFERER',
'HTTP_REFERER',
reverse_lazy('evidence:details', args=[self.object.uuid])
)

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -1,46 +1,52 @@
{% extends "login_base.html" %}
{% load i18n static %}
{% load i18n static language_code %}
{% block login_content %}
<div class="pt-2 pb-3">
<h5 class="card-title text-center pb-0 fs-4 help"> {% trans "Sign in" %}</h5>
</div>
<form action="{% url 'login:login' %}" method="post" class="row g-3 needs-validation" novalidate>
{% csrf_token %}
<div class="col-12">
<div class="col-12 mb-">
<input type="email" name="username" maxlength="100" autocapitalize="off"
autocorrect="off" class="form-control textinput textInput" id="yourEmail" required
autocorrect="off" class="form-control textinput textInput {% if form.username.errors %}is-invalid{% endif %}" id="yourEmail" required
autofocus placeholder="{{ form.username.label }}"
{% if form.username.value %}value="{{ form.username.value }}" {% endif %}>
<div class="invalid-feedback">Please enter your email.</div>
{% if form.username.errors %}
<p class="text-error">
{{ form.username.errors|striptags }}
</p>
<div class="invalid-feedback d-block">
{{ form.username.errors|striptags }}
</div>
{% endif %}
</div>
<div class="col-12">
<div class="col-12 mb-3">
<div class="input-group">
<input type="password" name="password" maxlength="100" autocapitalize="off"
autocorrect="off" class="form-control textinput textInput" id="id_password"
placeholder="{{ form.password.label }}" required>
<input type="password" name="password" maxlength="100" autocapitalize="off"
autocorrect="off" class="form-control textinput textInput {% if form.password.errors %}is-invalid{% endif %}" id="id_password"
placeholder="{{ form.password.label }}" required>
<i class="input-group-text bi bi-eye" id="togglePassword" style="cursor: pointer"></i>
</div>
{% if form.password.errors %}
<p class="text-error">
{{ form.password.errors|striptags }}
</p>
{% endif %}
<i class="input-group-text bi bi-eye" id="togglePassword" style="cursor: pointer">
</i>
<div class="invalid-feedback d-block">
{{ form.password.errors|striptags }}
</div>
<div class="invalid-feedback">Please enter your password!</div>
{% endif %}
</div>
<input name="next" type="hidden" value="{{ success_url }}">
<div class="col-12">
<button class="btn btn-primary w-100" type="submit">Next</button>
</div>
<div class="col-12 mb-3">
<button class="btn btn-green-user w-100" type="submit">{% trans "Login" %}</button>
</div>
</form>
<div id="login-footer" class="mt-3">
<a href="{% url 'login:password_reset' %}" data-toggle="modal" data-target="#forgotPasswordModal">{% trans "Forgot your password? Click here to recover" %}</a>
<div id="login-footer" class="d-flex justify-content-between align-items-center mt-4">
<a href="{% url 'login:password_reset' %}" data-toggle="modal" data-target="#forgotPasswordModal">{% trans "Forgot your password?" %}</a>
{% include "language_picker.html" %}
</div>
{% endblock %}

View file

@ -45,7 +45,7 @@
<div class="container">
<section class="section register min-vh-100 d-flex flex-column align-items-center justify-content-center py-4">
<section class="section register min-vh-100 d-flex flex-column align-items-center justify-content-center">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-4 col-md-6 d-flex flex-column align-items-center justify-content-center">
@ -57,51 +57,37 @@
</a>
</div><!-- End Logo -->
<div class="card mb-3 shadow p-3 mb-5 bg-body rounded">
<div class="card shadow bg-body rounded">
<div class="card-body">
<div class="pt-2 pb-3">
<h5 class="card-title text-center pb-0 fs-4 help">Sign in</h5>
</div>
{% block login_content %}
{% endblock login_content %}
</div>
</div>
{% if messages %}
<div class="col-12 mt-3">
{% for message in messages %}
<div class="alert alert-danger show text-center" role="alert">
{{message}}
</div>
{% endfor %}
</div>
{% endif %}
<div class="credits">
</div>
</div>
</div>
</div>
</div>
</section>
</div>
<div class="container-fluid">
<div class="row">
<main class="col-md-12 bt-5">
{% block messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags|default:'info' }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
{% endfor %}
{% endblock messages %}
</main>
</div>
</div>
<!-- Footer -->
<footer class="footer text-center">
<footer class="footer text-center fixed-bottom bg-light py-3">
<div class="container">
<span class="text-muted">{{ commit_id }}</span>
</div>

View file

@ -2,26 +2,23 @@
{% load i18n django_bootstrap5 %}
{% block login_content %}
<div class="well">
<div class="row-fluid">
<h2>{% trans 'Password reset' %}</h2>
<span>{% trans "Forgotten your password? Enter your email address below, and we'll email instructions for setting a new one." %}</span>
</div>
<div class="card-body">
<div class="pt-2 pb-3">
<h4 class="card-title text-center pb-2 help"> {% trans "Password Reset" %}</h5>
</div>
<p class="text-center text-muted fs-6">{% trans "Enter your email address below, and we'll email instructions for setting a new one." %}</p>
<form action="{% url 'login:password_reset' %}" method="post" class="mt-4">
{% csrf_token %}
{% bootstrap_form form layout='floating' %}
{% bootstrap_form_errors form type='non_fields' %}
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">{% trans 'Reset' %}</button>
</div>
</form>
</div>
<div class="well">
<div class="row-fluid">
<div>
<form action="{% url 'login:password_reset' %}" role="form" method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% bootstrap_form_errors form type='non_fields' %}
<div class="form-actions-no-box">
<input type="submit" name="submit" value="{% trans 'Reset my password' %}" class="btn btn-primary form-control" id="submit-id-submit">
</div>
</form>
</div>
</div><!-- /.row-fluid -->
</div><!--/.well-->
{% endblock %}
<div class="text-center mt-3">
<a href="{% url 'login:login' %}" class="btn btn-link text-secondary">{% trans 'Back to login' %}</a>
</div>
{% endblock %}

View file

@ -6,11 +6,19 @@
<div class="row-fluid">
<div class="well" style="width: 800px; margin: auto auto 50px auto">
<div class="row-fluid">
<h2>{% trans 'Password reset complete' %}</h2>
<p>{% trans 'Your password has been set. You may go ahead and log in now.' %}</p>
<a href="{% url 'login:login' %}">{% trans 'Login' %}</a>
<h2 class="card-title ">{% trans 'Password reset complete' %}</h2>
<p class="text-muted fs-6 mt-4">{% trans 'Your new password has been set. You may go ahead and log in now.' %}</p>
</div>
</div><!--/.well-->
</div><!-- /.row-fluid -->
<div class="text-end mt-3">
<a href="{% url 'login:login' %}" class="btn btn-link text-secondary">{% trans 'Back to login' %}</a>
</div>
</div><!-- /.container-fluid -->
{% endblock %}

View file

@ -4,12 +4,14 @@
{% block login_content %}
<div class="well">
<div class="row-fluid">
<h2>{% trans 'Password reset sent' %}</h2>
<p>{% trans "We've emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly." %}</p>
<p>{% trans "If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder." %}</p>
<h4 class="card-title text-center text-bold">{% trans 'Password reset sent' %}</h4>
<p class="text-center text-muted mt-4 fs-7">{% trans "We've sent you an email with instructions to reset your password. If an account with the provided email exists, you should receive it shortly." %}</p>
<p class="text-center text-muted fs-7">{% trans "If you don't receive an email, please check the email address you entered and look in your spam folder." %}</p>
</div><!-- /.row-fluid -->
<div class="text-center mt-3">
<a href="{% url 'login:login' %}" class="btn btn-link text-primary">{% trans 'Back to login' %}</a>
</div>
</div><!--/.well-->
{% endblock %}

View file

@ -1,5 +1,6 @@
import logging
from django.contrib import messages
from django.conf import settings
from django.urls import reverse_lazy
from django.contrib.auth import views as auth_views
@ -39,6 +40,10 @@ class LoginView(auth_views.LoginView):
return redirect(reverse_lazy("login:login"))
return redirect(self.extra_context['success_url'])
def form_invalid(self, form):
messages.error(self.request, _("Login error. Check credentials."))
return self.render_to_response(self.get_context_data(form=form), status=401)
def LogoutView(request):

View file

@ -1,28 +1,27 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<div class="row mb-3">
<div class="col">
<h3>{{ subtitle }}</h3>
</div>
<div class="col text-center">
{% block actions %}
{% if show_closed %}
<a href="?show_closed=false" class="btn btn-green-admin">
{% trans 'Hide closed lots' %}
<i class="bi bi-archive-fill"></i>
</a>
{% else %}
<a href="?show_closed=true" class="btn btn-green-admin">
{% trans 'Show closed lots' %}
<i class="bi bi-archive"></i>
</a>
{% endif %}
<a href="{% url 'lot:add' %}" type="button" class="btn btn-green-admin">
<i class="bi bi-plus"></i>
{% trans 'Add new lot' %}
<i class="bi bi-folder-plus"></i>
{% trans 'New lot' %}
</a>
</div>
</div>
{% endblock %}
{% block content %}
<div class="row">
<table class= "table table-striped table-sm">

View file

@ -1,25 +1,22 @@
{% extends "base.html" %}
{% load i18n %}
{% block actions %}
<a href="{% url 'lot:add_property' lot.pk %}" class="btn btn-green-admin d-flex align-items-center">
<i class="bi bi-plus pe-2"></i>
Add Property
<span class="caret"></span>
</a>
{% endblock %}
{% block content %}
<div class="row">
<div class="col">
<h3>Lot {{ lot.name }}</h3>
</div>
</div>
<h3>Lot {{ lot.name }}</h3>
<div class="row">
<div class="tab-pane fade show active" id="details">
<div class="d-flex justify-content-end mt-1 mb-3">
<a href="{% url 'lot:add_property' lot.pk %}" class="btn btn-green-admin d-flex align-items-center">
<i class="bi bi-plus"></i>
Add new lot Property
<span class="caret"></span>
</a>
</div>
<h5 class="card-title mt-2">Properties</h5>
<table class="table table-hover table-bordered table-responsive align-middle">
<thead class="table-light">
<tr>

View file

@ -17,7 +17,7 @@ from lot.forms import LotsForm
class NewLotView(DashboardView, CreateView):
template_name = "new_lot.html"
title = _("New lot")
breadcrumb = "lot / New lot"
breadcrumb = _("Lot") + " / " + _("New") + " / "
success_url = reverse_lazy('dashboard:unassigned_devices')
model = Lot
fields = (
@ -38,7 +38,7 @@ class NewLotView(DashboardView, CreateView):
class DeleteLotView(DashboardView, DeleteView):
template_name = "delete_lot.html"
title = _("Delete lot")
breadcrumb = "lot / Delete lot"
breadcrumb = _("Lot") + " / " + _("Delete") + " / "
success_url = reverse_lazy('dashboard:unassigned_devices')
model = Lot
fields = (
@ -57,7 +57,7 @@ class DeleteLotView(DashboardView, DeleteView):
class EditLotView(DashboardView, UpdateView):
template_name = "new_lot.html"
title = _("Edit lot")
breadcrumb = "Lot / Edit lot"
breadcrumb = _("Lot") + " / " + _("Edit") + " / "
success_url = reverse_lazy('dashboard:unassigned_devices')
model = Lot
fields = (
@ -83,7 +83,7 @@ class EditLotView(DashboardView, UpdateView):
class AddToLotView(DashboardView, FormView):
template_name = "list_lots.html"
title = _("Add to lots")
breadcrumb = "lot / add to lots"
breadcrumb = _("Lot") + " / " + _("Assign Device") + " / "
success_url = reverse_lazy('dashboard:unassigned_devices')
form_class = LotsForm
@ -112,7 +112,7 @@ class AddToLotView(DashboardView, FormView):
class DelToLotView(AddToLotView):
title = _("Remove from lots")
breadcrumb = "lot / remove from lots"
breadcrumb = _("Lot") + " / " + _("Unassign Device") + " / "
def form_valid(self, form):
form.devices = self.get_session_devices()
@ -124,7 +124,7 @@ class DelToLotView(AddToLotView):
class LotsTagsView(DashboardView, TemplateView):
template_name = "lots.html"
title = _("lots")
breadcrumb = _("lots") + " /"
breadcrumb = _("Lot") + " / "
success_url = reverse_lazy('dashboard:unassigned_devices')
def get_context_data(self, **kwargs):
@ -149,7 +149,7 @@ class LotsTagsView(DashboardView, TemplateView):
class LotPropertiesView(DashboardView, TemplateView):
template_name = "properties.html"
title = _("New Lot Property")
breadcrumb = "Lot / New property"
breadcrumb = _("Lot") + " / " + _("Property") + " / "
def get_context_data(self, **kwargs):
self.pk = kwargs.get('pk')
@ -172,7 +172,7 @@ class LotPropertiesView(DashboardView, TemplateView):
class AddLotPropertyView(DashboardView, CreateView):
template_name = "new_property.html"
title = _("New Lot Property")
breadcrumb = "Device / New property"
breadcrumb = _("Lot") + " / " + _("Property") + " / " +_("New")
success_url = reverse_lazy('dashboard:unassigned_devices')
model = LotProperty
fields = ("key", "value")
@ -206,7 +206,7 @@ class AddLotPropertyView(DashboardView, CreateView):
class UpdateLotPropertyView(DashboardView, UpdateView):
template_name = "properties.html"
title = _("Update lot Property")
breadcrumb = "Lot / Update Property"
breadcrumb = _("Lot") + " / " + _("Property") + " / " +_("Update")
model = LotProperty
fields = ("key", "value")

View file

@ -1,27 +1,53 @@
{% extends "base.html" %}
{% load i18n %}
{% load i18n get_language_code %}
{% block content %}
<div class="row mb-3">
<div class="col">
<h3>{{ subtitle }}</h3>
</div>
<div class="col text-center">
{{ user.email }}
</div>
</div>
<div class="row">
<div class="col">
<a class="nav-link fw-bold" href="{% url 'api:tokens' %}">
{% translate 'Admin your Tokens' %}
</a>
</div>
<div class="col">
<a class="nav-link fw-bold" href="{% url 'user:settings' %}">
{% translate 'Download a settings file' %}
</a>
</div>
</div>
<div class="row mb-5">
<div class="col d-flex align-items-center">
{% endblock %}
<i class="bi bi-person-circle fs-2 me-3"></i>
<h3 class="mb-0">{{ user.email }}</h3>
</div>
{# The language picker is mostly here to fill up some space on user settings #}
<div class="col text-end">
{% include "language_picker.html" %}
</div>
</div>
<div class="row mb-4">
<div class="col-md-6">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column">
<h5 class="card-title mb-3">
<i class="bi bi-key me-2"></i> {% translate 'Token Management' %}
</h5>
<p class="card-text flex-grow-1">{% translate 'Manage your personal tokens for using Devicehub.' %}</p>
<div class="text-end">
<a href="{% url 'api:tokens' %}" class="btn btn-outline-dark btn-sm d-inline-flex align-items-center">
<span class="me-2">{% translate 'Go' %}</span>
<i class="bi bi-arrow-right"></i>
</a>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100 shadow-sm border-0">
<div class="card-body d-flex flex-column">
<h5 class="card-title mb-3">
<i class="bi bi-gear me-2"></i> {% translate 'Settings File' %}
</h5>
<p class="card-text flex-grow-1">{% translate 'Download a settings file for your Workbench.' %}</p>
<div class="text-end">
<a href="{% url 'user:settings' %}" class="btn btn-outline-dark btn-sm d-inline-flex align-items-center">
<span class="me-2">{% translate 'Go' %}</span>
<i class="bi bi-arrow-right"></i>
</a>
</div>
</div>
</div>
</div>
</div>{% endblock %}