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" %}
<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>
{% endblock %}

View file

@ -1,34 +1,38 @@
{% 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>
<div class="row">
<div class="col">
<table class="table">
<thead>
<table class="table table-hover table-bordered">
<thead class="table-light">
<tr>
<th scope="col">Email</th>
<th>is Admin</th>
<th></th>
<th></th>
<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>
<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>

View file

@ -1,17 +1,22 @@
{% 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="row mb-4">
<div class="col">
Are you sure than want remove the lot {{ object.name }} with {{ object.devices.count }} devices.
<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>
@ -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="ms-3 mt-4">
<div class="row mb-3">
<div class="col">
<h3>{{ subtitle }}</h3>
</div>
</div>
{% load django_bootstrap5 %}
<form role="form" method="post">
<form role="form" method="post" enctype="multipart/form-data">
{% 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 />
<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 %}
<button class="btn-close" type="button" data-dismiss="alert" aria-label="Close"></button>
</div>
</ul>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</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 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">
@ -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,17 +219,25 @@
</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>
</div>
</footer>
@ -230,6 +245,15 @@
<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">
<i class="bi bi-reply"></i>
{% trans 'Exports' %}
{% 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' %}
{% trans 'Properties' %}
</a>
{% endif %}
</div>
</div>
<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>
<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>
<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" data-sortable="">
select
<th scope="col" class="text-center">
<input type="checkbox" id="select-all" />
</th>
<th scope="col" data-sortable="">
shortid
<th scope="col" class="text-center">
{% trans "Short ID" %}
</th>
<th scope="col" data-sortable="">
type
<th scope="col" class="text-center">
{% trans "Type" %}
</th>
<th scope="col" data-sortable="">
manufacturer
<th scope="col" class="text-center">
{% trans "Manufacturer" %}
</th>
<th scope="col" data-sortable="">
model
<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>
{% for dev in devices %}
<tbody>
{% for dev in devices %}
<tr>
<td>
<td class="text-center">
<input type="checkbox" name="devices" value="{{ dev.id }}" />
</td>
<td>
<td class="text-center">
<a href="{% url 'device:details' dev.id %}">
{{ dev.shortid }}
</a>
</td>
<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>
<td class="text-center">
{{ dev.manufacturer }}
</td>
<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 %}
{% block actions %}
<!-- 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">
<!-- 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">
<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 %}
<button class="btn-close" type="button" data-dismiss="alert" aria-label="Close"></button>
</div>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</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">
@ -25,4 +26,6 @@
</tbody>
</table>
</div>
{% render_pagination device_logs.number device_logs.paginator.num_pages 10 %}
</div>

View file

@ -2,47 +2,44 @@
{% load i18n %}
<div class="tab-pane fade" id="user_properties">
<div class="d-flex justify-content-end mt-1 mb-3">
<div class="d-flex justify-content-end mt-3 mb-4">
<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>
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">
<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="5%" class="text-end" title="{% trans 'Actions' %}"></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>
<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-info d-flex align-items-center" data-bs-toggle="modal"
<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"
<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 }}">
data-bs-toggle="modal"
data-bs-target="#deleteModal{{ a.id }}">
<i class="bi bi-trash me-1"></i>
{% trans 'Delete' %}
</button>
@ -53,39 +50,27 @@
</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 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>
<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>
<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>
<button type="submit" class="btn btn-danger">{% trans "Delete" %}</button>
</form>
</div>
</div>
@ -98,30 +83,24 @@
<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 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>
<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>
<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>
<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>

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,13 +105,16 @@ 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)
SystemProperty.objects.create(
@ -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,28 +1,89 @@
{% 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 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>
</li>
</ul>
</div>
</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">
<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' %}" />
<!-- 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

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">
<div class="invalid-feedback d-block">
{{ form.username.errors|striptags }}
</p>
</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"
autocorrect="off" class="form-control textinput textInput {% if form.password.errors %}is-invalid{% endif %}" id="id_password"
placeholder="{{ form.password.label }}" required>
{% 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>
<i class="input-group-text bi bi-eye" id="togglePassword" style="cursor: pointer"></i>
</div>
<div class="invalid-feedback">Please enter your password!</div>
{% if form.password.errors %}
<div class="invalid-feedback d-block">
{{ form.password.errors|striptags }}
</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 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,21 +57,24 @@
</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>
@ -83,25 +86,8 @@
</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 class="card-body">
<div class="pt-2 pb-3">
<h4 class="card-title text-center pb-2 help"> {% trans "Password Reset" %}</h5>
</div>
</div>
<div class="well">
<div class="row-fluid">
<div>
<form action="{% url 'login:password_reset' %}" role="form" method="post">
<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 %}
{% bootstrap_form form layout='floating' %}
{% 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 class="d-grid gap-2">
<button type="submit" class="btn btn-primary">{% trans 'Reset' %}</button>
</div>
</form>
</div>
</div><!-- /.row-fluid -->
</div><!--/.well-->
<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
@ -40,6 +41,10 @@ class LoginView(auth_views.LoginView):
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):
auth_logout(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>
<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 class="row mb-5">
<div class="col d-flex align-items-center">
<i class="bi bi-person-circle fs-2 me-3"></i>
<h3 class="mb-0">{{ user.email }}</h3>
</div>
<div class="col text-center">
{{ user.email }}
{# 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">
<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' %}
<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>
{% endblock %}
</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 %}