Merge branch 'main' into feature/90-implement-public-website-for-device

This commit is contained in:
sergio_gimenez 2024-10-23 08:54:12 +02:00
commit b1e45bd94e
24 changed files with 349 additions and 73 deletions

View File

@ -1,2 +1,20 @@
DOMAIN=localhost DOMAIN=localhost
DEMO=false DEMO=false
STATIC_ROOT=/tmp/static/
MEDIA_ROOT=/tmp/media/
ALLOWED_HOSTS=localhost,localhost:8000,127.0.0.1,
DOMAIN=localhost
DEBUG=True
EMAIL_HOST="mail.example.org"
EMAIL_HOST_USER="fillme_noreply"
EMAIL_HOST_PASSWORD="fillme_passwd"
EMAIL_PORT=587
EMAIL_USE_TLS=True
EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend"
EMAIL_FILE_PATH="/tmp/app-messages"
ENABLE_EMAIL=false
PREDEFINED_TOKEN='5018dd65-9abd-4a62-8896-80f34ac66150'
# TODO review these vars
#SNAPSHOTS_DIR=/path/to/TODO
#EVIDENCES_DIR=/path/to/TODO

View File

@ -1,33 +1,27 @@
import json import json
from django.conf import settings from uuid import uuid4
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.core.exceptions import ValidationError
from django_tables2 import SingleTableView from django_tables2 import SingleTableView
from django.views.generic.base import View
from django.views.generic.edit import ( from django.views.generic.edit import (
CreateView, CreateView,
DeleteView, DeleteView,
UpdateView, UpdateView,
) )
from django.http import JsonResponse
from uuid import uuid4
from utils.save_snapshots import move_json, save_in_disk
from dashboard.mixins import DashboardView from dashboard.mixins import DashboardView
from evidence.models import Annotation from evidence.models import Annotation
from evidence.parse import Build from evidence.parse import Build
from user.models import User
from api.models import Token from api.models import Token
from api.tables import TokensTable from api.tables import TokensTable
def save_in_disk(data, user):
pass
@csrf_exempt @csrf_exempt
def NewSnapshot(request): def NewSnapshot(request):
# Accept only posts # Accept only posts
@ -60,15 +54,16 @@ def NewSnapshot(request):
).first() ).first()
if exist_annotation: if exist_annotation:
raise ValidationError("error: the snapshot {} exist".format(data['uuid'])) txt = "error: the snapshot {} exist".format(data['uuid'])
return JsonResponse({'status': txt}, status=500)
# Process snapshot # Process snapshot
# save_in_disk(data, tk.user) path_name = save_in_disk(data, tk.owner.institution.name)
try: try:
Build(data, tk.owner) Build(data, tk.owner)
except Exception: except Exception as err:
return JsonResponse({'status': 'fail'}, status=200) return JsonResponse({'status': f"fail: {err}"}, status=500)
annotation = Annotation.objects.filter( annotation = Annotation.objects.filter(
uuid=data['uuid'], uuid=data['uuid'],
@ -80,19 +75,20 @@ def NewSnapshot(request):
if not annotation: if not annotation:
return JsonResponse({'status': 'fail'}, status=200) return JsonResponse({'status': 'fail'}, status=500)
url_args = reverse_lazy("device:details", args=(annotation.value,))
url = request.build_absolute_uri(url_args)
url = "{}://{}{}".format(
request.scheme,
settings.DOMAIN,
reverse_lazy("device:details", args=(annotation.value,))
)
response = { response = {
"status": "success", "status": "success",
"dhid": annotation.value[:6].upper(), "dhid": annotation.value[:6].upper(),
"url": url, "url": url,
# TODO replace with public_url when available
"public_url": url "public_url": url
} }
move_json(path_name, tk.owner.institution.name)
return JsonResponse(response, status=200) return JsonResponse(response, status=200)

View File

@ -1,4 +1,5 @@
from django.urls import resolve from django.urls import resolve
from django.conf import settings
from django.shortcuts import get_object_or_404, redirect, Http404 from django.shortcuts import get_object_or_404, redirect, Http404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
@ -32,6 +33,7 @@ class DashboardView(LoginRequiredMixin):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context.update({ context.update({
"commit_id": settings.COMMIT,
'title': self.title, 'title': self.title,
'subtitle': self.subtitle, 'subtitle': self.subtitle,
'breadcrumb': self.breadcrumb, 'breadcrumb': self.breadcrumb,

View File

@ -69,23 +69,17 @@ class SearchView(InventaryMixin):
if not matches.size(): if not matches.size():
return self.search_hids(query, offset, limit) return self.search_hids(query, offset, limit)
annotations = [] devices = []
for x in matches: for x in matches:
annotations.extend(self.get_annotations(x)) devices.append(self.get_annotations(x))
devices = [Device(id=x) for x in set(annotations)]
count = matches.size() count = matches.size()
return devices, count return devices, count
def get_annotations(self, xp): def get_annotations(self, xp):
snap = xp.document.get_data() snap = xp.document.get_data()
uuid = json.loads(snap).get('uuid') uuid = json.loads(snap).get('uuid')
return Device.get_annotation_from_uuid(uuid, self.request.user.institution)
return Annotation.objects.filter(
type=Annotation.Type.SYSTEM,
owner=self.request.user.institution,
uuid=uuid
).values_list("value", flat=True).distinct()
def search_hids(self, query, offset, limit): def search_hids(self, query, offset, limit):
qry = Q() qry = Q()

View File

@ -1,5 +1,6 @@
from django import forms from django import forms
from utils.device import create_annotation, create_doc, create_index from utils.device import create_annotation, create_doc, create_index
from utils.save_snapshots import move_json, save_in_disk
DEVICE_TYPES = [ DEVICE_TYPES = [
@ -56,8 +57,11 @@ class BaseDeviceFormSet(forms.BaseFormSet):
if not commit: if not commit:
return doc return doc
path_name = save_in_disk(doc, self.user.institution.name, place="placeholder")
create_index(doc, self.user) create_index(doc, self.user)
create_annotation(doc, user, commit=commit) create_annotation(doc, user, commit=commit)
move_json(path_name, self.user.institution.name, place="placeholder")
return doc return doc

View File

@ -90,9 +90,11 @@ class Device:
def get_hids(self): def get_hids(self):
annotations = self.get_annotations() annotations = self.get_annotations()
algos = list(ALGOS.keys())
algos.append('CUSTOM_ID')
self.hids = list(set(annotations.filter( self.hids = list(set(annotations.filter(
type=Annotation.Type.SYSTEM, type=Annotation.Type.SYSTEM,
key__in=ALGOS.keys(), key__in=algos,
).values_list("value", flat=True))) ).values_list("value", flat=True)))
def get_evidences(self): def get_evidences(self):
@ -118,9 +120,32 @@ class Device:
def get_unassigned(cls, institution, offset=0, limit=None): def get_unassigned(cls, institution, offset=0, limit=None):
sql = """ sql = """
SELECT DISTINCT t1.value from evidence_annotation as t1 WITH RankedAnnotations AS (
left join lot_devicelot as t2 on t1.value = t2.device_id SELECT
where t2.device_id is null and owner_id=={institution} and type=={type} t1.value,
t1.key,
ROW_NUMBER() OVER (
PARTITION BY t1.uuid
ORDER BY
CASE
WHEN t1.key = 'CUSTOM_ID' THEN 1
WHEN t1.key = 'hidalgo1' THEN 2
ELSE 3
END,
t1.created DESC
) AS row_num
FROM evidence_annotation AS t1
LEFT JOIN lot_devicelot AS t2 ON t1.value = t2.device_id
WHERE t2.device_id IS NULL
AND t1.owner_id = {institution}
AND t1.type = {type}
)
SELECT DISTINCT
value
FROM
RankedAnnotations
WHERE
row_num = 1
""".format( """.format(
institution=institution.id, institution=institution.id,
type=Annotation.Type.SYSTEM, type=Annotation.Type.SYSTEM,
@ -144,18 +169,83 @@ class Device:
def get_unassigned_count(cls, institution): def get_unassigned_count(cls, institution):
sql = """ sql = """
SELECT count(DISTINCT t1.value) from evidence_annotation as t1 WITH RankedAnnotations AS (
left join lot_devicelot as t2 on t1.value = t2.device_id SELECT
where t2.device_id is null and owner_id=={institution} and type=={type}; t1.value,
t1.key,
ROW_NUMBER() OVER (
PARTITION BY t1.uuid
ORDER BY
CASE
WHEN t1.key = 'CUSTOM_ID' THEN 1
WHEN t1.key = 'hidalgo1' THEN 2
ELSE 3
END,
t1.created DESC
) AS row_num
FROM evidence_annotation AS t1
LEFT JOIN lot_devicelot AS t2 ON t1.value = t2.device_id
WHERE t2.device_id IS NULL
AND t1.owner_id = {institution}
AND t1.type = {type}
)
SELECT
COUNT(DISTINCT value)
FROM
RankedAnnotations
WHERE
row_num = 1
""".format( """.format(
institution=institution.id, institution=institution.id,
type=Annotation.Type.SYSTEM, type=Annotation.Type.SYSTEM,
) )
with connection.cursor() as cursor: with connection.cursor() as cursor:
cursor.execute(sql) cursor.execute(sql)
return cursor.fetchall()[0][0] return cursor.fetchall()[0][0]
@classmethod
def get_annotation_from_uuid(cls, uuid, institution):
sql = """
WITH RankedAnnotations AS (
SELECT
t1.value,
t1.key,
ROW_NUMBER() OVER (
PARTITION BY t1.uuid
ORDER BY
CASE
WHEN t1.key = 'CUSTOM_ID' THEN 1
WHEN t1.key = 'hidalgo1' THEN 2
ELSE 3
END,
t1.created DESC
) AS row_num
FROM evidence_annotation AS t1
LEFT JOIN lot_devicelot AS t2 ON t1.value = t2.device_id
WHERE t2.device_id IS NULL
AND t1.owner_id = {institution}
AND t1.type = {type}
AND t1.uuid = '{uuid}'
)
SELECT DISTINCT
value
FROM
RankedAnnotations
WHERE
row_num = 1;
""".format(
uuid=uuid.replace("-", ""),
institution=institution.id,
type=Annotation.Type.SYSTEM,
)
annotations = []
with connection.cursor() as cursor:
cursor.execute(sql)
annotations = cursor.fetchall()
return cls(id=annotations[0][0])
@property @property
def is_websnapshot(self): def is_websnapshot(self):
if not self.last_evidence: if not self.last_evidence:

View File

@ -8,6 +8,43 @@
</div> </div>
</div> </div>
<div class="row">
<div class="col">
<ul class="nav nav-tabs nav-tabs-bordered">
<li class="nav-items">
<a href="#details" class="nav-link active" data-bs-toggle="tab" data-bs-target="#details">General details</a>
</li>
<li class="nav-items">
<a href="#annotations" class="nav-link" data-bs-toggle="tab" data-bs-target="#annotations">User annotations</a>
</li>
<li class="nav-items">
<a href="#documents" class="nav-link" data-bs-toggle="tab" data-bs-target="#documents">Documents</a>
</li>
<li class="nav-items">
<a href="#lots" class="nav-link" data-bs-toggle="tab" data-bs-target="#lots">Lots</a>
</li>
<li class="nav-items">
<a href="#components" class="nav-link" data-bs-toggle="tab" data-bs-target="#components">Components</a>
</li>
<li class="nav-items">
<a href="#evidences" class="nav-link" data-bs-toggle="tab" data-bs-target="#evidences">Evidences</a>
</li>
<li class="nav-items">
<a href="#web" class="nav-link" href="">Web</a>
</li>
</ul>
</div>
</div>
<div class="tab-content pt-2">
<div class="tab-pane fade show active" id="details">
<h5 class="card-title">Details</h5>
<div class="row mb-3">
<div class="col-lg-3 col-md-4 label ">Phid</div>
<div class="col-lg-9 col-md-8">{{ object.id }}</div>
</div>
</div>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<ul class="nav nav-tabs nav-tabs-bordered"> <ul class="nav nav-tabs nav-tabs-bordered">
@ -228,3 +265,25 @@
}) })
</script> </script>
{% endblock %} {% endblock %}
{% block extrascript %}
<script>
document.addEventListener("DOMContentLoaded", function() {
// Obtener el hash de la URL (ejemplo: #components)
const hash = window.location.hash;
// Verificar si hay un hash en la URL
if (hash) {
// Buscar el botón o enlace que corresponde al hash y activarlo
const tabTrigger = document.querySelector(`[data-bs-target="${hash}"]`);
if (tabTrigger) {
// Crear una instancia de tab de Bootstrap para activar el tab
const tab = new bootstrap.Tab(tabTrigger);
tab.show();
}
}
});
</script>
{% endblock %}

View File

@ -10,6 +10,7 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.0/ref/settings/ https://docs.djangoproject.com/en/5.0/ref/settings/
""" """
import os
import xapian import xapian
from pathlib import Path from pathlib import Path
@ -35,7 +36,7 @@ assert DOMAIN not in [None, ''], "DOMAIN var is MANDATORY"
print("DOMAIN: " + DOMAIN) print("DOMAIN: " + DOMAIN)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=DOMAIN, cast=Csv()) ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=DOMAIN, cast=Csv())
assert DOMAIN in ALLOWED_HOSTS, "DOMAIN is not ALLOWED_HOST" assert DOMAIN in ALLOWED_HOSTS, f"DOMAIN {DOMAIN} is not in ALLOWED_HOSTS {ALLOWED_HOSTS}"
CSRF_TRUSTED_ORIGINS = config('CSRF_TRUSTED_ORIGINS', default=f'https://{DOMAIN}', cast=Csv()) CSRF_TRUSTED_ORIGINS = config('CSRF_TRUSTED_ORIGINS', default=f'https://{DOMAIN}', cast=Csv())
@ -62,6 +63,7 @@ EMAIL_FILE_PATH = config('EMAIL_FILE_PATH', default='/tmp/app-messages')
ENABLE_EMAIL = config("ENABLE_EMAIL", default=True, cast=bool) ENABLE_EMAIL = config("ENABLE_EMAIL", default=True, cast=bool)
EVIDENCES_DIR = config("EVIDENCES_DIR", default=os.path.join(BASE_DIR, "db"))
# Application definition # Application definition
@ -156,12 +158,20 @@ AUTH_PASSWORD_VALIDATORS = [
LANGUAGE_CODE = "en-us" LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC" TIME_ZONE = config("TIME_ZONE", default="UTC")
USE_I18N = True USE_I18N = True
USE_TZ = False
if TIME_ZONE == "UTC":
USE_TZ = True USE_TZ = True
USE_L10N = True
LANGUAGES = [
('es', 'Spanish'),
('en', 'English'),
]
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.0/howto/static-files/ # https://docs.djangoproject.com/en/5.0/howto/static-files/
@ -206,3 +216,4 @@ LOGGING = {
SNAPSHOT_PATH="/tmp/" SNAPSHOT_PATH="/tmp/"
DATA_UPLOAD_MAX_NUMBER_FILES = 1000 DATA_UPLOAD_MAX_NUMBER_FILES = 1000
COMMIT = config('COMMIT', default='')

View File

@ -6,7 +6,9 @@ services:
environment: environment:
- DEBUG=true - DEBUG=true
- DOMAIN=${DOMAIN:-localhost} - DOMAIN=${DOMAIN:-localhost}
- DEMO=${DEMO:-n} - ALLOWED_HOSTS=${ALLOWED_HOSTS:-$DOMAIN}
- DEMO=${DEMO:-false}
- PREDEFINED_TOKEN=${PREDEFINED_TOKEN:-}
volumes: volumes:
- .:/opt/devicehub-django - .:/opt/devicehub-django
ports: ports:

View File

@ -9,11 +9,13 @@ set -u
set -x set -x
main() { main() {
cd "$(dirname "${0}")"
if [ "${DETACH:-}" ]; then if [ "${DETACH:-}" ]; then
detach_arg='-d' detach_arg='-d'
fi fi
# remove old database # remove old database
sudo rm -vf db/* sudo rm -vfr ./db/*
docker compose down -v docker compose down -v
docker compose build docker compose build
docker compose up ${detach_arg:-} docker compose up ${detach_arg:-}

View File

@ -12,6 +12,14 @@ check_app_is_there() {
} }
deploy() { deploy() {
# TODO this is weird, find better workaround
git config --global --add safe.directory /opt/devicehub-django
export COMMIT=$(git log --format="%H %ad" --date=iso -n 1)
if [ "${DEBUG:-}" = 'true' ]; then
./manage.py print_settings
fi
# detect if existing deployment (TODO only works with sqlite) # detect if existing deployment (TODO only works with sqlite)
if [ -f "${program_dir}/db/db.sqlite3" ]; then if [ -f "${program_dir}/db/db.sqlite3" ]; then
echo "INFO: detected EXISTING deployment" echo "INFO: detected EXISTING deployment"
@ -24,11 +32,13 @@ deploy() {
INIT_ORG="${INIT_ORG:-example-org}" INIT_ORG="${INIT_ORG:-example-org}"
INIT_USER="${INIT_USER:-user@example.org}" INIT_USER="${INIT_USER:-user@example.org}"
INIT_PASSWD="${INIT_PASSWD:-1234}" INIT_PASSWD="${INIT_PASSWD:-1234}"
ADMIN='True'
PREDEFINED_TOKEN="${PREDEFINED_TOKEN:-}"
./manage.py add_institution "${INIT_ORG}" ./manage.py add_institution "${INIT_ORG}"
# TODO: one error on add_user, and you don't add user anymore # TODO: one error on add_user, and you don't add user anymore
./manage.py add_user "${INIT_ORG}" "${INIT_USER}" "${INIT_PASSWD}" ./manage.py add_user "${INIT_ORG}" "${INIT_USER}" "${INIT_PASSWD}" "${ADMIN}" "${PREDEFINED_TOKEN}"
if [ "${DEMO:-}" ]; then if [ "${DEMO:-}" = 'true' ]; then
./manage.py up_snapshots example/snapshots/ "${INIT_USER}" ./manage.py up_snapshots example/snapshots/ "${INIT_USER}"
fi fi
fi fi
@ -36,7 +46,7 @@ deploy() {
runserver() { runserver() {
PORT="${PORT:-8000}" PORT="${PORT:-8000}"
if [ "${DEBUG:-}" ]; then if [ "${DEBUG:-}" = 'true' ]; then
./manage.py runserver 0.0.0.0:${PORT} ./manage.py runserver 0.0.0.0:${PORT}
else else
# TODO # TODO

View File

@ -9,6 +9,7 @@ from utils.forms import MultipleFileField
from device.models import Device from device.models import Device
from evidence.parse import Build from evidence.parse import Build
from evidence.models import Annotation from evidence.models import Annotation
from utils.save_snapshots import move_json, save_in_disk
class UploadForm(forms.Form): class UploadForm(forms.Form):
@ -48,7 +49,9 @@ class UploadForm(forms.Form):
return return
for ev in self.evidences: for ev in self.evidences:
path_name = save_in_disk(ev[1], user.institution.name)
Build(ev[1], user) Build(ev[1], user)
move_json(path_name, user.institution.name)
class UserTagForm(forms.Form): class UserTagForm(forms.Form):
@ -151,8 +154,11 @@ class ImportForm(forms.Form):
if commit: if commit:
for doc, cred in table: for doc, cred in table:
path_name = save_in_disk(doc, self.user.institution.name, place="placeholder")
cred.save() cred.save()
create_index(doc, self.user) create_index(doc, self.user)
move_json(path_name, self.user.institution.name, place="placeholder")
return table return table
return return

View File

@ -67,7 +67,7 @@ class Evidence:
for xa in matches: for xa in matches:
self.doc = json.loads(xa.document.get_data()) self.doc = json.loads(xa.document.get_data())
if self.doc.get("software") == "EreuseWorkbench": if self.doc.get("software") == "workbench-script":
dmidecode_raw = self.doc["data"]["dmidecode"] dmidecode_raw = self.doc["data"]["dmidecode"]
self.dmi = DMIParse(dmidecode_raw) self.dmi = DMIParse(dmidecode_raw)
@ -80,7 +80,7 @@ class Evidence:
self.created = self.annotations.last().created self.created = self.annotations.last().created
def get_components(self): def get_components(self):
if self.doc.get("software") != "EreuseWorkbench": if self.doc.get("software") != "workbench-script":
return self.doc.get('components', []) return self.doc.get('components', [])
self.set_components() self.set_components()
return self.components return self.components
@ -92,7 +92,7 @@ class Evidence:
return "" return ""
return list(self.doc.get('kv').values())[0] return list(self.doc.get('kv').values())[0]
if self.doc.get("software") != "EreuseWorkbench": if self.doc.get("software") != "workbench-script":
return self.doc['device']['manufacturer'] return self.doc['device']['manufacturer']
return self.dmi.manufacturer().strip() return self.dmi.manufacturer().strip()
@ -104,13 +104,13 @@ class Evidence:
return "" return ""
return list(self.doc.get('kv').values())[1] return list(self.doc.get('kv').values())[1]
if self.doc.get("software") != "EreuseWorkbench": if self.doc.get("software") != "workbench-script":
return self.doc['device']['model'] return self.doc['device']['model']
return self.dmi.model().strip() return self.dmi.model().strip()
def get_chassis(self): def get_chassis(self):
if self.doc.get("software") != "EreuseWorkbench": if self.doc.get("software") != "workbench-script":
return self.doc['device']['model'] return self.doc['device']['model']
chassis = self.dmi.get("Chassis")[0].get("Type", '_virtual') chassis = self.dmi.get("Chassis")[0].get("Type", '_virtual')
@ -126,7 +126,8 @@ class Evidence:
return Annotation.objects.filter( return Annotation.objects.filter(
owner=user.institution, owner=user.institution,
type=Annotation.Type.SYSTEM, type=Annotation.Type.SYSTEM,
).order_by("-created").values_list("uuid", flat=True).distinct() key="hidalgo1",
).order_by("-created").values_list("uuid", "created").distinct()
def set_components(self): def set_components(self):
snapshot = ParseSnapshot(self.doc).snapshot_json snapshot = ParseSnapshot(self.doc).snapshot_json

View File

@ -5,6 +5,8 @@ import hashlib
from datetime import datetime from datetime import datetime
from dmidecode import DMIParse from dmidecode import DMIParse
from json_repair import repair_json
from evidence.models import Annotation from evidence.models import Annotation
from evidence.xapian import index from evidence.xapian import index
from utils.constants import ALGOS, CHASSIS_DH from utils.constants import ALGOS, CHASSIS_DH
@ -20,7 +22,12 @@ def get_network_cards(child, nets):
def get_mac(lshw): def get_mac(lshw):
nets = [] nets = []
try: try:
get_network_cards(json.loads(lshw), nets) hw = json.loads(lshw)
except json.decoder.JSONDecodeError:
hw = json.loads(repair_json(lshw))
try:
get_network_cards(hw, nets)
except Exception as ss: except Exception as ss:
print("WARNING!! {}".format(ss)) print("WARNING!! {}".format(ss))
return return
@ -57,7 +64,7 @@ class Build:
} }
def get_hid_14(self): def get_hid_14(self):
if self.json.get("software") == "EreuseWorkbench": if self.json.get("software") == "workbench-script":
hid = self.get_hid(self.json) hid = self.get_hid(self.json)
else: else:
device = self.json['device'] device = self.json['device']
@ -113,7 +120,8 @@ class Build:
# mac = get_mac2(hwinfo_raw) or "" # mac = get_mac2(hwinfo_raw) or ""
mac = get_mac(lshw) or "" mac = get_mac(lshw) or ""
if not mac: if not mac:
print("WARNING!! No there are MAC address") print(f"WARNING: Could not retrieve MAC address in snapshot {snapshot['uuid']}" )
# TODO generate system annotation for that snapshot
else: else:
print(f"{manufacturer}{model}{chassis}{serial_number}{sku}{mac}") print(f"{manufacturer}{model}{chassis}{serial_number}{sku}{mac}")

View File

@ -3,6 +3,8 @@ import numpy as np
from datetime import datetime from datetime import datetime
from dmidecode import DMIParse from dmidecode import DMIParse
from json_repair import repair_json
from utils.constants import CHASSIS_DH, DATASTORAGEINTERFACE from utils.constants import CHASSIS_DH, DATASTORAGEINTERFACE
@ -160,6 +162,7 @@ class ParseSnapshot:
continue continue
model = sm.get('model_name') model = sm.get('model_name')
manufacturer = None manufacturer = None
hours = sm.get("power_on_time", {}).get("hours", 0)
if model and len(model.split(" ")) > 1: if model and len(model.split(" ")) > 1:
mm = model.split(" ") mm = model.split(" ")
model = mm[-1] model = mm[-1]
@ -175,6 +178,7 @@ class ParseSnapshot:
"size": self.get_data_storage_size(sm), "size": self.get_data_storage_size(sm),
"variant": sm.get("firmware_version"), "variant": sm.get("firmware_version"),
"interface": self.get_data_storage_interface(sm), "interface": self.get_data_storage_interface(sm),
"hours": hours,
} }
) )
@ -478,7 +482,11 @@ class ParseSnapshot:
def loads(self, x): def loads(self, x):
if isinstance(x, str): if isinstance(x, str):
try: try:
return json.loads(x) try:
hw = json.loads(lshw)
except json.decoder.JSONDecodeError:
hw = json.loads(repair_json(lshw))
return hw
except Exception as ss: except Exception as ss:
print("WARNING!! {}".format(ss)) print("WARNING!! {}".format(ss))
return {} return {}

View File

@ -14,10 +14,13 @@
{% for ev in evidences %} {% for ev in evidences %}
<tr> <tr>
<td> <td>
<a href="{% url 'evidence:details' ev %}">{{ ev }}</a> <a href="{% url 'evidence:details' ev.0 %}">{{ ev.0 }}</a>
</td> </td>
<td> <td>
<a href="{# url 'evidence:delete' ev #}"><i class="bi bi-trash text-danger"></i></a> <small class="text-muted">{{ ev.1 }}</small>
</td>
<td>
<a href="{# url 'evidence:delete' ev.0 #}"><i class="bi bi-trash text-danger"></i></a>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@ -42,4 +42,5 @@
<div id="login-footer" class="mt-3"> <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> <a href="{% url 'login:password_reset' %}" data-toggle="modal" data-target="#forgotPasswordModal">{% trans "Forgot your password? Click here to recover" %}</a>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,5 +1,6 @@
import logging import logging
from django.conf import settings
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.contrib.auth import views as auth_views from django.contrib.auth import views as auth_views
from django.contrib.auth import login as auth_login from django.contrib.auth import login as auth_login
@ -17,7 +18,7 @@ class LoginView(auth_views.LoginView):
extra_context = { extra_context = {
'title': _('Login'), 'title': _('Login'),
'success_url': reverse_lazy('dashboard:unassigned_devices'), 'success_url': reverse_lazy('dashboard:unassigned_devices'),
# 'commit_id': settings.COMMIT, 'commit_id': settings.COMMIT,
} }
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
@ -65,7 +66,6 @@ class PasswordResetView(auth_views.PasswordResetView):
success_url = reverse_lazy('login:password_reset_done') success_url = reverse_lazy('login:password_reset_done')
def form_valid(self, form): def form_valid(self, form):
import pdb; pdb.set_trace()
try: try:
response = super().form_valid(form) response = super().form_valid(form)
return response return response

View File

@ -31,7 +31,7 @@ class Lot(models.Model):
name = models.CharField(max_length=STR_SIZE, blank=True, null=True) name = models.CharField(max_length=STR_SIZE, blank=True, null=True)
code = models.CharField(max_length=STR_SIZE, blank=True, null=True) code = models.CharField(max_length=STR_SIZE, blank=True, null=True)
description = models.CharField(max_length=STR_SIZE, blank=True, null=True) description = models.CharField(max_length=STR_SIZE, blank=True, null=True)
closed = models.BooleanField(default=True) closed = models.BooleanField(default=False)
owner = models.ForeignKey(Institution, on_delete=models.CASCADE) owner = models.ForeignKey(Institution, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True) user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
type = models.ForeignKey(LotTag, on_delete=models.CASCADE) type = models.ForeignKey(LotTag, on_delete=models.CASCADE)

View File

@ -7,6 +7,16 @@
<h3>{{ subtitle }}</h3> <h3>{{ subtitle }}</h3>
</div> </div>
<div class="col text-center"> <div class="col text-center">
{% if show_closed %}
<a href="?show_closed=false" class="btn btn-green-admin">
{% trans 'Hide closed lots' %}
</a>
{% else %}
<a href="?show_closed=true" class="btn btn-green-admin">
{% trans 'Show closed lots' %}
</a>
{% endif %}
<a href="{% url 'lot:add' %}" type="button" class="btn btn-green-admin"> <a href="{% url 'lot:add' %}" type="button" class="btn btn-green-admin">
<i class="bi bi-plus"></i> <i class="bi bi-plus"></i>
{% trans 'Add new lot' %} {% trans 'Add new lot' %}

View File

@ -131,11 +131,13 @@ class LotsTagsView(DashboardView, TemplateView):
tag = get_object_or_404(LotTag, owner=self.request.user.institution, id=self.pk) tag = get_object_or_404(LotTag, owner=self.request.user.institution, id=self.pk)
self.title += " {}".format(tag.name) self.title += " {}".format(tag.name)
self.breadcrumb += " {}".format(tag.name) self.breadcrumb += " {}".format(tag.name)
lots = Lot.objects.filter(owner=self.request.user.institution).filter(type=tag) show_closed = self.request.GET.get('show_closed', 'false') == 'true'
lots = Lot.objects.filter(owner=self.request.user.institution).filter(type=tag, closed=show_closed)
context.update({ context.update({
'lots': lots, 'lots': lots,
'title': self.title, 'title': self.title,
'breadcrumb': self.breadcrumb 'breadcrumb': self.breadcrumb,
'show_closed': show_closed
}) })
return context return context

View File

@ -10,4 +10,4 @@ pandas==2.2.2
xlrd==2.0.1 xlrd==2.0.1
odfpy==1.4.1 odfpy==1.4.1
pytz==2024.2 pytz==2024.2
json-repair==0.30.0

View File

@ -17,15 +17,17 @@ class Command(BaseCommand):
parser.add_argument('email', type=str, help='email') parser.add_argument('email', type=str, help='email')
parser.add_argument('password', type=str, help='password') parser.add_argument('password', type=str, help='password')
parser.add_argument('is_admin', nargs='?', default=False, type=str, help='is admin') parser.add_argument('is_admin', nargs='?', default=False, type=str, help='is admin')
parser.add_argument('predefined_token', nargs='?', default='', type=str, help='predefined token')
def handle(self, *args, **kwargs): def handle(self, *args, **kwargs):
email = kwargs['email'] email = kwargs['email']
password = kwargs['password'] password = kwargs['password']
is_admin = kwargs['is_admin'] is_admin = kwargs['is_admin']
predefined_token = kwargs['predefined_token']
institution = Institution.objects.get(name=kwargs['institution']) institution = Institution.objects.get(name=kwargs['institution'])
self.create_user(institution, email, password, is_admin) self.create_user(institution, email, password, is_admin, predefined_token)
def create_user(self, institution, email, password, is_admin): def create_user(self, institution, email, password, is_admin, predefined_token):
self.u = User.objects.create( self.u = User.objects.create(
institution=institution, institution=institution,
email=email, email=email,
@ -34,6 +36,10 @@ class Command(BaseCommand):
) )
self.u.set_password(password) self.u.set_password(password)
self.u.save() self.u.save()
if predefined_token:
token = predefined_token
else:
token = uuid4() token = uuid4()
Token.objects.create(token=token, owner=self.u) Token.objects.create(token=token, owner=self.u)
print(f"TOKEN: {token}") print(f"TOKEN: {token}")

43
utils/save_snapshots.py Normal file
View File

@ -0,0 +1,43 @@
import os
import json
import shutil
from datetime import datetime
from django.conf import settings
def move_json(path_name, user, place="snapshots"):
if place != "snapshots":
place = "placeholders"
tmp_snapshots = settings.EVIDENCES_DIR
path_dir = os.path.join(tmp_snapshots, user, place)
if os.path.isfile(path_name):
shutil.copy(path_name, path_dir)
os.remove(path_name)
def save_in_disk(data, user, place="snapshots"):
uuid = data.get('uuid', '')
now = datetime.now()
year = now.year
month = now.month
day = now.day
hour = now.hour
minutes = now.minute
tmp_snapshots = settings.EVIDENCES_DIR
if place != "snapshots":
place = "placeholders"
name_file = f"{year}-{month}-{day}-{hour}-{minutes}_{uuid}.json"
path_dir = os.path.join(tmp_snapshots, user, place, "errors")
path_name = os.path.join(path_dir, name_file)
if not os.path.isdir(path_dir):
os.system(f'mkdir -p {path_dir}')
with open(path_name, 'w') as snapshot_file:
snapshot_file.write(json.dumps(data))
return path_name