diff --git a/ereuse_devicehub/dummy/dummy.py b/ereuse_devicehub/dummy/dummy.py index ec276de4..f3533d48 100644 --- a/ereuse_devicehub/dummy/dummy.py +++ b/ereuse_devicehub/dummy/dummy.py @@ -230,7 +230,7 @@ class Dummy: user1.get(res=Device, item=sample_pc_devicehub_id) # Test anonymous = self.app.test_client() html, _ = anonymous.get(res=Device, item=sample_pc_devicehub_id, accept=ANY) - assert 'intel core2 duo cpu' in html + assert 'hewlett-packard' in html # For netbook: to preapre -> torepair -> to dispose -> disposed print('⭐ Done.') diff --git a/ereuse_devicehub/forms.py b/ereuse_devicehub/forms.py index 4b237a8f..a9d8ac5e 100644 --- a/ereuse_devicehub/forms.py +++ b/ereuse_devicehub/forms.py @@ -1,11 +1,19 @@ +from boltons.urlutils import URL from flask import current_app as app from flask import g, session from flask_wtf import FlaskForm from werkzeug.security import generate_password_hash -from wtforms import BooleanField, EmailField, PasswordField, validators +from wtforms import ( + BooleanField, + EmailField, + PasswordField, + StringField, + URLField, + validators, +) from ereuse_devicehub.db import db -from ereuse_devicehub.resources.user.models import User +from ereuse_devicehub.resources.user.models import SanitizationEntity, User class LoginForm(FlaskForm): @@ -107,7 +115,7 @@ class PasswordForm(FlaskForm): g.user.reset_dlt_keys(self.newpassword.data, keys_dlt) token_dlt = ( - user.get_dlt_keys(self.password.data).get('data', {}).get('api_token') + g.user.get_dlt_keys(self.password.data).get('data', {}).get('api_token') ) session['token_dlt'] = token_dlt @@ -117,3 +125,48 @@ class PasswordForm(FlaskForm): if commit: db.session.commit() return + + +class SanitizationEntityForm(FlaskForm): + + logo = URLField( + 'Logo', + [validators.Optional(), validators.URL()], + render_kw={'class': "form-control"}, + ) + company_name = StringField('Company Name', render_kw={'class': "form-control"}) + location = StringField('Location', render_kw={'class': "form-control"}) + responsable_person = StringField( + 'Responsable person', render_kw={'class': "form-control"} + ) + supervisor_person = StringField( + 'Supervisor person', render_kw={'class': "form-control"} + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if isinstance(self.logo.data, URL): + self.logo.data = self.logo.data.to_text() + + def validate(self, extra_validators=None): + is_valid = super().validate(extra_validators) + + if not is_valid: + return False + + return True + + def save(self, commit=True): + sanitation_data = SanitizationEntity( + logo=URL(self.logo.data), + company_name=self.company_name.data, + location=self.location.data, + responsable_person=self.responsable_person.data, + supervisor_person=self.supervisor_person.data, + user=g.user, + ) + db.session.add(sanitation_data) + + if commit: + db.session.commit() + return diff --git a/ereuse_devicehub/inventory/forms.py b/ereuse_devicehub/inventory/forms.py index d5387f32..543ef0ea 100644 --- a/ereuse_devicehub/inventory/forms.py +++ b/ereuse_devicehub/inventory/forms.py @@ -30,7 +30,12 @@ from wtforms import ( from wtforms.fields import FormField from ereuse_devicehub.db import db -from ereuse_devicehub.inventory.models import DeliveryNote, ReceiverNote, Transfer +from ereuse_devicehub.inventory.models import ( + DeliveryNote, + ReceiverNote, + Transfer, + TransferCustomerDetails, +) from ereuse_devicehub.parser.models import PlaceholdersLog, SnapshotsLog from ereuse_devicehub.parser.parser import ParseSnapshotLsHw from ereuse_devicehub.parser.schemas import Snapshot_lite @@ -44,6 +49,7 @@ from ereuse_devicehub.resources.action.views.snapshot import ( from ereuse_devicehub.resources.device.models import ( SAI, Cellphone, + Computer, ComputerMonitor, Desktop, Device, @@ -297,7 +303,7 @@ class UploadSnapshotForm(SnapshotMixin, FlaskForm): return is_lite - def save(self, commit=True): + def save(self, commit=True, user_trusts=True): if any([x == 'Error' for x in self.result.values()]): return schema = SnapshotSchema() @@ -333,6 +339,8 @@ class UploadSnapshotForm(SnapshotMixin, FlaskForm): self.result[filename] = 'Error' continue + if isinstance(response.device, Computer): + response.device.user_trusts = user_trusts db.session.add(response) devices.append(response.device.binding.device) @@ -1516,6 +1524,54 @@ class NotesForm(FlaskForm): return self._obj +class CustomerDetailsForm(FlaskForm): + company_name = StringField( + 'Company name', + [validators.Optional()], + render_kw={'class': "form-control"}, + description="Name of the company", + ) + location = StringField( + 'Location', + [validators.Optional()], + render_kw={'class': "form-control"}, + description="""Location where is the company""", + ) + logo = URLField( + 'Logo', + [validators.Optional()], + render_kw={'class': "form-control"}, + description="Url where is the logo", + ) + + def __init__(self, *args, **kwargs): + lot_id = kwargs.pop('lot_id', None) + self._tmp_lot = Lot.query.filter(Lot.id == lot_id).one() + self._obj = self._tmp_lot.transfer.customer_details + if self._obj: + kwargs['obj'] = self._obj + if not self._obj: + self._obj = TransferCustomerDetails(transfer_id=self._tmp_lot.transfer.id) + + super().__init__(*args, **kwargs) + if isinstance(self.logo.data, URL): + self.logo.data = URL(self.logo.data).to_text() + + def validate(self, extra_validators=None): + is_valid = super().validate(extra_validators) + return is_valid + + def save(self, commit=True): + self.populate_obj(self._obj) + self._obj.logo = URL(self._obj.logo) + db.session.add(self._obj) + + if commit: + db.session.commit() + + return self._obj + + class UploadPlaceholderForm(FlaskForm): type = StringField('Type', [validators.DataRequired()]) placeholder_file = FileField( diff --git a/ereuse_devicehub/inventory/models.py b/ereuse_devicehub/inventory/models.py index 45d25157..f8b4f977 100644 --- a/ereuse_devicehub/inventory/models.py +++ b/ereuse_devicehub/inventory/models.py @@ -5,7 +5,7 @@ from flask import g from sqlalchemy import Column, Integer from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import backref, relationship -from teal.db import CASCADE_OWN +from teal.db import CASCADE_OWN, URL from ereuse_devicehub.db import db from ereuse_devicehub.resources.models import Thing @@ -90,3 +90,23 @@ class ReceiverNote(Thing): backref=backref('receiver_note', lazy=True, uselist=False, cascade=CASCADE_OWN), primaryjoin='ReceiverNote.transfer_id == Transfer.id', ) + + +class TransferCustomerDetails(Thing): + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) + company_name = Column(CIText(), nullable=True) + location = Column(CIText(), nullable=True) + logo = Column(URL(), nullable=True) + + transfer_id = db.Column( + UUID(as_uuid=True), + db.ForeignKey('transfer.id'), + nullable=False, + ) + transfer = relationship( + 'Transfer', + backref=backref( + 'customer_details', lazy=True, uselist=False, cascade=CASCADE_OWN + ), + primaryjoin='TransferCustomerDetails.transfer_id == Transfer.id', + ) diff --git a/ereuse_devicehub/inventory/views.py b/ereuse_devicehub/inventory/views.py index 15988ddb..835f95e6 100644 --- a/ereuse_devicehub/inventory/views.py +++ b/ereuse_devicehub/inventory/views.py @@ -1,7 +1,9 @@ import copy import csv +import datetime import logging import os +import uuid from io import StringIO from pathlib import Path @@ -20,6 +22,7 @@ from ereuse_devicehub.inventory.forms import ( AdvancedSearchForm, AllocateForm, BindingForm, + CustomerDetailsForm, DataWipeForm, EditTransferForm, FilterForm, @@ -79,6 +82,7 @@ class DeviceListMixin(GenericMixin): form_transfer = '' form_delivery = '' form_receiver = '' + form_customer_details = '' if lot_id: lot = lots.filter(Lot.id == lot_id).one() @@ -86,6 +90,7 @@ class DeviceListMixin(GenericMixin): form_transfer = EditTransferForm(lot_id=lot.id) form_delivery = NotesForm(lot_id=lot.id, type='Delivery') form_receiver = NotesForm(lot_id=lot.id, type='Receiver') + form_customer_details = CustomerDetailsForm(lot_id=lot.id) form_new_action = NewActionForm(lot=lot_id) self.context.update( @@ -97,6 +102,7 @@ class DeviceListMixin(GenericMixin): 'form_transfer': form_transfer, 'form_delivery': form_delivery, 'form_receiver': form_receiver, + 'form_customer_details': form_customer_details, 'form_filter': form_filter, 'form_print_labels': PrintLabelsForm(), 'lot': lot, @@ -1039,7 +1045,7 @@ class ExportsView(View): return self.response_csv(data, "Erasures.csv") - def build_erasure_certificate(self): + def get_datastorages(self): erasures = [] for device in self.find_devices(): if device.placeholder and device.placeholder.binding: @@ -1050,11 +1056,66 @@ class ExportsView(View): elif isinstance(device, DataStorage): if device.privacy: erasures.append(device.privacy) + return erasures + + def get_costum_details(self): + my_data = None + customer_details = None + if hasattr(g.user, 'sanitization_entity'): + if g.user.sanitization_entity: + my_data = list(g.user.sanitization_entity)[0] + + try: + if len(request.referrer.split('/lot/')) > 1: + lot_id = request.referrer.split('/lot/')[-1].split('/')[0] + lot = Lot.query.filter_by(owner=g.user).filter_by(id=lot_id).first() + customer_details = lot.transfer.customer_details + except Exception: + pass + return my_data, customer_details + + def get_server_erasure_hosts(self, erasures): + erasures_host = [] + erasures_on_server = [] + for erase in erasures: + try: + if erase.parent.binding.kangaroo: + erasures_host.append(erase.parent) + erasures_on_server.append(erase) + except Exception: + pass + return erasures_host, erasures_on_server + + def build_erasure_certificate(self): + erasures = self.get_datastorages() + software = 'USODY DRIVE ERASURE' + if erasures and erasures[0].snapshot: + software += ' {}'.format( + erasures[0].snapshot.version, + ) + + my_data, customer_details = self.get_costum_details() + + a, b = self.get_server_erasure_hosts(erasures) + erasures_host, erasures_on_server = a, b + + result = 'Success' + if "Failed" in [e.severity.get_public_name() for e in erasures]: + result = 'Failed' params = { 'title': 'Erasure Certificate', 'erasures': tuple(erasures), 'url_pdf': '', + 'date_report': '{:%c}'.format(datetime.datetime.now()), + 'uuid_report': '{}'.format(uuid.uuid4()), + 'software': software, + 'my_data': my_data, + 'n_computers': len(set([x.parent for x in erasures])), + 'result': result, + 'customer_details': customer_details, + 'erasure_hosts': erasures_host, + 'erasures_normal': list(set(erasures) - set(erasures_on_server)), } return flask.render_template('inventory/erasure.html', **params) @@ -1257,6 +1318,28 @@ class SnapshotDetailView(GenericMixin): ) +class CustomerDetailsView(GenericMixin): + methods = ['POST'] + form_class = CustomerDetailsForm + + def dispatch_request(self, lot_id): + self.get_context() + form = self.form_class(request.form, lot_id=lot_id) + next_url = url_for('inventory.lotdevicelist', lot_id=lot_id) + + if form.validate_on_submit(): + form.save() + messages.success('Customer details updated successfully!') + return flask.redirect(next_url) + + messages.error('Customer details updated error!') + for k, v in form.errors.items(): + value = ';'.join(v) + key = form[k].label.text + messages.error('Error {key}: {value}!'.format(key=key, value=value)) + return flask.redirect(next_url) + + class DeliveryNoteView(GenericMixin): methods = ['POST'] form_class = NotesForm @@ -1448,6 +1531,10 @@ devices.add_url_rule( '/lot//transfer/', view_func=EditTransferView.as_view('edit_transfer'), ) +devices.add_url_rule( + '/lot//customerdetails/', + view_func=CustomerDetailsView.as_view('customer_details'), +) devices.add_url_rule( '/lot//deliverynote/', view_func=DeliveryNoteView.as_view('delivery_note'), diff --git a/ereuse_devicehub/migrations/versions/4f33137586dd_sanitization.py b/ereuse_devicehub/migrations/versions/4f33137586dd_sanitization.py new file mode 100644 index 00000000..c546483d --- /dev/null +++ b/ereuse_devicehub/migrations/versions/4f33137586dd_sanitization.py @@ -0,0 +1,85 @@ +"""sanitization + +Revision ID: 4f33137586dd +Revises: 8334535d56fa +Create Date: 2023-02-13 18:01:00.092527 + +""" +import citext +import sqlalchemy as sa +import teal +from alembic import context, op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '4f33137586dd' +down_revision = '8334535d56fa' +branch_labels = None +depends_on = None + + +def get_inv(): + INV = context.get_x_argument(as_dictionary=True).get('inventory') + if not INV: + raise ValueError("Inventory value is not specified") + return INV + + +def upgrade(): + op.create_table( + 'sanitization_entity', + sa.Column('id', sa.BigInteger(), nullable=False), + sa.Column( + 'updated', + sa.TIMESTAMP(timezone=True), + server_default=sa.text('CURRENT_TIMESTAMP'), + nullable=False, + ), + sa.Column( + 'created', + sa.TIMESTAMP(timezone=True), + server_default=sa.text('CURRENT_TIMESTAMP'), + nullable=False, + ), + sa.Column('company_name', sa.String(), nullable=True), + sa.Column('logo', teal.db.URL(), nullable=True), + sa.Column('responsable_person', sa.String(), nullable=True), + sa.Column('supervisor_person', sa.String(), nullable=True), + sa.Column('location', sa.String(), nullable=True), + sa.Column('user_id', postgresql.UUID(as_uuid=True), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.ForeignKeyConstraint( + ['user_id'], + ['common.user.id'], + ), + schema=f'{get_inv()}', + ) + + op.create_table( + 'transfer_customer_details', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column( + 'updated', + sa.TIMESTAMP(timezone=True), + server_default=sa.text('CURRENT_TIMESTAMP'), + nullable=False, + ), + sa.Column( + 'created', + sa.TIMESTAMP(timezone=True), + server_default=sa.text('CURRENT_TIMESTAMP'), + nullable=False, + ), + sa.Column('company_name', citext.CIText(), nullable=True), + sa.Column('logo', teal.db.URL(), nullable=True), + sa.Column('location', citext.CIText(), nullable=True), + sa.Column('transfer_id', postgresql.UUID(as_uuid=True), nullable=False), + sa.ForeignKeyConstraint(['transfer_id'], [f'{get_inv()}.transfer.id']), + sa.PrimaryKeyConstraint('id'), + schema=f'{get_inv()}', + ) + + +def downgrade(): + op.drop_table('sanitization_entity', schema=f'{get_inv()}') + op.drop_table('transfer_customer_details', schema=f'{get_inv()}') diff --git a/ereuse_devicehub/resources/action/models.py b/ereuse_devicehub/resources/action/models.py index 37650c6a..df23cf44 100644 --- a/ereuse_devicehub/resources/action/models.py +++ b/ereuse_devicehub/resources/action/models.py @@ -527,6 +527,9 @@ class EraseBasic(JoinedWithOneDeviceMixin, ActionWithOneDevice): proof = Proof(**d) db.session.add(proof) + def get_public_name(self): + return "Basic" + def __str__(self) -> str: return '{} on {}.'.format(self.severity, self.date_str) @@ -556,12 +559,32 @@ class EraseSectors(EraseBasic): method = 'Badblocks' + def get_public_name(self): + steps_random = 0 + steps_zeros = 0 + for s in self.steps: + if s.type == 'StepRandom': + steps_random += 1 + if s.type == 'StepZero': + steps_zeros += 1 + if steps_zeros < 1: + return "Basic" + if 0 < steps_random < 3: + return "Baseline" + if steps_random > 2: + return "Enhanced" + + return "Basic" + class ErasePhysical(EraseBasic): """The act of physically destroying a data storage unit.""" method = Column(DBEnum(PhysicalErasureMethod)) + def get_public_name(self): + return "Physical" + class Step(db.Model): erasure_id = Column( diff --git a/ereuse_devicehub/resources/device/models.py b/ereuse_devicehub/resources/device/models.py index 450fd6b8..3e3544ab 100644 --- a/ereuse_devicehub/resources/device/models.py +++ b/ereuse_devicehub/resources/device/models.py @@ -758,6 +758,24 @@ class Device(Thing): return "" + def get_exist_untrusted_device(self): + if isinstance(self, Computer): + if not self.system_uuid: + return True + + return ( + Computer.query.filter_by( + hid=self.hid, + user_trusts=False, + owner_id=g.user.id, + active=True, + placeholder=None, + ).first() + or False + ) + + return False + def get_from_db(self): if 'property_hid' in app.blueprints.keys(): try: @@ -939,7 +957,7 @@ class Device(Thing): if not snapshot1: return - self.create_new_device(snapshots.values()) + self.create_new_device(snapshots.values(), user_trusts=self.user_trusts) self.remove_snapshot(snapshots.keys()) return @@ -956,7 +974,7 @@ class Device(Thing): snapshot = file_snapshot.read() return json.loads(snapshot) - def create_new_device(self, snapshots): + def create_new_device(self, snapshots, user_trusts=True): from ereuse_devicehub.inventory.forms import UploadSnapshotForm new_snapshots = [] @@ -969,7 +987,7 @@ class Device(Thing): form.result = {} form.snapshots = new_snapshots form.create_new_devices = True - form.save(commit=False) + form.save(commit=False, user_trusts=user_trusts) def remove_snapshot(self, snapshots): from ereuse_devicehub.parser.models import SnapshotsLog diff --git a/ereuse_devicehub/resources/device/sync.py b/ereuse_devicehub/resources/device/sync.py index 8dfba8b2..32c4a2f2 100644 --- a/ereuse_devicehub/resources/device/sync.py +++ b/ereuse_devicehub/resources/device/sync.py @@ -16,19 +16,20 @@ from ereuse_devicehub.resources.action.models import Remove from ereuse_devicehub.resources.device.models import ( Component, Computer, + DataStorage, Device, Placeholder, ) from ereuse_devicehub.resources.tag.model import Tag -DEVICES_ALLOW_DUPLICITY = [ - 'RamModule', - 'Display', - 'SoundCard', - 'Battery', - 'Camera', - 'GraphicCard', -] +# DEVICES_ALLOW_DUPLICITY = [ +# 'RamModule', +# 'Display', +# 'SoundCard', +# 'Battery', +# 'Camera', +# 'GraphicCard', +# ] class Sync: @@ -119,7 +120,7 @@ class Sync: """ assert inspect(component).transient, 'Component should not be synced from DB' # if not is a DataStorage, then need build a new one - if component.t in DEVICES_ALLOW_DUPLICITY: + if not isinstance(component, DataStorage): db.session.add(component) is_new = True return component, is_new @@ -171,6 +172,8 @@ class Sync: if not db_device or create_new_device: device.tags.clear() # We don't want to add the transient dummy tags + if create_new_device or device.get_exist_untrusted_device(): + device.user_trusts = False db.session.add(device) db_device = device try: diff --git a/ereuse_devicehub/resources/device/templates/devices/layout.html b/ereuse_devicehub/resources/device/templates/devices/layout.html index f513d0b9..f71806c0 100644 --- a/ereuse_devicehub/resources/device/templates/devices/layout.html +++ b/ereuse_devicehub/resources/device/templates/devices/layout.html @@ -1,400 +1,220 @@ -{% import 'devices/macros.html' as macros %} + - - - - - Devicehub | {{ device.__format__('t') }} - + + + + + + + + + + + + + + + + + + + + + + - -
-
- -
-
-
-
-
-
    - {% for key, value in device.public_properties.items() %} -
  • {{ key }}: {{ value or '' }}
  • - {% endfor %} -
- {% if isinstance(device, d.Computer) %} -
- - - - - - - - - {% if device.processor_model %} - - - - - {% endif %} - {% if device.ram_size %} - - - - - {% endif %} - {% if device.data_storage_size %} - - - - - {% endif %} - {% if device.graphic_card_model %} - - - - - {% endif %} - {% if device.network_speeds %} - - - - - {% endif %} - -
Range
- CPU – {{ device.processor_model }} - - Processor Rate = {% if device.rate %} - {{ device.rate.processor_range }} - ({{ device.rate.processor }}) - {% endif %} -
- RAM – {{ device.ram_size // 1000 }} GB - {{ macros.component_type(device.components, 'RamModule') }} - - RAM Rate = {% if device.rate %} - {{ device.rate.ram_range }} - ({{ device.rate.ram }}) - {% endif %} -
- Data Storage – {{ device.data_storage_size // 1000 }} GB - {{ macros.component_type(device.components, 'SolidStateDrive') }} - {{ macros.component_type(device.components, 'HardDrive') }} - - Data Storage Rate = {% if device.rate %} - {{ device.rate.data_storage_range }} - ({{ device.rate.data_storage }}) - {% endif %} -
- Graphics – {{ device.graphic_card_model }} - {{ macros.component_type(device.components, 'GraphicCard') }} -
- Network – - {% if device.network_speeds[0] %} - Ethernet - {% if device.network_speeds[0] != None %} - max. {{ device.network_speeds[0] }} Mbps - {% endif %} - {% endif %} - {% if device.network_speeds[0] and device.network_speeds[1] %} - + - {% endif %} - {% if device.network_speeds[1] %} - WiFi - {% if device.network_speeds[1] != None %} - max. {{ device.network_speeds[1] }} Mbps - {% endif %} - {% endif %} - {{ macros.component_type(device.components, 'NetworkAdapter') }} -
-
-

Actual Status

-
    -
  1. - - Lifecycle Status - - — - {% if device.status %} - {{ device.status.type }} - {% endif %} -
  2. -
  3. - - Allocate Status - - — - {% if device.allocated_status %} - {{ device.allocated_status.type }} - {% endif %} -
  4. -
  5. - - Physical Status - - — - {% if device.physical_status %} - {{ device.physical_status.type }} - {% endif %} -
  6. -
+
-

Public traceability log of the device

-
- Latest one. -
-
    - {% for action in device.public_actions %} -
  1. - - {{ device.is_status(action) }} - {% if not device.is_status(action) %} - {{ action.type }} - {% endif %} - - — - {% if device.is_status(action) %} - {{ action }} {{ action.type }} - {% else %} - {{ action }} - {% endif %} -
    -
    - - {{ action._date_str }} - -
    - {% if action.certificate %} - See the certificate - {% endif %} -
  2. - {% endfor %} -
-
- Oldest one. -
- {% endif %} -
-
- {% if abstract %} -
- -
-
-
-
-
-
    - {% for key, value in abstract.public_properties.items() %} -
  • {{ key }}: {{ value or '' }}
  • - {% endfor %} -
- {% if isinstance(abstract, d.Computer) %} -
- - - - - - - - - {% if abstract.processor_model %} - - - - - {% endif %} - {% if abstract.ram_size %} - - - - - {% endif %} - {% if abstract.data_storage_size %} - - - - - {% endif %} - {% if abstract.graphic_card_model %} - - - - - {% endif %} - {% if abstract.network_speeds %} - - - - - {% endif %} - -
Range
- CPU – {{ abstract.processor_model }} - - Processor Rate = {% if abstract.rate %} - {{ abstract.rate.processor_range }} - ({{ abstract.rate.processor }}) - {% endif %} -
- RAM – {{ abstract.ram_size // 1000 }} GB - {{ macros.component_type(abstract.components, 'RamModule') }} - - RAM Rate = {% if abstract.rate %} - {{ abstract.rate.ram_range }} - ({{ abstract.rate.ram }}) - {% endif %} -
- Data Storage – {{ abstract.data_storage_size // 1000 }} GB - {{ macros.component_type(abstract.components, 'SolidStateDrive') }} - {{ macros.component_type(abstract.components, 'HardDrive') }} - - Data Storage Rate = {% if abstract.rate %} - {{ abstract.rate.data_storage_range }} - ({{ abstract.rate.data_storage }}) - {% endif %} -
- Graphics – {{ abstract.graphic_card_model }} - {{ macros.component_type(abstract.components, 'GraphicCard') }} -
- Network – - {% if abstract.network_speeds[0] %} - Ethernet - {% if abstract.network_speeds[0] != None %} - max. {{ abstract.network_speeds[0] }} Mbps - {% endif %} - {% endif %} - {% if abstract.network_speeds[0] and abstract.network_speeds[1] %} - + - {% endif %} - {% if abstract.network_speeds[1] %} - WiFi - {% if abstract.network_speeds[1] != None %} - max. {{ abstract.network_speeds[1] }} Mbps - {% endif %} - {% endif %} - {{ macros.component_type(abstract.components, 'NetworkAdapter') }} -
-
-

Actual Status

-
    -
  1. - - Lifecycle Status - - — - {% if abstract.status %} - {{ abstract.status.type }} - {% endif %} -
  2. -
  3. - - Allocate Status - - — - {% if abstract.allocated_status %} - {{ abstract.allocated_status.type }} - {% endif %} -
  4. -
  5. - - Physical Status - - — - {% if abstract.physical_status %} - {{ abstract.physical_status.type }} - {% endif %} -
  6. -
+
+
-

Public traceability log of the abstract

-
- Latest one. +
+
+ +
+
+ +
+
+
Basic
+
+
+ Usody Identifier (DHID) +
+
+ {{ device_real.dhid }} +
+
+
+
+ Inventory Identifier (PHID) +
+
+ {{ device_real.phid() }} +
+
+
+
+ Type +
+
+ {{ device_real.type or '- not detected -' }} +
+
+
+
+ Manufacturer +
+
+ {{ device_real.manufacturer or '- not detected -' }} +
+
+
+
+ Model +
+
+ {{ device_real.model or '- not detected -' }} +
+
+
+
+ Part Number +
+
+ {{ device_real.part_number or '- not detected -' }} +
+
+
+
+ Serial Number +
+
+ - anonymized - +
+
-
    - {% for action in abstract.public_actions %} -
  1. - - {{ abstract.is_status(action) }} - {% if not abstract.is_status(action) %} - {{ action.type }} - {% endif %} - - — - {% if abstract.is_status(action) %} - {{ action }} {{ action.type }} - {% else %} - {{ action }} - {% endif %} -
    -
    - - {{ action._date_str }} - -
    - {% if action.certificate %} - See the certificate - {% endif %} -
  2. - {% endfor %} -
-
- Oldest one. +
- {% endif %} +
+
Status
+
+
+
Physical
+
{{ device_real.physical_status and device.physical_status.type or '- not status -' }}
+
+
+
+
+
Lifecycle
+
{{ device_real.status and device_real.status.type or '- not status -' }}
+
+
+
+
+
Allocation
+
+ {% if device_real.allocated %} + Allocated + {% else %} + Not allocated + {% endif %} +
+
+
+
+
+
+
+
Components
+
+ {% if placeholder.binding %} +
+ {% for component in placeholder.binding.components|sort(attribute='type') %} +
+
+
{{ component.type }}
+ {{ component.created.strftime('%H:%M %d-%m-%Y') }} +
+

+ {{ component.manufacturer or '- not detected -' }}
+ {{ component.model or '- not detected -' }}
+

+ + {% if component.type in ['RamModule', 'HardDrive', 'SolidStateDrive'] %} + {{ component.size }}MB + {% endif %} + +
+ {% endfor %} +
+ {% else %} +
+
+ - not detected - +
+
+ {% endif %} +
+
+
+
Repair history
+
+
+ {% for action in placeholder.actions %} +
+ {{ action.type }} {{ action.severity }} + {{ action.created.strftime('%H:%M %d-%m-%Y') }} +
+ {% endfor %} +
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+ +
+ Help | + Privacy | + Terms +
+
+ DeviceHub +
+
- {% endif %} +
-
-
-
- Page generated by:
- -
-
-
+ diff --git a/ereuse_devicehub/resources/device/views.py b/ereuse_devicehub/resources/device/views.py index 3aa9d9ff..59beffd9 100644 --- a/ereuse_devicehub/resources/device/views.py +++ b/ereuse_devicehub/resources/device/views.py @@ -148,8 +148,17 @@ class DeviceView(View): if device.is_abstract() == 'Twin': abstract = device.placeholder.binding + placeholder = device.binding or device.placeholder + device_abstract = placeholder and placeholder.binding or device + device_real = placeholder and placeholder.device or device return render_template( - 'devices/layout.html', device=device, states=states, abstract=abstract + 'devices/layout.html', + placeholder=placeholder, + device=device, + device_abstract=device_abstract, + device_real=device_real, + states=states, + abstract=abstract, ) @auth.Auth.requires_auth diff --git a/ereuse_devicehub/resources/enums.py b/ereuse_devicehub/resources/enums.py index a2254b29..b6419cd0 100644 --- a/ereuse_devicehub/resources/enums.py +++ b/ereuse_devicehub/resources/enums.py @@ -334,6 +334,12 @@ class Severity(IntEnum): def __format__(self, format_spec): return str(self) + def get_public_name(self): + if self.value == 3: + return "Failed" + + return "Success" + class PhysicalErasureMethod(Enum): """Methods of physically erasing the data-storage, usually diff --git a/ereuse_devicehub/resources/user/models.py b/ereuse_devicehub/resources/user/models.py index 8b7975b6..ff8d0051 100644 --- a/ereuse_devicehub/resources/user/models.py +++ b/ereuse_devicehub/resources/user/models.py @@ -8,7 +8,7 @@ from flask_login import UserMixin from sqlalchemy import BigInteger, Boolean, Column, Sequence from sqlalchemy.dialects.postgresql import UUID from sqlalchemy_utils import EmailType, PasswordType -from teal.db import IntEnum +from teal.db import URL, IntEnum from ereuse_devicehub.db import db from ereuse_devicehub.resources.enums import SessionType @@ -178,3 +178,22 @@ class Session(Thing): def __str__(self) -> str: return '{0.token}'.format(self) + + +class SanitizationEntity(Thing): + id = Column(BigInteger, primary_key=True) + company_name = Column(db.String, nullable=True) + location = Column(db.String, nullable=True) + logo = Column(db.String, nullable=True) + logo = Column(URL(), nullable=True) + responsable_person = Column(db.String, nullable=True) + supervisor_person = Column(db.String, nullable=True) + user_id = db.Column(db.UUID(as_uuid=True), db.ForeignKey(User.id)) + user = db.relationship( + User, + backref=db.backref('sanitization_entity', lazy=True, collection_class=set), + collection_class=set, + ) + + def __str__(self) -> str: + return '{0.company_name}'.format(self) diff --git a/ereuse_devicehub/static/css/simple-datatables.css b/ereuse_devicehub/static/css/simple-datatables.css deleted file mode 100644 index ad225bc3..00000000 --- a/ereuse_devicehub/static/css/simple-datatables.css +++ /dev/null @@ -1,182 +0,0 @@ -.dataTable-wrapper.no-header .dataTable-container { - border-top: 1px solid #d9d9d9; -} - -.dataTable-wrapper.no-footer .dataTable-container { - border-bottom: 1px solid #d9d9d9; -} - -.dataTable-top, -.dataTable-bottom { - padding: 8px 10px; -} - -.dataTable-top > nav:first-child, -.dataTable-top > div:first-child, -.dataTable-bottom > nav:first-child, -.dataTable-bottom > div:first-child { - float: left; -} - -.dataTable-top > nav:last-child, -.dataTable-top > div:last-child, -.dataTable-bottom > nav:last-child, -.dataTable-bottom > div:last-child { - float: right; -} - -.dataTable-selector { - padding: 6px; -} - -.dataTable-input { - padding: 6px 12px; -} - -.dataTable-info { - margin: 7px 0; -} - -/* PAGER */ -.dataTable-pagination ul { - margin: 0; - padding-left: 0; -} - -.dataTable-pagination li { - list-style: none; - float: left; -} - -.dataTable-pagination a { - border: 1px solid transparent; - float: left; - margin-left: 2px; - padding: 6px 12px; - position: relative; - text-decoration: none; - color: #333; -} - -.dataTable-pagination a:hover { - background-color: #d9d9d9; -} - -.dataTable-pagination .active a, -.dataTable-pagination .active a:focus, -.dataTable-pagination .active a:hover { - background-color: #d9d9d9; - cursor: default; -} - -.dataTable-pagination .ellipsis a, -.dataTable-pagination .disabled a, -.dataTable-pagination .disabled a:focus, -.dataTable-pagination .disabled a:hover { - cursor: not-allowed; -} - -.dataTable-pagination .disabled a, -.dataTable-pagination .disabled a:focus, -.dataTable-pagination .disabled a:hover { - cursor: not-allowed; - opacity: 0.4; -} - -.dataTable-pagination .pager a { - font-weight: bold; -} - -/* TABLE */ -.dataTable-table { - max-width: 100%; - width: 100%; - border-spacing: 0; - border-collapse: separate; -} - -.dataTable-table > tbody > tr > td, -.dataTable-table > tbody > tr > th, -.dataTable-table > tfoot > tr > td, -.dataTable-table > tfoot > tr > th, -.dataTable-table > thead > tr > td, -.dataTable-table > thead > tr > th { - vertical-align: top; - padding: 8px 10px; -} - -.dataTable-table > thead > tr > th { - vertical-align: bottom; - text-align: left; - border-bottom: 1px solid #d9d9d9; -} - -.dataTable-table > tfoot > tr > th { - vertical-align: bottom; - text-align: left; - border-top: 1px solid #d9d9d9; -} - -.dataTable-table th { - vertical-align: bottom; - text-align: left; -} - -.dataTable-table th a { - text-decoration: none; - color: inherit; -} - -.dataTable-sorter { - display: inline-block; - height: 100%; - position: relative; - width: 100%; -} - -.dataTable-sorter::before, -.dataTable-sorter::after { - content: ""; - height: 0; - width: 0; - position: absolute; - right: 4px; - border-left: 4px solid transparent; - border-right: 4px solid transparent; - opacity: 0.2; -} - -.dataTable-sorter::before { - border-top: 4px solid #000; - bottom: 0px; -} - -.dataTable-sorter::after { - border-bottom: 4px solid #000; - border-top: 4px solid transparent; - top: 0px; -} - -.asc .dataTable-sorter::after, -.desc .dataTable-sorter::before { - opacity: 0.6; -} - -.dataTables-empty { - text-align: center; -} - -.dataTable-top::after, .dataTable-bottom::after { - clear: both; - content: " "; - display: table; -} - -table.dataTable-table:focus tr.dataTable-cursor > td:first-child { - border-left: 3px blue solid; -} - -table.dataTable-table:focus { - outline: solid 1px black; - outline-offset: -1px; -} \ No newline at end of file diff --git a/ereuse_devicehub/static/js/simple-datatables-5.0.3.js b/ereuse_devicehub/static/js/simple-datatables-5.0.3.js deleted file mode 100644 index 0a894d0b..00000000 --- a/ereuse_devicehub/static/js/simple-datatables-5.0.3.js +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Minified by jsDelivr using Terser v5.15.1. - * Original file: /npm/simple-datatables@5.0.3/dist/umd/simple-datatables.js - * - * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files - */ -!function(t){if(typeof exports==="object"&&typeof module!=="undefined")module.exports=t();else if(typeof define==="function"&&define.amd)define([],t);else{(typeof window!=="undefined"?window:typeof global!=="undefined"?global:typeof self!=="undefined"?self:this).simpleDatatables=t()}}((() =>function t(e,s,i){function a(o,r){if(!s[o]){if(!e[o]){const l=typeof require==="function"&&require;if(!r&&l)return l(o,!0);if(n)return n(o,!0);const h=new Error(`Cannot find module '${o}'`);throw h.code="MODULE_NOT_FOUND",h}const d=s[o]={exports:{}};e[o][0].call(d.exports,((t) =>a(e[o][1][t]||t)),d,d.exports,t,e,s,i)}return s[o].exports}for(var n=typeof require==="function"&&require,o=0;o=e?t:`${Array(e+1-i.length).join(s)}${t}`}; const b={s:m,z(t){const e=-t.utcOffset(); const s=Math.abs(e); const i=Math.floor(s/60); const a=s%60;return`${(e<=0?"+":"-")+m(i,2,"0")}:${m(a,2,"0")}`},m:function t(e,s){if(e.date()1)return t(o[0])}else{const r=e.name;w[r]=e,a=r}return!i&&a&&(v=a),a||!i&&v}; const x=function(t,e){if(y(t))return t.clone();const s=typeof e==="object"?e:{};return s.date=t,s.args=arguments,new M(s)}; const T=b;T.l=C,T.i=y,T.w=function(t,e){return x(t,{locale:e.$L,utc:e.$u,x:e.$x,$offset:e.$offset})};var M=function(){function g(t){this.$L=C(t.locale,null,!0),this.parse(t)}const m=g.prototype;return m.parse=function(t){this.$d=function(t){const e=t.date; const s=t.utc;if(e===null)return new Date(NaN);if(T.u(e))return new Date;if(e instanceof Date)return new Date(e);if(typeof e==="string"&&!/Z$/i.test(e)){const i=e.match(p);if(i){const a=i[2]-1||0; const n=(i[7]||"0").substring(0,3);return s?new Date(Date.UTC(i[1],a,i[3]||1,i[4]||0,i[5]||0,i[6]||0,n)):new Date(i[1],a,i[3]||1,i[4]||0,i[5]||0,i[6]||0,n)}}return new Date(e)}(t),this.$x=t.x||{},this.init()},m.init=function(){const t=this.$d;this.$y=t.getFullYear(),this.$M=t.getMonth(),this.$D=t.getDate(),this.$W=t.getDay(),this.$H=t.getHours(),this.$m=t.getMinutes(),this.$s=t.getSeconds(),this.$ms=t.getMilliseconds()},m.$utils=function(){return T},m.isValid=function(){return!(this.$d.toString()===u)},m.isSame=function(t,e){const s=x(t);return this.startOf(e)<=s&&s<=this.endOf(e)},m.isAfter=function(t,e){return x(t)e||g[t]||a.replace(":","")))},m.utcOffset=function(){return 15*-Math.round(this.$d.getTimezoneOffset()/15)},m.diff=function(s,c,u){let p; const f=T.p(c); const g=x(s); const m=(g.utcOffset()-this.utcOffset())*t; const b=this-g; let v=T.m(this,g);return v=(p={},p[d]=v/12,p[l]=v,p[h]=v/3,p[r]=(b-m)/6048e5,p[o]=(b-m)/864e5,p[n]=b/e,p[a]=b/t,p[i]=b/1e3,p)[f]||b,u?v:T.a(v)},m.daysInMonth=function(){return this.endOf(l).$D},m.$locale=function(){return w[this.$L]},m.locale=function(t,e){if(!t)return this.$L;const s=this.clone(); const i=C(t,e,!0);return i&&(s.$L=i),s},m.clone=function(){return T.w(this.$d,this)},m.toDate=function(){return new Date(this.valueOf())},m.toJSON=function(){return this.isValid()?this.toISOString():null},m.toISOString=function(){return this.$d.toISOString()},m.toString=function(){return this.$d.toUTCString()},g}(); const E=M.prototype;return x.prototype=E,[["$ms",s],["$s",i],["$m",a],["$H",n],["$W",o],["$M",l],["$y",d],["$D",c]].forEach(((t) =>{E[t[1]]=function(e){return this.$g(e,t[0],t[1])}})),x.extend=function(t,e){return t.$i||(t(e,M,x),t.$i=!0),x},x.locale=C,x.isDayjs=y,x.unix=function(t){return x(1e3*t)},x.en=w[v],x.Ls=w,x.p={},x}(); const i={exports:{}}.exports=function(){const t={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"}; const e=/(\[[^[]*\])|([-_:/.,()\s]+)|(A|a|YYYY|YY?|MM?M?M?|Do|DD?|hh?|HH?|mm?|ss?|S{1,3}|z|ZZ?)/g; const s=/\d\d/; const i=/\d\d?/; const a=/\d*[^-_:/,()\s\d]+/; let n={}; let o=function(t){return(t=+t)+(t>68?1900:2e3)}; const r=function(t){return function(e){this[t]=+e}}; const l=[/[+-]\d\d:?(\d\d)?|Z/,function(t){(this.zone||(this.zone={})).offset=function(t){if(!t)return 0;if(t==="Z")return 0;const e=t.match(/([+-]|\d\d)/g); const s=60*e[1]+(+e[2]||0);return s===0?0:e[0]==="+"?-s:s}(t)}]; const h=function(t){const e=n[t];return e&&(e.indexOf?e:e.s.concat(e.f))}; const d=function(t,e){let s; const i=n.meridiem;if(i){for(let a=1;a<=24;a+=1)if(t.indexOf(i(a,0,e))>-1){s=a>12;break}}else s=t===(e?"pm":"PM");return s}; const c={A:[a,function(t){this.afternoon=d(t,!1)}],a:[a,function(t){this.afternoon=d(t,!0)}],S:[/\d/,function(t){this.milliseconds=100*+t}],SS:[s,function(t){this.milliseconds=10*+t}],SSS:[/\d{3}/,function(t){this.milliseconds=+t}],s:[i,r("seconds")],ss:[i,r("seconds")],m:[i,r("minutes")],mm:[i,r("minutes")],H:[i,r("hours")],h:[i,r("hours")],HH:[i,r("hours")],hh:[i,r("hours")],D:[i,r("day")],DD:[s,r("day")],Do:[a,function(t){const e=n.ordinal; const s=t.match(/\d+/);if(this.day=s[0],e)for(let i=1;i<=31;i+=1)e(i).replace(/\[|\]/g,"")===t&&(this.day=i)}],M:[i,r("month")],MM:[s,r("month")],MMM:[a,function(t){const e=h("months"); const s=(h("monthsShort")||e.map(((t) =>t.slice(0,3)))).indexOf(t)+1;if(s<1)throw new Error;this.month=s%12||s}],MMMM:[a,function(t){const e=h("months").indexOf(t)+1;if(e<1)throw new Error;this.month=e%12||e}],Y:[/[+-]?\d+/,r("year")],YY:[s,function(t){this.year=o(t)}],YYYY:[/\d{4}/,r("year")],Z:l,ZZ:l};function u(s){let i; let a;i=s,a=n&&n.formats;for(var o=(s=i.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g,((e,s,i) =>{const n=i&&i.toUpperCase();return s||a[i]||t[i]||a[n].replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g,((t,e,s) =>e||s.slice(1)))}))).match(e),r=o.length,l=0;l-1)return new Date((e==="X"?1e3:1)*t);const i=u(e)(t); const a=i.year; const n=i.month; const o=i.day; const r=i.hours; const l=i.minutes; const h=i.seconds; const d=i.milliseconds; const c=i.zone; const p=new Date; const f=o||(a||n?1:p.getDate()); const g=a||p.getFullYear(); let m=0;a&&!n||(m=n>0?n-1:p.getMonth());const b=r||0; const v=l||0; const w=h||0; const y=d||0;return c?new Date(Date.UTC(g,m,f,b,v,w,y+60*c.offset*1e3)):s?new Date(Date.UTC(g,m,f,b,v,w,y)):new Date(g,m,f,b,v,w,y)}catch(t){return new Date("")}}(e,r,i),this.init(),c&&!0!==c&&(this.$L=this.locale(c).$L),d&&e!=this.format(r)&&(this.$d=new Date("")),n={}}else if(r instanceof Array)for(let p=r.length,f=1;f<=p;f+=1){o[1]=r[f-1];const g=s.apply(this,o);if(g.isValid()){this.$d=g.$d,this.$L=g.$L,this.init();break}f===p&&(this.$d=new Date(""))}else a.call(this,t)}}}();e.extend(i),s.parseDate=(t,s)=>{let i=!1;if(s)switch(s){case"ISO_8601":i=t;break;case"RFC_2822":i=e(t.slice(5),"DD MMM YYYY HH:mm:ss ZZ").unix();break;case"MYSQL":i=e(t,"YYYY-MM-DD hh:mm:ss").unix();break;case"UNIX":i=e(t).unix();break;default:i=e(t,s,!0).valueOf()}return i}}).call(this)}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{}],2:[function(t,e,s){"use strict"; - -Object.defineProperty(s,"__esModule",{value:!0});const i=t=>Object.prototype.toString.call(t)==="[object Object]"; const a=t=>{let e=!1;try{e=JSON.parse(t)}catch(t){return!1}return!(e===null||!Array.isArray(e)&&!i(e))&&e}; const n=(t,e)=>{const s=document.createElement(t);if(e&&typeof e==="object")for(const t in e)t==="html"?s.innerHTML=e[t]:s.setAttribute(t,e[t]);return s}; const o=t=>{t instanceof NodeList?t.forEach((t=>o(t))):t.innerHTML=""}; const r=(t,e,s)=>n("li",{class:t,html:`${s}`}); const l=(t,e)=>{let s; let i;e===1?(s=0,i=t.length):e===-1&&(s=t.length-1,i=-1);for(let a=!0;a;){a=!1;for(let n=s;n!=i;n+=e)if(t[n+e]&&t[n].value>t[n+e].value){const s=t[n]; const i=t[n+e]; const o=s;t[n]=i,t[n+e]=o,a=!0}}return t};class h{constructor(t){this.dt=t,this.cursor=!1} - -build(t){const e=n("tr");let s=this.dt.headings;return s.length||(s=t.map((()=>""))),s.forEach(((s,i)=>{const a=n("td");t[i]&&t[i].length||(t[i]=""),a.innerHTML=t[i],a.data=t[i],e.appendChild(a)})),e} - -setCursor(t=!1){let e;Array.from(this.dt.dom.rows).forEach((t=>{e=t,t.classList.remove("dataTable-cursor")})),t&&(t.classList.add("dataTable-cursor"),this.cursor=t,this.dt.options.scrollY&&this.cursor.scrollIntoView({block:"nearest"}),this.dt.emit("datatable.cursormove",this.cursor,e))} - -render(t){return t} - -add(t){if(Array.isArray(t)){const e=this.dt;Array.isArray(t[0])?t.forEach((t=>{e.data.push(this.build(t))})):e.data.push(this.build(t)),e.data.length&&(e.hasRows=!0),this.update(),e.columns.rebuild()}} - -remove(t){const e=this.dt;Array.isArray(t)?(t.sort(((t,e)=>e-t)),t.forEach((t=>{e.data.splice(t,1)}))):t=="all"?e.data=[]:e.data.splice(t,1),e.data.length||(e.hasRows=!1),this.update(),e.columns.rebuild()} - -update(){this.dt.data.forEach(((t,e)=>{t.dataIndex=e}))} - -findRowIndex(t,e){return this.dt.data.findIndex((s=>s.children[t].innerText.toLowerCase().includes(String(e).toLowerCase())))} - -findRow(t,e){const s=this.findRowIndex(t,e);if(s<0)return{index:-1,row:null,cols:[]};const i=this.dt.data[s];return{index:s,row:i,cols:[...i.cells].map((t=>t.innerHTML))}} - -updateRow(t,e){const s=this.build(e);this.dt.data.splice(t,1,s),this.update(),this.dt.columns.rebuild()}}class d{constructor(t){this.dt=t} - -swap(t){if(t.length&&t.length===2){const e=[];this.dt.headings.forEach(((t,s)=>{e.push(s)}));const s=t[0]; const i=t[1]; const a=e[i];e[i]=e[s],e[s]=a,this.order(e)}} - -order(t){let e; let s; let i; let a; let n; let o; let r;const l=[[],[],[],[]]; const h=this.dt;t.forEach(((t,i)=>{n=h.headings[t],o=n.getAttribute("data-sortable")!=="false",e=n.cloneNode(!0),e.originalCellIndex=i,e.sortable=o,l[0].push(e),h.hiddenColumns.includes(t)||(s=n.cloneNode(!0),s.originalCellIndex=i,s.sortable=o,l[1].push(s))})),h.data.forEach(((e,s)=>{i=e.cloneNode(!1),a=e.cloneNode(!1),i.dataIndex=a.dataIndex=s,e.searchIndex!==null&&void 0!==e.searchIndex&&(i.searchIndex=a.searchIndex=e.searchIndex),t.forEach((t=>{r=e.cells[t].cloneNode(!0),r.data=e.cells[t].data,i.appendChild(r),h.hiddenColumns.includes(t)||(r=e.cells[t].cloneNode(!0),r.data=e.cells[t].data,a.appendChild(r))})),l[2].push(i),l[3].push(a)})),h.headings=l[0],h.activeHeadings=l[1],h.data=l[2],h.activeRows=l[3],h.update()} - -hide(t){if(t.length){const e=this.dt;t.forEach((t=>{e.hiddenColumns.includes(t)||e.hiddenColumns.push(t)})),this.rebuild()}} - -show(t){if(t.length){let e;const s=this.dt;t.forEach((t=>{e=s.hiddenColumns.indexOf(t),e>-1&&s.hiddenColumns.splice(e,1)})),this.rebuild()}} - -visible(t){let e;const s=this.dt;return t=t||s.headings.map((t=>t.originalCellIndex)),isNaN(t)?Array.isArray(t)&&(e=[],t.forEach((t=>{e.push(!s.hiddenColumns.includes(t))}))):e=!s.hiddenColumns.includes(t),e} - -add(t){let e;const s=document.createElement("th");if(!this.dt.headings.length)return this.dt.insert({headings:[t.heading],data:t.data.map((t=>[t]))}),void this.rebuild();this.dt.hiddenHeader?s.innerHTML="":t.heading.nodeName?s.appendChild(t.heading):s.innerHTML=t.heading,this.dt.headings.push(s),this.dt.data.forEach(((s,i)=>{t.data[i]&&(e=document.createElement("td"),t.data[i].nodeName?e.appendChild(t.data[i]):e.innerHTML=t.data[i],e.data=e.innerHTML,t.render&&(e.innerHTML=t.render.call(this,e.data,e,s)),s.appendChild(e))})),t.type&&s.setAttribute("data-type",t.type),t.format&&s.setAttribute("data-format",t.format),t.hasOwnProperty("sortable")&&(s.sortable=t.sortable,s.setAttribute("data-sortable",!0===t.sortable?"true":"false")),this.rebuild(),this.dt.renderHeader()} - -remove(t){Array.isArray(t)?(t.sort(((t,e)=>e-t)),t.forEach((t=>this.remove(t)))):(this.dt.headings.splice(t,1),this.dt.data.forEach((e=>{e.removeChild(e.cells[t])}))),this.rebuild()} - -filter(t,e,s,i){const a=this.dt;if(a.filterState||(a.filterState={originalData:a.data}),!a.filterState[t]){const e=[...i,()=>!0];a.filterState[t]=function(){let t=0;return()=>e[t++%e.length]}()}const n=a.filterState[t](); const o=Array.from(a.filterState.originalData).filter((e=>{const s=e.cells[t]; const i=s.hasAttribute("data-content")?s.getAttribute("data-content"):s.innerText;return typeof n==="function"?n(i):i===n}));a.data=o,a.data.length?(this.rebuild(),a.update()):(a.clear(),a.hasRows=!1,a.setMessage(a.options.labels.noRows)),s||a.emit("datatable.sort",t,e)} - -sort(e,s,i){const a=this.dt;if(a.hasHeadings&&(e<0||e>a.headings.length))return!1;const n=a.options.filters&&a.options.filters[a.headings[e].textContent];if(n&&n.length!==0)return void this.filter(e,s,i,n);a.sorting=!0,i||a.emit("datatable.sorting",e,s);let o=a.data;const r=[]; const h=[];let d=0; let c=0;const u=a.headings[e]; const p=[];if(u.getAttribute("data-type")==="date"){let e=!1;u.hasAttribute("data-format")&&(e=u.getAttribute("data-format")),p.push(Promise.resolve().then((() =>t("./date-7061ceee.js"))).then((({parseDate:t})=>s=>t(s,e))))}Promise.all(p).then((t=>{const n=t[0];let p; let f;Array.from(o).forEach((t=>{const s=t.cells[e]; const i=s.hasAttribute("data-content")?s.getAttribute("data-content"):s.innerText;let a;a=n?n(i):typeof i==="string"?i.replace(/(\$|,|\s|%)/g,""):i,parseFloat(a)==a?h[c++]={value:Number(a),row:t}:r[d++]={value:typeof i==="string"?i.toLowerCase():i,row:t}})),s||(s=u.classList.contains("asc")?"desc":"asc"),s=="desc"?(p=l(r,-1),f=l(h,-1),u.classList.remove("asc"),u.classList.add("desc"),u.setAttribute("aria-sort","descending")):(p=l(h,1),f=l(r,1),u.classList.remove("desc"),u.classList.add("asc"),u.setAttribute("aria-sort","ascending")),a.lastTh&&u!=a.lastTh&&(a.lastTh.classList.remove("desc"),a.lastTh.classList.remove("asc"),a.lastTh.removeAttribute("aria-sort")),a.lastTh=u,o=p.concat(f),a.data=[];const g=[];o.forEach(((t,e)=>{a.data.push(t.row),t.row.searchIndex!==null&&void 0!==t.row.searchIndex&&g.push(e)})),a.searchData=g,this.rebuild(),a.update(),i||a.emit("datatable.sort",e,s)}))} - -rebuild(){let t; let e; let s; let i;const a=this.dt; const n=[];a.activeRows=[],a.activeHeadings=[],a.headings.forEach(((t,e)=>{t.originalCellIndex=e,t.sortable=t.getAttribute("data-sortable")!=="false",a.hiddenColumns.includes(e)||a.activeHeadings.push(t)})),a.selectedColumns.length&&a.data.forEach((t=>{Array.from(t.cells).forEach(((e,s)=>{a.selectedColumns.includes(s)&&a.columnRenderers.forEach((i=>{i.columns.includes(s)&&(a.data[e.parentNode.dataIndex].cells[e.cellIndex].innerHTML=e.innerHTML=i.renderer.call(this,e.data,e,t))}))}))})),a.data.forEach(((o,r)=>{t=o.cloneNode(!1),e=o.cloneNode(!1),t.dataIndex=e.dataIndex=r,o.searchIndex!==null&&void 0!==o.searchIndex&&(t.searchIndex=e.searchIndex=o.searchIndex),Array.from(o.cells).forEach((n=>{s=n.cloneNode(!0),s.data=n.data,t.appendChild(s),a.hiddenColumns.includes(s.cellIndex)||(i=s.cloneNode(!0),i.data=s.data,e.appendChild(i))})),n.push(t),a.activeRows.push(e)})),a.data=n,a.update()}}const c=function(t){let e=!1; let s=!1;if((t=t||this.options.data).headings){e=n("thead");const s=n("tr");t.headings.forEach((t=>{const e=n("th",{html:t});s.appendChild(e)})),e.appendChild(s)}t.data&&t.data.length&&(s=n("tbody"),t.data.forEach((e=>{if(t.headings&&t.headings.length!==e.length)throw new Error("The number of rows do not match the number of headings.");const i=n("tr");e.forEach((t=>{const e=n("td",{html:t});i.appendChild(e)})),s.appendChild(i)}))),e&&(this.dom.tHead!==null&&this.dom.removeChild(this.dom.tHead),this.dom.appendChild(e)),s&&(this.dom.tBodies.length&&this.dom.removeChild(this.dom.tBodies[0]),this.dom.appendChild(s))}; const u={sortable:!0,searchable:!0,paging:!0,perPage:10,perPageSelect:[5,10,15,20,25],nextPrev:!0,firstLast:!1,prevText:"‹",nextText:"›",firstText:"«",lastText:"»",ellipsisText:"…",ascText:"▴",descText:"▾",truncatePager:!0,pagerDelta:2,scrollY:"",fixedColumns:!0,fixedHeight:!1,header:!0,hiddenHeader:!1,footer:!1,tabIndex:!1,rowNavigation:!1,labels:{placeholder:"Search...",perPage:"{select} entries per page",noRows:"No entries found",noResults:"No results match your search query",info:"Showing {start} to {end} of {rows} entries"},layout:{top:"{select}{search}",bottom:"{info}{pager}"}}; const p={classes:{row:"dataTable-editor-row",form:"dataTable-editor-form",item:"dataTable-editor-item",menu:"dataTable-editor-menu",save:"dataTable-editor-save",block:"dataTable-editor-block",close:"dataTable-editor-close",inner:"dataTable-editor-inner",input:"dataTable-editor-input",label:"dataTable-editor-label",modal:"dataTable-editor-modal",action:"dataTable-editor-action",header:"dataTable-editor-header",wrapper:"dataTable-editor-wrapper",editable:"dataTable-editor-editable",container:"dataTable-editor-container",separator:"dataTable-editor-separator"},labels:{editCell:"Edit Cell",editRow:"Edit Row",removeRow:"Remove Row",reallyRemove:"Are you sure?"},hiddenColumns:!1,contextMenu:!0,clickEvent:"dblclick",excludeColumns:[],menuItems:[{text:t=>t.options.labels.editCell,action:(t,e)=>{const s=t.event.target.closest("td");return t.editCell(s)}},{text:t=>t.options.labels.editRow,action:(t,e)=>{const s=t.event.target.closest("tr");return t.editRow(s)}},{separator:!0},{text:t=>t.options.labels.removeRow,action:(t,e)=>{if(confirm(t.options.labels.reallyRemove)){const e=t.event.target.closest("tr");t.removeRow(e)}}}]};class f{constructor(t,e={}){this.dataTable=t,this.options={...p,...e}} - -init(){this.initialized||(this.dataTable.wrapper.classList.add(this.options.classes.editable),this.options.contextMenu&&(this.container=n("div",{id:this.options.classes.container}),this.wrapper=n("div",{class:this.options.classes.wrapper}),this.menu=n("ul",{class:this.options.classes.menu}),this.options.menuItems&&this.options.menuItems.length&&this.options.menuItems.forEach((t=>{const e=n("li",{class:t.separator?this.options.classes.separator:this.options.classes.item});if(!t.separator){const s=n("a",{class:this.options.classes.action,href:t.url||"#",html:typeof t.text==="function"?t.text(this):t.text});e.appendChild(s),t.action&&typeof t.action==="function"&&s.addEventListener("click",(e=>{e.preventDefault(),t.action(this,e)}))}this.menu.appendChild(e)})),this.wrapper.appendChild(this.menu),this.container.appendChild(this.wrapper),this.update()),this.data={},this.closed=!0,this.editing=!1,this.editingRow=!1,this.editingCell=!1,this.bindEvents(),setTimeout((()=>{this.initialized=!0,this.dataTable.emit("editable.init")}),10))} - -bindEvents(){this.events={context:this.context.bind(this),update:this.update.bind(this),dismiss:this.dismiss.bind(this),keydown:this.keydown.bind(this),click:this.click.bind(this)},this.dataTable.body.addEventListener(this.options.clickEvent,this.events.click),document.addEventListener("click",this.events.dismiss),document.addEventListener("keydown",this.events.keydown),this.options.contextMenu&&(this.dataTable.body.addEventListener("contextmenu",this.events.context),this.events.reset=function(t,e=300){let s;return(...i)=>{clearTimeout(s),s=setTimeout((()=>{t.apply(this,i)}),e)}}(this.events.update,50),window.addEventListener("resize",this.events.reset),window.addEventListener("scroll",this.events.reset))} - -context(t){this.event=t;const e=this.dataTable.body.contains(t.target);if(this.options.contextMenu&&!this.disabled&&e){t.preventDefault();let e=t.pageX; let s=t.pageY;e>this.limits.x&&(e-=this.rect.width),s>this.limits.y&&(s-=this.rect.height),this.wrapper.style.top=`${s}px`,this.wrapper.style.left=`${e}px`,this.openMenu(),this.update()}} - -click(t){if(this.editing&&this.data&&this.editingCell)this.saveCell();else if(!this.editing){const e=t.target.closest("td");e&&(this.editCell(e),t.preventDefault())}} - -keydown(t){this.modal?t.key==="Escape"?this.closeModal():t.key==="Enter"&&this.saveRow():this.editing&&this.data&&(t.key==="Enter"?this.editingCell?this.saveCell():this.editingRow&&this.saveRow():t.key==="Escape"&&this.saveCell(this.data.content))} - -editCell(t){this.options.excludeColumns.includes(t.cellIndex)?this.closeMenu():(t=this.dataTable.body.rows[t.parentNode.dataIndex].cells[t.cellIndex],this.data={cell:t,content:t.dataset.content||t.innerHTML,input:n("input",{type:"text",value:t.dataset.content||t.innerHTML,class:this.options.classes.input})},t.innerHTML="",t.appendChild(this.data.input),setTimeout((()=>{this.data.input.focus(),this.data.input.selectionStart=this.data.input.selectionEnd=this.data.input.value.length,this.editing=!0,this.editingCell=!0,this.closeMenu()}),10))} - -saveCell(t,e){e=e||this.data.cell,t=t||this.data.input.value;const s=this.data.content;this.dataTable.data[e.parentNode.dataIndex].cells[e.cellIndex].innerHTML=e.innerHTML=t.trim(),this.data={},this.editing=this.editingCell=!1,this.dataTable.emit("editable.save.cell",t,s,e)} - -editRow(t){if(!(t=t||this.event.target.closest("tr"))||t.nodeName!=="TR"||this.editing)return;t=this.dataTable.body.rows[t.dataIndex];const e=[`
`,`
`,"

Editing row

",``,"
",`
`,`
`,`
`,``,"
","
","
","
"].join(""); const s=n("div",{class:this.options.classes.modal,html:e}); const i=s.firstElementChild.lastElementChild.firstElementChild;Array.from(t.cells).forEach(((t,e)=>{(!t.hidden||t.hidden&&this.options.hiddenColumns)&&!this.options.excludeColumns.includes(e)&&i.insertBefore(n("div",{class:this.options.classes.row,html:[`
`,``,``,"
"].join("")}),i.lastElementChild)})),this.modal=s,this.openModal();const a=Array.from(i.elements);a.pop(),this.data={row:t,inputs:a},this.editing=!0,this.editingRow=!0,s.addEventListener("click",(t=>{t.target.hasAttribute("data-editor-close")?this.closeModal():t.target.hasAttribute("data-editor-save")&&this.saveRow()})),this.closeMenu()} - -saveRow(t,e){t=t||this.data.inputs.map((t=>t.value.trim())),e=e||this.data.row;const s=Array.from(e.cells).map((t=>t.dataset.content||t.innerHTML));Array.from(e.cells).forEach(((e,s)=>{e.innerHTML=t[s]})),this.closeModal(),this.dataTable.emit("editable.save.row",t,s,e)} - -openModal(){!this.editing&&this.modal&&document.body.appendChild(this.modal)} - -closeModal(){this.editing&&this.modal&&(document.body.removeChild(this.modal),this.modal=this.editing=this.editingRow=!1)} - -removeRow(t){t?(t instanceof Element&&t.nodeName==="TR"&&void 0!==t.dataIndex&&(t=t.dataIndex),this.dataTable.rows.remove(t),this.closeMenu()):(t=this.event.target.closest("tr"))&&void 0!==t.dataIndex&&(this.dataTable.rows.remove(t.dataIndex),this.closeMenu())} - -update(){const t=window.scrollX||window.pageXOffset; const e=window.scrollY||window.pageYOffset;this.rect=this.wrapper.getBoundingClientRect(),this.limits={x:window.innerWidth+t-this.rect.width,y:window.innerHeight+e-this.rect.height}} - -dismiss(t){let e=!0;this.options.contextMenu&&(e=!this.wrapper.contains(t.target),this.editing&&(e=!this.wrapper.contains(t.target)&&t.target!==this.data.input)),e&&(this.editingCell&&this.saveCell(this.data.content),this.closeMenu())} - -openMenu(){this.editing&&this.data&&this.editingCell&&this.saveCell(),this.options.contextMenu&&(document.body.appendChild(this.container),this.closed=!1,this.dataTable.emit("editable.context.open"))} - -closeMenu(){this.options.contextMenu&&!this.closed&&(this.closed=!0,document.body.removeChild(this.container),this.dataTable.emit("editable.context.close"))} - -destroy(){this.dataTable.body.removeEventListener(this.options.clickEvent,this.events.click),this.dataTable.body.removeEventListener("contextmenu",this.events.context),document.removeEventListener("click",this.events.dismiss),document.removeEventListener("keydown",this.events.keydown),window.removeEventListener("resize",this.events.reset),window.removeEventListener("scroll",this.events.reset),document.body.contains(this.container)&&document.body.removeChild(this.container),this.initialized=!1}}s.DataTable=class{constructor(t,e={}){const s=typeof t==="string"?document.querySelector(t):t;if(this.options={...u,...e,layout:{...u.layout,...e.layout},labels:{...u.labels,...e.labels}},this.rows=new h(this),this.columns=new d(this),this.initialized=!1,this.initialLayout=s.innerHTML,this.initialSortable=this.options.sortable,this.options.tabIndex?s.tabIndex=this.options.tabIndex:this.options.rowNavigation&&s.tabIndex===-1&&(s.tabIndex=0),this.options.header||(this.options.sortable=!1),s.tHead===null&&(!this.options.data||this.options.data&&!this.options.data.headings)&&(this.options.sortable=!1),s.tBodies.length&&!s.tBodies[0].rows.length&&this.options.data&&!this.options.data.data)throw new Error("You seem to be using the data option, but you've not defined any rows.");this.dom=s,this.listeners={onResize:t=>this.onResize(t)},this.init()} - -init(t){if(this.initialized||this.dom.classList.contains("dataTable-table"))return!1;Object.assign(this.options,t||{}),this.currentPage=1,this.onFirstPage=!0,this.hiddenColumns=[],this.columnRenderers=[],this.selectedColumns=[],this.render(),setTimeout((()=>{this.emit("datatable.init"),this.initialized=!0}),10)} - -render(){let t="";if(this.options.data&&c.call(this),this.body=this.dom.tBodies[0],this.head=this.dom.tHead,this.foot=this.dom.tFoot,this.body||(this.body=n("tbody"),this.dom.appendChild(this.body)),this.hasRows=this.body.rows.length>0,!this.head){const t=n("thead"); const e=n("tr");this.hasRows&&(Array.from(this.body.rows[0].cells).forEach((()=>{e.appendChild(n("th"))})),t.appendChild(e)),this.head=t,this.dom.insertBefore(this.head,this.body),this.hiddenHeader=this.options.hiddenHeader}if(this.headings=[],this.hasHeadings=this.head.rows.length>0,this.hasHeadings&&(this.header=this.head.rows[0],this.headings=[].slice.call(this.header.cells)),this.options.header||this.head&&this.dom.removeChild(this.dom.tHead),this.options.footer?this.head&&!this.foot&&(this.foot=n("tfoot",{html:this.head.innerHTML}),this.dom.appendChild(this.foot)):this.foot&&this.dom.removeChild(this.dom.tFoot),this.wrapper=n("div",{class:"dataTable-wrapper dataTable-loading"}),t+="
",t+=this.options.layout.top,t+="
",this.options.scrollY.length?t+=`
`:t+="
",t+="
",t+=this.options.layout.bottom,t+="
",t=t.replace("{info}",this.options.paging?"
":""),this.options.paging&&this.options.perPageSelect){let e="
";const s=n("select",{class:"dataTable-selector"});this.options.perPageSelect.forEach((t=>{const e=t===this.options.perPage; const i=new Option(t,t,e,e);s.add(i)})),e=e.replace("{select}",s.outerHTML),t=t.replace("{select}",e)}else t=t.replace("{select}","");if(this.options.searchable){const e=``;t=t.replace("{search}",e)}else t=t.replace("{search}","");this.hasHeadings&&this.renderHeader(),this.dom.classList.add("dataTable-table");const e=n("nav",{class:"dataTable-pagination"}); const s=n("ul",{class:"dataTable-pagination-list"});e.appendChild(s),t=t.replace(/\{pager\}/g,e.outerHTML),this.wrapper.innerHTML=t,this.container=this.wrapper.querySelector(".dataTable-container"),this.pagers=this.wrapper.querySelectorAll(".dataTable-pagination-list"),this.label=this.wrapper.querySelector(".dataTable-info"),this.dom.parentNode.replaceChild(this.wrapper,this.dom),this.container.appendChild(this.dom),this.rect=this.dom.getBoundingClientRect(),this.data=Array.from(this.body.rows),this.activeRows=this.data.slice(),this.activeHeadings=this.headings.slice(),this.update(),this.setColumns(),this.fixHeight(),this.fixColumns(),this.options.header||this.wrapper.classList.add("no-header"),this.options.footer||this.wrapper.classList.add("no-footer"),this.options.sortable&&this.wrapper.classList.add("sortable"),this.options.searchable&&this.wrapper.classList.add("searchable"),this.options.fixedHeight&&this.wrapper.classList.add("fixed-height"),this.options.fixedColumns&&this.wrapper.classList.add("fixed-columns"),this.bindEvents()} - -renderPage(t=!1){if(this.hasHeadings&&(o(this.header),this.activeHeadings.forEach((t=>this.header.appendChild(t)))),this.hasRows&&this.totalPages){this.currentPage>this.totalPages&&(this.currentPage=1);const t=this.currentPage-1; const e=document.createDocumentFragment();this.pages[t].forEach((t=>e.appendChild(this.rows.render(t)))),this.clear(e),this.onFirstPage=this.currentPage===1,this.onLastPage=this.currentPage===this.lastPage}else this.setMessage(this.options.labels.noRows);let e; let s=0; let i=0; let a=0;if(this.totalPages&&(s=this.currentPage-1,i=s*this.options.perPage,a=i+this.pages[s].length,i+=1,e=this.searching?this.searchData.length:this.data.length),this.label&&this.options.labels.info.length){const t=this.options.labels.info.replace("{start}",i).replace("{end}",a).replace("{page}",this.currentPage).replace("{pages}",this.totalPages).replace("{rows}",e);this.label.innerHTML=e?t:""}if(this.currentPage==1&&this.fixHeight(),this.options.rowNavigation&&(!this.rows.cursor||!this.pages[this.currentPage-1].includes(this.rows.cursor))){const e=this.pages[this.currentPage-1];t?this.rows.setCursor(e[e.length-1]):this.rows.setCursor(e[0])}} - -renderPager(){if(o(this.pagers),this.totalPages>1){const t="pager"; const e=document.createDocumentFragment(); const s=this.onFirstPage?1:this.currentPage-1; const i=this.onLastPage?this.totalPages:this.currentPage+1;this.options.firstLast&&e.appendChild(r(t,1,this.options.firstText)),this.options.nextPrev&&!this.onFirstPage&&e.appendChild(r(t,s,this.options.prevText));let a=this.links;this.options.truncatePager&&(a=((t,e,s,i,a)=>{let o;const r=2*(i=i||2);let l=e-i; let h=e+i;const d=[]; const c=[];e<4-i+r?h=3+r:e>s-(3-i+r)&&(l=s-(2+r));for(let e=1;e<=s;e++)if(e==1||e==s||e>=l&&e<=h){const s=t[e-1];s.classList.remove("active"),d.push(s)}return d.forEach((e=>{const s=e.children[0].getAttribute("data-page");if(o){const e=o.children[0].getAttribute("data-page");if(s-e==2)c.push(t[e]);else if(s-e!=1){const t=n("li",{class:"ellipsis",html:`${a}`});c.push(t)}}c.push(e),o=e})),c})(this.links,this.currentPage,this.pages.length,this.options.pagerDelta,this.options.ellipsisText)),this.links[this.currentPage-1].classList.add("active"),a.forEach((t=>{t.classList.remove("active"),e.appendChild(t)})),this.links[this.currentPage-1].classList.add("active"),this.options.nextPrev&&!this.onLastPage&&e.appendChild(r(t,i,this.options.nextText)),this.options.firstLast&&e.appendChild(r(t,this.totalPages,this.options.lastText)),this.pagers.forEach((t=>{t.appendChild(e.cloneNode(!0))}))}} - -renderHeader(){this.labels=[],this.headings&&this.headings.length&&this.headings.forEach(((t,e)=>{if(this.labels[e]=t.textContent,t.firstElementChild&&t.firstElementChild.classList.contains("dataTable-sorter")&&(t.innerHTML=t.firstElementChild.innerHTML),t.sortable=t.getAttribute("data-sortable")!=="false",t.originalCellIndex=e,this.options.sortable&&t.sortable){const e=n("a",{href:"#",class:"dataTable-sorter",html:t.innerHTML});t.innerHTML="",t.setAttribute("data-sortable",""),t.appendChild(e)}})),this.fixColumns()} - -bindEvents(){if(this.options.perPageSelect){const t=this.wrapper.querySelector(".dataTable-selector");t&&t.addEventListener("change",(()=>{this.options.perPage=parseInt(t.value,10),this.update(),this.fixHeight(),this.emit("datatable.perpage",this.options.perPage)}),!1)}this.options.searchable&&(this.input=this.wrapper.querySelector(".dataTable-input"),this.input&&this.input.addEventListener("keyup",(()=>this.search(this.input.value)),!1)),this.wrapper.addEventListener("click",(t=>{const e=t.target.closest("a");e&&e.nodeName.toLowerCase()==="a"&&(e.hasAttribute("data-page")?(this.page(e.getAttribute("data-page")),t.preventDefault()):this.options.sortable&&e.classList.contains("dataTable-sorter")&&e.parentNode.getAttribute("data-sortable")!="false"&&(this.columns.sort(this.headings.indexOf(e.parentNode)),t.preventDefault()))}),!1),this.options.rowNavigation?(this.dom.addEventListener("keydown",(t=>{t.key==="ArrowUp"?this.rows.cursor.previousElementSibling?(this.rows.setCursor(this.rows.cursor.previousElementSibling),t.preventDefault(),t.stopPropagation()):this.onFirstPage||this.page(this.currentPage-1,!0):t.key==="ArrowDown"?this.rows.cursor.nextElementSibling?(this.rows.setCursor(this.rows.cursor.nextElementSibling),t.preventDefault(),t.stopPropagation()):this.onLastPage||this.page(this.currentPage+1):["Enter"," "].includes(t.key)&&this.emit("datatable.selectrow",this.rows.cursor,t)})),this.body.addEventListener("mousedown",(t=>{if(this.body.matches(":focus")){const e=Array.from(this.body.rows).find((e=>e.contains(t.target)));this.emit("datatable.selectrow",e,t)}}))):this.body.addEventListener("mousedown",(t=>{const e=Array.from(this.body.rows).find((e=>e.contains(t.target)));this.emit("datatable.selectrow",e,t)})),window.addEventListener("resize",this.listeners.onResize)} - -onResize(){this.rect=this.container.getBoundingClientRect(),this.rect.width&&this.fixColumns()} - -setColumns(t){t||this.data.forEach((t=>{Array.from(t.cells).forEach((t=>{t.data=t.innerHTML}))})),this.options.columns&&this.headings.length&&this.options.columns.forEach((t=>{Array.isArray(t.select)||(t.select=[t.select]),t.hasOwnProperty("render")&&typeof t.render==="function"&&(this.selectedColumns=this.selectedColumns.concat(t.select),this.columnRenderers.push({columns:t.select,renderer:t.render})),t.select.forEach((e=>{const s=this.headings[e];s&&(t.type&&s.setAttribute("data-type",t.type),t.format&&s.setAttribute("data-format",t.format),t.hasOwnProperty("sortable")&&s.setAttribute("data-sortable",t.sortable),t.hasOwnProperty("hidden")&&!1!==t.hidden&&this.columns.hide([e]),t.hasOwnProperty("sort")&&t.select.length===1&&this.columns.sort(t.select[0],t.sort,!0))}))})),this.hasRows&&(this.data.forEach(((t,e)=>{t.dataIndex=e,Array.from(t.cells).forEach((t=>{t.data=t.innerHTML}))})),this.columns.rebuild()),this.renderHeader()} - -destroy(){this.dom.innerHTML=this.initialLayout,this.dom.classList.remove("dataTable-table"),this.wrapper.parentNode.replaceChild(this.dom,this.wrapper),this.initialized=!1,window.removeEventListener("resize",this.listeners.onResize)} - -update(){this.wrapper.classList.remove("dataTable-empty"),this.paginate(),this.renderPage(),this.links=[];let t=this.pages.length;for(;t--;){const e=t+1;this.links[t]=r(t===0?"active":"",e,e)}this.sorting=!1,this.renderPager(),this.rows.update(),this.emit("datatable.update")} - -paginate(){let t=this.activeRows;return this.searching&&(t=[],this.searchData.forEach((e=>t.push(this.activeRows[e])))),this.options.paging?this.pages=t.map(((e,s)=>s%this.options.perPage==0?t.slice(s,s+this.options.perPage):null)).filter((t=>t)):this.pages=[t],this.totalPages=this.lastPage=this.pages.length,this.totalPages} - -fixColumns(){if((this.options.scrollY.length||this.options.fixedColumns)&&this.activeHeadings&&this.activeHeadings.length){let t; let e=!1;if(this.columnWidths=[],this.dom.tHead){this.options.scrollY.length&&(e=n("thead"),e.appendChild(n("tr")),e.style.height="0px",this.headerTable&&(this.dom.tHead=this.headerTable.tHead)),this.activeHeadings.forEach((t=>{t.style.width=""}));const t=this.activeHeadings.reduce(((t,e)=>t+e.offsetWidth),0);if(this.activeHeadings.forEach(((s,i)=>{const a=s.offsetWidth; const o=a/t*100;if(s.style.width=`${o}%`,this.columnWidths[i]=a,this.options.scrollY.length){const t=n("th");e.firstElementChild.appendChild(t),t.style.width=`${o}%`,t.style.paddingTop="0",t.style.paddingBottom="0",t.style.border="0"}})),this.options.scrollY.length){const t=this.dom.parentElement;if(!this.headerTable){this.headerTable=n("table",{class:"dataTable-table"});const e=n("div",{class:"dataTable-headercontainer"});e.appendChild(this.headerTable),t.parentElement.insertBefore(e,t)}const s=this.dom.tHead;this.dom.replaceChild(e,s),this.headerTable.tHead=s,this.headerTable.parentElement.style.paddingRight=`${this.headerTable.clientWidth-this.dom.clientWidth+parseInt(this.headerTable.parentElement.style.paddingRight||"0",10)}px`,t.scrollHeight>t.clientHeight&&(t.style.overflowY="scroll")}}else{t=[],e=n("thead");const s=n("tr");Array.from(this.dom.tBodies[0].rows[0].cells).forEach((()=>{const e=n("th");s.appendChild(e),t.push(e)})),e.appendChild(s),this.dom.insertBefore(e,this.body);const i=[];t.forEach(((t,e)=>{const s=t.offsetWidth; const a=s/this.rect.width*100;i.push(a),this.columnWidths[e]=s})),this.data.forEach((t=>{Array.from(t.cells).forEach(((t,e)=>{this.columns.visible(t.cellIndex)&&(t.style.width=`${i[e]}%`)}))})),this.dom.removeChild(e)}}} - -fixHeight(){this.options.fixedHeight&&(this.container.style.height=null,this.rect=this.container.getBoundingClientRect(),this.container.style.height=`${this.rect.height}px`)} - -search(t){return!!this.hasRows&&(t=t.toLowerCase(),this.currentPage=1,this.searching=!0,this.searchData=[],t.length?(this.clear(),this.data.forEach(((e,s)=>{const i=this.searchData.includes(e);t.split(" ").reduce(((t,s)=>{let i=!1; let a=null; let n=null;for(let t=0;tthis.pages.length||t<0)&&(this.renderPage(e),this.renderPager(),void this.emit("datatable.page",t)))} - -sortColumn(t,e){this.columns.sort(t,e)} - -insert(t){let e=[];if(i(t)){if(t.headings&&!this.hasHeadings&&!this.hasRows){const e=n("tr");t.headings.forEach((t=>{const s=n("th",{html:t});e.appendChild(s)})),this.head.appendChild(e),this.header=e,this.headings=[].slice.call(e.cells),this.hasHeadings=!0,this.options.sortable=this.initialSortable,this.renderHeader(),this.activeHeadings=this.headings.slice()}t.data&&Array.isArray(t.data)&&(e=t.data)}else Array.isArray(t)&&t.forEach((t=>{const s=[];Object.entries(t).forEach((([t,e])=>{const i=this.labels.indexOf(t);i>-1&&(s[i]=e)})),e.push(s)}));e.length&&(this.rows.add(e),this.hasRows=!0),this.update(),this.setColumns(),this.fixColumns()} - -refresh(){this.options.searchable&&(this.input.value="",this.searching=!1),this.currentPage=1,this.onFirstPage=!0,this.update(),this.emit("datatable.refresh")} - -clear(t){this.body&&o(this.body);let e=this.body;this.body||(e=this.dom),t&&(typeof t==="string"&&(document.createDocumentFragment().innerHTML=t),e.appendChild(t))} - -print(){const t=this.activeHeadings; const e=this.activeRows; const s=n("table"); const i=n("thead"); const a=n("tbody"); const o=n("tr");t.forEach((t=>{o.appendChild(n("th",{html:t.textContent}))})),i.appendChild(o),e.forEach((t=>{const e=n("tr");Array.from(t.cells).forEach((t=>{e.appendChild(n("td",{html:t.textContent}))})),a.appendChild(e)})),s.appendChild(i),s.appendChild(a);const r=window.open();r.document.body.appendChild(s),r.print()} - -setMessage(t){let e=1;this.hasRows?e=this.data[0].cells.length:this.activeHeadings.length&&(e=this.activeHeadings.length),this.wrapper.classList.add("dataTable-empty"),this.label&&(this.label.innerHTML=""),this.totalPages=0,this.renderPager(),this.clear(n("tr",{html:`${t}`}))} - -on(t,e){this.events=this.events||{},this.events[t]=this.events[t]||[],this.events[t].push(e)} - -off(t,e){this.events=this.events||{},t in this.events!=0&&this.events[t].splice(this.events[t].indexOf(e),1)} - -emit(t){if(this.events=this.events||{},t in this.events!=0)for(let e=0;et.trim().replace(/(^"|"$)/g,"")))),t.shift()),t.forEach(((t,i)=>{e.data[i]=[];const a=t.split(s.columnDelimiter);a.length&&a.forEach((t=>{s.removeDoubleQuotes&&(t=t.trim().replace(/(^"|"$)/g,"")),e.data[i].push(t)}))}))),e)return e}return!1},s.convertJSON=function(t={}){let e=!1;if(!i(t))return!1;const s={...t};if(s.data.length||i(s.data)){const t=a(s.data);if(t?(e={headings:[],data:[]},t.forEach(((t,s)=>{e.data[s]=[],Object.entries(t).forEach((([t,i])=>{e.headings.includes(t)||e.headings.push(t),e.data[s].push(i)}))}))):console.warn("That's not valid JSON!"),e)return e}return!1},s.createElement=n,s.exportCSV=function(t,e={}){if(!t.hasHeadings&&!t.hasRows)return!1;const s=t.activeHeadings;let a; let n; let o; let r; let l=[];if(!i(e))return!1;const h={download:!0,skipColumn:[],lineDelimiter:"\n",columnDelimiter:",",...e};if(l[0]=t.header,h.selection)if(isNaN(h.selection)){if(Array.isArray(h.selection))for(a=0;as.init())),s}},{"./date-7061ceee.js":1}]},{},[2])(2))); -// # sourceMappingURL=/sm/ecd7e55334cc99506bcb5e8cc047ef01e68c1dcb40a0d23a5d482f195c93ec51.map \ No newline at end of file diff --git a/ereuse_devicehub/templates/ereuse_devicehub/base.html b/ereuse_devicehub/templates/ereuse_devicehub/base.html index 85063560..6c54dd49 100644 --- a/ereuse_devicehub/templates/ereuse_devicehub/base.html +++ b/ereuse_devicehub/templates/ereuse_devicehub/base.html @@ -19,12 +19,14 @@ - + + + - + diff --git a/ereuse_devicehub/templates/ereuse_devicehub/base_site.html b/ereuse_devicehub/templates/ereuse_devicehub/base_site.html index f3865433..f6dfb1b3 100644 --- a/ereuse_devicehub/templates/ereuse_devicehub/base_site.html +++ b/ereuse_devicehub/templates/ereuse_devicehub/base_site.html @@ -309,8 +309,8 @@
Help | - Privacy | - Terms + Privacy | + Terms
DeviceHub {{ version }} diff --git a/ereuse_devicehub/templates/ereuse_devicehub/user_login.html b/ereuse_devicehub/templates/ereuse_devicehub/user_login.html index 78776b16..b8b6cbc0 100644 --- a/ereuse_devicehub/templates/ereuse_devicehub/user_login.html +++ b/ereuse_devicehub/templates/ereuse_devicehub/user_login.html @@ -71,8 +71,8 @@
Help | - Privacy | - Terms + Privacy | + Terms
diff --git a/ereuse_devicehub/templates/ereuse_devicehub/user_profile.html b/ereuse_devicehub/templates/ereuse_devicehub/user_profile.html index fd8b8804..462fc6ec 100644 --- a/ereuse_devicehub/templates/ereuse_devicehub/user_profile.html +++ b/ereuse_devicehub/templates/ereuse_devicehub/user_profile.html @@ -34,7 +34,10 @@
@@ -65,7 +68,34 @@
+
+
+ +
+ {% for f in sanitization_form %} + {% if f == sanitization_form.csrf_token %} + {{ f }} + {% else %} +
+ +
+ {{ f }} + {% if f.errors %} +

+ {% for error in f.errors %} + {{ error }}
+ {% endfor %} +

+ {% endif %} +
+
+ {% endif %} + {% endfor %} +
+ +
+
diff --git a/ereuse_devicehub/templates/inventory/device_list.html b/ereuse_devicehub/templates/inventory/device_list.html index 78c3e39d..f89c93f6 100644 --- a/ereuse_devicehub/templates/inventory/device_list.html +++ b/ereuse_devicehub/templates/inventory/device_list.html @@ -93,6 +93,11 @@ Receiver Note + {% endif %} {% endif %} @@ -656,6 +661,37 @@ {% endif %} + +
+
Customer Details
+
+ {{ form_customer_details.csrf_token }} + + {% for field in form_customer_details %} + {% if field != form_customer_details.csrf_token %} +
+ {% if field != form_customer_details.type %} + {{ field.label(class_="form-label") }} + {{ field }} + {{ field.description }} + {% if field.errors %} +

+ {% for error in field.errors %} + {{ error }}
+ {% endfor %} +

+ {% endif %} + {% endif %} +
+ {% endif %} + {% endfor %} + +
+ Cancel + +
+
+
{% endif %} diff --git a/ereuse_devicehub/templates/inventory/erasure.html b/ereuse_devicehub/templates/inventory/erasure.html index 57fa5139..76df0694 100644 --- a/ereuse_devicehub/templates/inventory/erasure.html +++ b/ereuse_devicehub/templates/inventory/erasure.html @@ -1,80 +1,398 @@ -{% extends "documents/layout.html" %} -{% block body %} -
-

Summary

- - - - - - - - - - - {% for erasure in erasures %} - - - - - - - {% endfor %} - -
S/N Data StorageType of erasureResultDate
- {{ erasure.device.serial_number.upper() }} - - {{ erasure.type }} - - {{ erasure.severity }} - - {{ erasure.date_str }} -
-
-
-

Details

- {% for erasure in erasures %} -
-

{{ erasure.device.__format__('t') }}

-
-
Data storage:
-
{{ erasure.device.__format__('ts') }}
+ + + +Data Sanitization Certificate + + + + + + + +
+
+
+
- + +
+
+
+

Data Sanitization Certificate

-{% endblock %} +
+ +
+
+ Entity Information +
+
+ + + + + + + + + + +
+ Name: + + {{ customer_details and customer_details.company_name or ''}} +
+ Location: + + {{ customer_details and customer_details.location or '' }} +
+
+
+ +
+
+ Responsible Sanitization Entity +
+
+ + + + + + + + + + + + + + +
+ Name: + + {{ my_data and my_data.company_name or '' }} +
+ Responsible Person + + {{ my_data and my_data.responsable_person or '' }} +
+ Location: + + {{ my_data and my_data.location or '' }} +
+
+
+ +
+
+ Summary +
+
+ + + {% if erasure_hosts %} + {% for e in erasure_hosts %} + + + + + {% endfor %} + {% else %} + + + + + {% endif %} + + + + + + + + +
+ N° of sanitization server ({{ loop.index }}/{{ erasure_hosts|length }}): + + {% if e.serial_number %} + {{ e.serial_number.upper() }} + {% endif %} +
+ N° of computers: + + {{ n_computers }} +
+ N° of data storage unit(s): + + {{ erasures | length }} +
+ Sanitization result: + + {{ result }} +
+
+
+ +
+
+ Report Details +
+
+ + + + + + + + + + + + + + +
+ Report UUID: + + {{ uuid_report }} +
+ Report Date: + + {{ date_report }} +
+ Software Version: + + {{ software }} +
+
+
+ +
+
+

+ I hereby declare that the data erasure process has been carried + out in accordance with the instructions received. +

+
+
+ +
+
+ + + + + + +
+ Data Responsable +
+ {{ my_data and my_data.responsable_person or '' }} +
+ Data Supervisor +
+ {{ my_data and my_data.supervisor_person or '' }} +
+
+
+ +{% if erasures %} + {% if erasure_hosts %} + {% for server in erasure_hosts %} +
+
+

Server Summary

+
+
+

SN Server {{ server.serial_number and server.serial_number.upper() }}

+
+
+
+
+ + + + + + + + + + + {% for erasure in erasures %} + {% if erasure.parent == server %} + + + + + + + {% endif %} + {% endfor %} + +
SN StorageMethodResultDate
+ {{ erasure.device.serial_number.upper() }} + + {{ erasure.get_public_name() }} + + {{ erasure.severity.get_public_name() }} + + {{ erasure.date_str }} +
+
+
+ {% endfor %} + {% endif %} + {% if erasures_normal %} +
+
+

Devices Summary

+
+
+
+
+ + + + + + + + + + + {% for erasure in erasures_normal %} + + + + + + + {% endfor %} + +
SN StorageMethodResultDate
+ {{ erasure.device.serial_number.upper() }} + + {{ erasure.get_public_name() }} + + {{ erasure.severity.get_public_name() }} + + {{ erasure.date_str }} +
+
+
+ {% endif %} +
+{% for erasure in erasures %} +
+

{{ erasure.device.serial_number.upper() }}

+
+
Data storage:
+
{{ erasure.device.__format__('ts') }}
+ + {% if erasure.parent %} +
Computer where was erase:
+
Title: {{ erasure.parent.__format__('ts') }}
+
DevicehubID: {{ erasure.parent.dhid }}
+
Hid: {{ erasure.parent.hid }}
+
Tags: {{ erasure.parent.tags }}
+ +
Computer where it resides:
+
Title: {{ erasure.device.parent.__format__('ts') }}
+
DevicehubID: {{ erasure.device.parent.dhid }}
+
Hid: {{ erasure.device.parent.hid }}
+
Tags: {{ erasure.device.parent.tags }}
+ {% endif %} + +
Erasure:
+
{{ erasure.__format__('ts') }}
+ {% if erasure.steps %} +
Erasure steps:
+
+
    + {% for step in erasure.steps %} +
  1. {{ step.__format__('') }}
  2. + {% endfor %} +
+
+ {% endif %} +
+
+{% endfor %} +{% endif %} + + + \ No newline at end of file diff --git a/ereuse_devicehub/templates/workbench/wbSettings.ini b/ereuse_devicehub/templates/workbench/wbSettings.ini index 213240c1..53bbc34d 100644 --- a/ereuse_devicehub/templates/workbench/wbSettings.ini +++ b/ereuse_devicehub/templates/workbench/wbSettings.ini @@ -7,7 +7,7 @@ DH_HOST = {{ api_host }} DH_DATABASE = {{ schema }} DEVICEHUB_URL = https://${DB_HOST}/${DB_DATABASE}/ -WB_BENCHMARK = True +WB_BENCHMARK = False WB_STRESS_TEST = 0 WB_SMART_TEST = short @@ -21,7 +21,7 @@ DH_HOST = {{ api_host }} DH_DATABASE = {{ schema }} DEVICEHUB_URL = https://${DB_HOST}/${DB_DATABASE}/ -WB_BENCHMARK = True +WB_BENCHMARK = False WB_STRESS_TEST = 0 WB_SMART_TEST = short diff --git a/ereuse_devicehub/views.py b/ereuse_devicehub/views.py index 1316efae..f2cd4bc1 100644 --- a/ereuse_devicehub/views.py +++ b/ereuse_devicehub/views.py @@ -1,14 +1,15 @@ import flask +from decouple import config from flask import Blueprint from flask import current_app as app -from flask import g, session +from flask import g from flask.views import View from flask_login import current_user, login_required, login_user, logout_user from sqlalchemy import or_ from ereuse_devicehub import __version__, messages from ereuse_devicehub.db import db -from ereuse_devicehub.forms import LoginForm, PasswordForm +from ereuse_devicehub.forms import LoginForm, PasswordForm, SanitizationEntityForm from ereuse_devicehub.resources.action.models import Trade from ereuse_devicehub.resources.lot.models import Lot from ereuse_devicehub.resources.user.models import User @@ -46,7 +47,7 @@ class LoginView(View): url_reset_password = "#" if 'register' in app.blueprints.keys(): - url_register = flask.url_for('register.user-registration') + url_register = config("PRICES_PAGE", "#") if 'reset_password' in app.blueprints.keys(): url_reset_password = flask.url_for('reset_password.reset-password') @@ -99,10 +100,15 @@ class UserProfileView(GenericMixin): def dispatch_request(self): self.get_context() + sanitization_form = SanitizationEntityForm() + if g.user.sanitization_entity: + sanitization = list(g.user.sanitization_entity)[0] + sanitization_form = SanitizationEntityForm(obj=sanitization) self.context.update( { 'current_user': current_user, 'password_form': PasswordForm(), + 'sanitization_form': sanitization_form, } ) @@ -126,7 +132,27 @@ class UserPasswordView(View): return flask.redirect(flask.url_for('core.user-profile')) +class SanitizationEntityView(View): + methods = ['POST'] + decorators = [login_required] + + def dispatch_request(self): + form = SanitizationEntityForm() + db.session.commit() + if form.validate_on_submit(): + form.save(commit=False) + messages.success('Sanitization datas updated successfully!') + else: + messages.error('Error modifying Sanitization datas!') + + db.session.commit() + return flask.redirect(flask.url_for('core.user-profile')) + + core.add_url_rule('/login/', view_func=LoginView.as_view('login')) core.add_url_rule('/logout/', view_func=LogoutView.as_view('logout')) core.add_url_rule('/profile/', view_func=UserProfileView.as_view('user-profile')) core.add_url_rule('/set_password/', view_func=UserPasswordView.as_view('set-password')) +core.add_url_rule( + '/set_sanitization/', view_func=SanitizationEntityView.as_view('set-sanitization') +) diff --git a/ereuse_devicehub/workbench/__init__.py b/ereuse_devicehub/workbench/__init__.py index 70ac8a85..ea44bae3 100644 --- a/ereuse_devicehub/workbench/__init__.py +++ b/ereuse_devicehub/workbench/__init__.py @@ -8,7 +8,7 @@ isos = { 'url': 'https://releases.usody.com/2022/', }, "erease": { - 'iso': "USODY_14.0.0.iso", + 'iso': "USODY_14.2.0.iso", 'url': 'https://releases.usody.com/v14/', }, } diff --git a/tests/test_action.py b/tests/test_action.py index 9ccb07a3..01ca8ca6 100644 --- a/tests/test_action.py +++ b/tests/test_action.py @@ -2941,7 +2941,7 @@ def test_delete_devices_check_sync(user: UserClient): in [y.device.id for y in x.actions if hasattr(y, 'device')] ] ) - == 2 + == 0 ) diff --git a/tests/test_basic.py b/tests/test_basic.py index 7322e77b..e9927a0e 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -71,6 +71,7 @@ def test_api_docs(client: Client): '/inventory/lot/{lot_id}/transfer/', '/inventory/lot/transfer/{type_id}/', '/inventory/lot/{lot_id}/upload-snapshot/', + '/inventory/lot/{lot_id}/customerdetails/', '/inventory/snapshots/{snapshot_uuid}/', '/inventory/snapshots/', '/inventory/tag/devices/{dhid}/add/', @@ -98,6 +99,7 @@ def test_api_docs(client: Client): '/metrics/', '/profile/', '/set_password/', + '/set_sanitization/', '/tags/', '/tags/{tag_id}/device/{device_id}', '/trade-documents/', diff --git a/tests/test_device.py b/tests/test_device.py index c4e961bc..0d67c325 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -417,7 +417,6 @@ def test_get_device_permissions( html, _ = client.get(res=d.Device, item=s['device']['devicehubID'], accept=ANY) assert 'intel atom cpu n270 @ 1.60ghz' in html - assert '00:24:8C:7F:CF:2D – 100 Mbps' in html pc2, res2 = user2.get(res=d.Device, item=s['device']['devicehubID'], accept=ANY) assert res2.status_code == 200 assert pc2 == html @@ -549,7 +548,6 @@ def test_device_public(user: UserClient, client: Client): s, _ = user.post(file('asus-eee-1000h.snapshot.11'), res=m.Snapshot) html, _ = client.get(res=d.Device, item=s['device']['devicehubID'], accept=ANY) assert 'intel atom cpu n270 @ 1.60ghz' in html - assert '00:24:8C:7F:CF:2D – 100 Mbps' in html @pytest.mark.mvp diff --git a/tests/test_render_2_0.py b/tests/test_render_2_0.py index ac8c6887..c84ad143 100644 --- a/tests/test_render_2_0.py +++ b/tests/test_render_2_0.py @@ -320,7 +320,7 @@ def test_export_certificates(user3: UserClientFlask): body = str(next(body)) assert status == '200 OK' assert "PDF-1.5" in body - assert 'hts54322' in body + assert 'e2024242cv86mm'.upper() in body @pytest.mark.mvp @@ -2718,7 +2718,7 @@ def test_unreliable_device(user3: UserClientFlask): snapshots = Snapshot.query.all() assert snapshot2 not in snapshots assert snapshots[0].device != snapshots[1].device - assert len(snapshots[0].device.components) == 4 + assert len(snapshots[0].device.components) == 8 assert len(snapshots[1].device.components) == 9 assert len(snapshots[0].device.actions) == 11 assert len(snapshots[1].device.actions) == 10 @@ -2772,5 +2772,5 @@ def test_reliable_device(user3: UserClientFlask): assert Device.query.filter_by(hid=snapshot.device.hid).count() == 2 assert Snapshot.query.count() == 1 assert Snapshot.query.first() == snapshot - assert len(snapshot.device.components) == 4 - assert len(snapshot.device.actions) == 4 + assert len(snapshot.device.components) == 8 + assert len(snapshot.device.actions) == 7 diff --git a/tests/test_snapshot.py b/tests/test_snapshot.py index 9b4eca8e..6501a01c 100644 --- a/tests/test_snapshot.py +++ b/tests/test_snapshot.py @@ -158,12 +158,6 @@ def test_snapshot_update_timefield_updated(user: UserClient): perform_second_snapshot=False, ) computer2 = yaml2json('2-second-device-with-components-of-first.snapshot') - snapshot_and_check( - user, - computer2, - action_types=('Remove',), - perform_second_snapshot=False, - ) pc1_devicehub_id = snapshot['device']['devicehubID'] pc1, _ = user.get(res=m.Device, item=pc1_devicehub_id) assert pc1['updated'] != snapshot['device']['updated'] @@ -264,30 +258,25 @@ def test_snapshot_component_add_remove(user: UserClient): pc1, _ = user.get(res=m.Device, item=pc1_devicehub_id) pc2, _ = user.get(res=m.Device, item=pc2_devicehub_id) # Check if the update_timestamp is updated - update1_pc2 = pc2['updated'] - update2_pc1 = pc1['updated'] - assert update1_pc1 != update2_pc1 # PC1 - assert tuple(c['serialNumber'] for c in pc1['components']) == ('p1c1s', 'p1c3s') + assert tuple(c['serialNumber'] for c in pc1['components']) == ( + 'p1c1s', + 'p1c2s', + 'p1c3s', + ) assert all(c['parent'] == pc1_id for c in pc1['components']) assert tuple(e['type'] for e in pc1['actions']) == ( 'BenchmarkProcessor', 'Snapshot', - 'Remove', ) # PC2 - assert tuple(c['serialNumber'] for c in pc2['components']) == ('p1c2s', 'p2c1s') + assert tuple(c['serialNumber'] for c in pc2['components']) == ('p2c1s', 'p1c2s') assert all(c['parent'] == pc2_id for c in pc2['components']) assert tuple(e['type'] for e in pc2['actions']) == ('Snapshot',) # p1c2s has two Snapshots, a Remove and an Add - p1c2s_dev = m.Device.query.filter_by(id=pc2['components'][0]['id']).one() + p1c2s_dev = m.Device.query.filter_by(id=pc2['components'][1]['id']).one() p1c2s, _ = user.get(res=m.Device, item=p1c2s_dev.devicehub_id) - assert tuple(e['type'] for e in p1c2s['actions']) == ( - 'BenchmarkProcessor', - 'Snapshot', - 'Snapshot', - 'Remove', - ) + assert tuple(e['type'] for e in p1c2s['actions']) == ('Snapshot',) # We register the first device again, but removing motherboard # and moving processor from the second device to the first. @@ -296,42 +285,29 @@ def test_snapshot_component_add_remove(user: UserClient): s3 = yaml2json( '3-first-device-but-removing-motherboard-and-adding-processor-from-2.snapshot' ) - snapshot_and_check(user, s3, ('Remove',), perform_second_snapshot=False) pc1, _ = user.get(res=m.Device, item=pc1_devicehub_id) pc2, _ = user.get(res=m.Device, item=pc2_devicehub_id) # Check if the update_timestamp is updated update2_pc2 = pc2['updated'] update3_pc1 = pc1['updated'] - assert not update3_pc1 in [update1_pc1, update2_pc1] - assert update1_pc2 != update2_pc2 # PC1 - assert {c['serialNumber'] for c in pc1['components']} == {'p1c2s', 'p1c3s'} - assert all(c['parent'] == pc1_id for c in pc1['components']) + assert {c['serialNumber'] for c in pc1['components']} == {'p1c1s', 'p1c3s', 'p1c2s'} + assert all(c['parent'] == pc1['id'] for c in pc1['components']) assert tuple(get_actions_info(pc1['actions'])) == ( # id, type, components, snapshot ('BenchmarkProcessor', []), # first BenchmarkProcessor - ('Snapshot', ['p1c1s', 'p1c2s', 'p1c3s', 'p1c2s']), # first Snapshot1 - ('Remove', ['p1c2s', 'p1c2s']), # Remove Processor in Snapshot2 - ('Snapshot', ['p1c2s', 'p1c3s']), # This Snapshot3 + ('Snapshot', ['p1c1s', 'p1c2s', 'p1c3s']), # first Snapshot1 ) # PC2 - assert tuple(c['serialNumber'] for c in pc2['components']) == ('p2c1s',) - assert all(c['parent'] == pc2_id for c in pc2['components']) - assert tuple(e['type'] for e in pc2['actions']) == ( - 'Snapshot', # Second Snapshot - 'Remove', # the processor we added in 2. - ) + assert tuple(c['serialNumber'] for c in pc2['components']) == ('p2c1s', 'p1c2s') + assert all(c['parent'] == pc2['id'] for c in pc2['components']) + assert tuple(e['type'] for e in pc2['actions']) == ('Snapshot',) # Second Snapshot # p1c2s has Snapshot, Remove and Add p1c2s_dev = m.Device.query.filter_by(id=pc1['components'][0]['id']).one() p1c2s, _ = user.get(res=m.Device, item=p1c2s_dev.devicehub_id) assert tuple(get_actions_info(p1c2s['actions'])) == ( - ('BenchmarkProcessor', []), # first BenchmarkProcessor - ('Snapshot', ['p1c1s', 'p1c2s', 'p1c3s', 'p1c2s']), # First Snapshot to PC1 - ('Snapshot', ['p1c2s', 'p2c1s']), # Second Snapshot to PC2 - ('Remove', ['p1c2s', 'p1c2s']), # ...which caused p1c2s to be removed form PC1 - ('Snapshot', ['p1c2s', 'p1c3s']), # The third Snapshot to PC1 - ('Remove', ['p1c2s']), # ...which caused p1c2 to be removed from PC2 + ('Snapshot', ['p1c1s', 'p1c2s', 'p1c3s']), # First Snapshot to PC1 ) # We register the first device but without the processor, @@ -344,16 +320,15 @@ def test_snapshot_component_add_remove(user: UserClient): # Check if the update_timestamp is updated update3_pc2 = pc2['updated'] update4_pc1 = pc1['updated'] - assert update4_pc1 in [update1_pc1, update2_pc1, update3_pc1] assert update3_pc2 == update2_pc2 # PC 0: p1c3s, p1c4s. PC1: p2c1s - assert {c['serialNumber'] for c in pc1['components']} == {'p1c2s', 'p1c3s'} - assert all(c['parent'] == pc1_id for c in pc1['components']) + assert {c['serialNumber'] for c in pc1['components']} == {'p1c1s', 'p1c2s', 'p1c3s'} + assert all(c['parent'] == pc1['id'] for c in pc1['components']) # This last Action only # PC2 # We haven't changed PC2 - assert tuple(c['serialNumber'] for c in pc2['components']) == ('p2c1s',) - assert all(c['parent'] == pc2_id for c in pc2['components']) + assert tuple(c['serialNumber'] for c in pc2['components']) == ('p2c1s', 'p1c2s') + assert all(c['parent'] == pc2['id'] for c in pc2['components']) @pytest.mark.mvp @@ -454,7 +429,7 @@ def test_ram_remove(user: UserClient): dev1 = m.Device.query.filter_by(id=snap1['device']['id']).one() dev2 = m.Device.query.filter_by(id=snap2['device']['id']).one() - assert len(dev1.components) == 1 + assert len(dev1.components) == 2 assert len(dev2.components) == 3 ssd = [x for x in dev2.components if x.t == 'SolidStateDrive'][0] remove = [x for x in ssd.actions if x.t == 'Remove'][0] @@ -685,7 +660,7 @@ def test_erase_changing_hdd_between_pcs(user: UserClient): db.session.commit() assert dev2.components[2].parent == dev2 - assert dev2.components[2].actions[-1].device == dev1 + assert dev2.components[2].actions[-1].device == dev2.components[2] doc1, response = user.get( res=documents.DocumentDef.t, item='erasures/{}'.format(dev1.id), accept=ANY ) @@ -1343,6 +1318,7 @@ def test_placeholder(user: UserClient): bodyLite, res = user.post(snapshot_lite, uri="/api/inventory/") assert res.status_code == 201 dev = m.Device.query.filter_by(devicehub_id=bodyLite['dhid']).one() + dev = dev.placeholder.binding assert dev.placeholder is None assert dev.binding.phid == '12' assert len(dev.binding.device.components) == 11 @@ -1380,6 +1356,7 @@ def test_system_uuid_motherboard(user: UserClient): if c['type'] == 'Motherboard': c['serialNumber'] = 'ABee0123456720' + s['uuid'] = str(uuid.uuid4()) snap2, _ = user.post(s, res=Snapshot, status=422) txt = "We have detected that a there is a device in your inventory" assert txt in snap2['message'][0] @@ -1407,7 +1384,7 @@ def test_bug_4028_components(user: UserClient): assert '' not in [c.phid() for c in components1] assert '' not in [c.phid() for c in components2] assert len(components1) == len(components2) - assert m.Placeholder.query.count() == 15 + assert m.Placeholder.query.count() == 19 assert m.Placeholder.query.count() * 2 == m.Device.query.count() for c in m.Placeholder.query.filter(): assert c.binding