merge testing

This commit is contained in:
Cayo Puigdefabregas 2023-03-03 17:45:41 +01:00
commit 327e5f20cb
29 changed files with 1135 additions and 861 deletions

View File

@ -230,7 +230,7 @@ class Dummy:
user1.get(res=Device, item=sample_pc_devicehub_id) # Test user1.get(res=Device, item=sample_pc_devicehub_id) # Test
anonymous = self.app.test_client() anonymous = self.app.test_client()
html, _ = anonymous.get(res=Device, item=sample_pc_devicehub_id, accept=ANY) 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 # For netbook: to preapre -> torepair -> to dispose -> disposed
print('⭐ Done.') print('⭐ Done.')

View File

@ -1,11 +1,19 @@
from boltons.urlutils import URL
from flask import current_app as app from flask import current_app as app
from flask import g, session from flask import g, session
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from werkzeug.security import generate_password_hash 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.db import db
from ereuse_devicehub.resources.user.models import User from ereuse_devicehub.resources.user.models import SanitizationEntity, User
class LoginForm(FlaskForm): class LoginForm(FlaskForm):
@ -107,7 +115,7 @@ class PasswordForm(FlaskForm):
g.user.reset_dlt_keys(self.newpassword.data, keys_dlt) g.user.reset_dlt_keys(self.newpassword.data, keys_dlt)
token_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 session['token_dlt'] = token_dlt
@ -117,3 +125,48 @@ class PasswordForm(FlaskForm):
if commit: if commit:
db.session.commit() db.session.commit()
return 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

View File

@ -30,7 +30,12 @@ from wtforms import (
from wtforms.fields import FormField from wtforms.fields import FormField
from ereuse_devicehub.db import db 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.models import PlaceholdersLog, SnapshotsLog
from ereuse_devicehub.parser.parser import ParseSnapshotLsHw from ereuse_devicehub.parser.parser import ParseSnapshotLsHw
from ereuse_devicehub.parser.schemas import Snapshot_lite 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 ( from ereuse_devicehub.resources.device.models import (
SAI, SAI,
Cellphone, Cellphone,
Computer,
ComputerMonitor, ComputerMonitor,
Desktop, Desktop,
Device, Device,
@ -297,7 +303,7 @@ class UploadSnapshotForm(SnapshotMixin, FlaskForm):
return is_lite 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()]): if any([x == 'Error' for x in self.result.values()]):
return return
schema = SnapshotSchema() schema = SnapshotSchema()
@ -333,6 +339,8 @@ class UploadSnapshotForm(SnapshotMixin, FlaskForm):
self.result[filename] = 'Error' self.result[filename] = 'Error'
continue continue
if isinstance(response.device, Computer):
response.device.user_trusts = user_trusts
db.session.add(response) db.session.add(response)
devices.append(response.device.binding.device) devices.append(response.device.binding.device)
@ -1516,6 +1524,54 @@ class NotesForm(FlaskForm):
return self._obj 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): class UploadPlaceholderForm(FlaskForm):
type = StringField('Type', [validators.DataRequired()]) type = StringField('Type', [validators.DataRequired()])
placeholder_file = FileField( placeholder_file = FileField(

View File

@ -5,7 +5,7 @@ from flask import g
from sqlalchemy import Column, Integer from sqlalchemy import Column, Integer
from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import backref, relationship 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.db import db
from ereuse_devicehub.resources.models import Thing 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), backref=backref('receiver_note', lazy=True, uselist=False, cascade=CASCADE_OWN),
primaryjoin='ReceiverNote.transfer_id == Transfer.id', 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',
)

View File

@ -1,7 +1,9 @@
import copy import copy
import csv import csv
import datetime
import logging import logging
import os import os
import uuid
from io import StringIO from io import StringIO
from pathlib import Path from pathlib import Path
@ -20,6 +22,7 @@ from ereuse_devicehub.inventory.forms import (
AdvancedSearchForm, AdvancedSearchForm,
AllocateForm, AllocateForm,
BindingForm, BindingForm,
CustomerDetailsForm,
DataWipeForm, DataWipeForm,
EditTransferForm, EditTransferForm,
FilterForm, FilterForm,
@ -79,6 +82,7 @@ class DeviceListMixin(GenericMixin):
form_transfer = '' form_transfer = ''
form_delivery = '' form_delivery = ''
form_receiver = '' form_receiver = ''
form_customer_details = ''
if lot_id: if lot_id:
lot = lots.filter(Lot.id == lot_id).one() lot = lots.filter(Lot.id == lot_id).one()
@ -86,6 +90,7 @@ class DeviceListMixin(GenericMixin):
form_transfer = EditTransferForm(lot_id=lot.id) form_transfer = EditTransferForm(lot_id=lot.id)
form_delivery = NotesForm(lot_id=lot.id, type='Delivery') form_delivery = NotesForm(lot_id=lot.id, type='Delivery')
form_receiver = NotesForm(lot_id=lot.id, type='Receiver') form_receiver = NotesForm(lot_id=lot.id, type='Receiver')
form_customer_details = CustomerDetailsForm(lot_id=lot.id)
form_new_action = NewActionForm(lot=lot_id) form_new_action = NewActionForm(lot=lot_id)
self.context.update( self.context.update(
@ -97,6 +102,7 @@ class DeviceListMixin(GenericMixin):
'form_transfer': form_transfer, 'form_transfer': form_transfer,
'form_delivery': form_delivery, 'form_delivery': form_delivery,
'form_receiver': form_receiver, 'form_receiver': form_receiver,
'form_customer_details': form_customer_details,
'form_filter': form_filter, 'form_filter': form_filter,
'form_print_labels': PrintLabelsForm(), 'form_print_labels': PrintLabelsForm(),
'lot': lot, 'lot': lot,
@ -1039,7 +1045,7 @@ class ExportsView(View):
return self.response_csv(data, "Erasures.csv") return self.response_csv(data, "Erasures.csv")
def build_erasure_certificate(self): def get_datastorages(self):
erasures = [] erasures = []
for device in self.find_devices(): for device in self.find_devices():
if device.placeholder and device.placeholder.binding: if device.placeholder and device.placeholder.binding:
@ -1050,11 +1056,66 @@ class ExportsView(View):
elif isinstance(device, DataStorage): elif isinstance(device, DataStorage):
if device.privacy: if device.privacy:
erasures.append(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 = { params = {
'title': 'Erasure Certificate', 'title': 'Erasure Certificate',
'erasures': tuple(erasures), 'erasures': tuple(erasures),
'url_pdf': '', '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) 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): class DeliveryNoteView(GenericMixin):
methods = ['POST'] methods = ['POST']
form_class = NotesForm form_class = NotesForm
@ -1448,6 +1531,10 @@ devices.add_url_rule(
'/lot/<string:lot_id>/transfer/', '/lot/<string:lot_id>/transfer/',
view_func=EditTransferView.as_view('edit_transfer'), view_func=EditTransferView.as_view('edit_transfer'),
) )
devices.add_url_rule(
'/lot/<string:lot_id>/customerdetails/',
view_func=CustomerDetailsView.as_view('customer_details'),
)
devices.add_url_rule( devices.add_url_rule(
'/lot/<string:lot_id>/deliverynote/', '/lot/<string:lot_id>/deliverynote/',
view_func=DeliveryNoteView.as_view('delivery_note'), view_func=DeliveryNoteView.as_view('delivery_note'),

View File

@ -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()}')

View File

@ -527,6 +527,9 @@ class EraseBasic(JoinedWithOneDeviceMixin, ActionWithOneDevice):
proof = Proof(**d) proof = Proof(**d)
db.session.add(proof) db.session.add(proof)
def get_public_name(self):
return "Basic"
def __str__(self) -> str: def __str__(self) -> str:
return '{} on {}.'.format(self.severity, self.date_str) return '{} on {}.'.format(self.severity, self.date_str)
@ -556,12 +559,32 @@ class EraseSectors(EraseBasic):
method = 'Badblocks' 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): class ErasePhysical(EraseBasic):
"""The act of physically destroying a data storage unit.""" """The act of physically destroying a data storage unit."""
method = Column(DBEnum(PhysicalErasureMethod)) method = Column(DBEnum(PhysicalErasureMethod))
def get_public_name(self):
return "Physical"
class Step(db.Model): class Step(db.Model):
erasure_id = Column( erasure_id = Column(

View File

@ -758,6 +758,24 @@ class Device(Thing):
return "" 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): def get_from_db(self):
if 'property_hid' in app.blueprints.keys(): if 'property_hid' in app.blueprints.keys():
try: try:
@ -939,7 +957,7 @@ class Device(Thing):
if not snapshot1: if not snapshot1:
return return
self.create_new_device(snapshots.values()) self.create_new_device(snapshots.values(), user_trusts=self.user_trusts)
self.remove_snapshot(snapshots.keys()) self.remove_snapshot(snapshots.keys())
return return
@ -956,7 +974,7 @@ class Device(Thing):
snapshot = file_snapshot.read() snapshot = file_snapshot.read()
return json.loads(snapshot) 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 from ereuse_devicehub.inventory.forms import UploadSnapshotForm
new_snapshots = [] new_snapshots = []
@ -969,7 +987,7 @@ class Device(Thing):
form.result = {} form.result = {}
form.snapshots = new_snapshots form.snapshots = new_snapshots
form.create_new_devices = True form.create_new_devices = True
form.save(commit=False) form.save(commit=False, user_trusts=user_trusts)
def remove_snapshot(self, snapshots): def remove_snapshot(self, snapshots):
from ereuse_devicehub.parser.models import SnapshotsLog from ereuse_devicehub.parser.models import SnapshotsLog

View File

@ -16,19 +16,20 @@ from ereuse_devicehub.resources.action.models import Remove
from ereuse_devicehub.resources.device.models import ( from ereuse_devicehub.resources.device.models import (
Component, Component,
Computer, Computer,
DataStorage,
Device, Device,
Placeholder, Placeholder,
) )
from ereuse_devicehub.resources.tag.model import Tag from ereuse_devicehub.resources.tag.model import Tag
DEVICES_ALLOW_DUPLICITY = [ # DEVICES_ALLOW_DUPLICITY = [
'RamModule', # 'RamModule',
'Display', # 'Display',
'SoundCard', # 'SoundCard',
'Battery', # 'Battery',
'Camera', # 'Camera',
'GraphicCard', # 'GraphicCard',
] # ]
class Sync: class Sync:
@ -119,7 +120,7 @@ class Sync:
""" """
assert inspect(component).transient, 'Component should not be synced from DB' assert inspect(component).transient, 'Component should not be synced from DB'
# if not is a DataStorage, then need build a new one # 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) db.session.add(component)
is_new = True is_new = True
return component, is_new return component, is_new
@ -171,6 +172,8 @@ class Sync:
if not db_device or create_new_device: if not db_device or create_new_device:
device.tags.clear() # We don't want to add the transient dummy tags 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.session.add(device)
db_device = device db_device = device
try: try:

View File

@ -1,400 +1,220 @@
{% import 'devices/macros.html' as macros %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta content="width=device-width, initial-scale=1.0" name="viewport">
<link href="https://stackpath.bootstrapcdn.com/bootswatch/3.3.7/flatly/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-+ENW/yibaokMnme+vBLnHMphUYxHs34h9lpdbSLuAwGkOKFRl4C34WkjazBtb7eT"
crossorigin="anonymous">
<script src="https://use.fontawesome.com/7553aecc27.js"></script>
<title>Devicehub | {{ device.__format__('t') }}</title>
<style>
/*Sticky footer*/
html {
position: relative;
min-height: 100%;
}
body { <title>Device {{ device_real.dhid }} - Usody</title>
margin-bottom: 60px; /* Margin bottom by footer height */ <meta content="" name="description">
} <meta content="" name="keywords">
.footer { <!-- Favicons -->
position: absolute; <link href="{{ url_for('static', filename='img/favicon.png') }}" rel="icon">
bottom: 0; <link href="{{ url_for('static', filename='img/apple-touch-icon.png') }}" rel="apple-touch-icon">
width: 100%;
height: 6em; <!-- Google Fonts -->
} <link href="https://fonts.gstatic.com" rel="preconnect">
</style> <link href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i|Nunito:300,300i,400,400i,600,600i,700,700i|Poppins:300,300i,400,400i,500,500i,600,600i,700,700i" rel="stylesheet">
<!-- JS Files -->
<script src="{{ url_for('static', filename='js/jquery-3.6.0.min.js') }}"></script>
<!-- Vendor CSS Files -->
<link href="{{ url_for('static', filename='vendor/bootstrap/css/bootstrap.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='vendor/bootstrap-icons/bootstrap-icons.css') }}" rel="stylesheet">
<!-- Template Main CSS File -->
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/devicehub.css') }}" rel="stylesheet">
<!-- =======================================================
* Template Name: NiceAdmin - v2.2.0
* Template URL: https://bootstrapmade.com/nice-admin-bootstrap-admin-html-template/
* Author: BootstrapMade.com
* License: https://bootstrapmade.com/license/
======================================================== -->
</head> </head>
<body> <body>
<nav class="navbar navbar-default" style="background-color: gainsboro; margin: 0 !important"> <main>
<div class="container-fluid">
<a href="https://www.usody.com/" target="_blank">
<h1 align="center">Usody Public Link</h1>
</a>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<div class="page-header col-md-6 col-md-offset-3">
<h1>{% if abstract %}Real device {% endif %}{{ device.__format__('t') or '' }}<br>
<small>{{ device.__format__('s') or '' }}</small>
</h1>
</div>
</div>
<div class="row">
<div class="col-md-3">
</div>
<div class="col-md-6">
<ul>
{% for key, value in device.public_properties.items() %}
<li>{{ key }}: {{ value or '' }}</li>
{% endfor %}
</ul>
{% if isinstance(device, d.Computer) %}
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th></th>
<th>Range</th>
</tr>
</thead>
<tbody>
{% if device.processor_model %}
<tr>
<td>
CPU {{ device.processor_model }}
</td>
<td>
Processor Rate = {% if device.rate %}
{{ device.rate.processor_range }}
({{ device.rate.processor }})
{% endif %}
</td>
</tr>
{% endif %}
{% if device.ram_size %}
<tr>
<td>
RAM {{ device.ram_size // 1000 }} GB
{{ macros.component_type(device.components, 'RamModule') }}
</td>
<td>
RAM Rate = {% if device.rate %}
{{ device.rate.ram_range }}
({{ device.rate.ram }})
{% endif %}
</td>
</tr>
{% endif %}
{% if device.data_storage_size %}
<tr>
<td>
Data Storage {{ device.data_storage_size // 1000 }} GB
{{ macros.component_type(device.components, 'SolidStateDrive') }}
{{ macros.component_type(device.components, 'HardDrive') }}
</td>
<td>
Data Storage Rate = {% if device.rate %}
{{ device.rate.data_storage_range }}
({{ device.rate.data_storage }})
{% endif %}
</td>
</tr>
{% endif %}
{% if device.graphic_card_model %}
<tr>
<td>
Graphics {{ device.graphic_card_model }}
{{ macros.component_type(device.components, 'GraphicCard') }}
</td>
<td></td>
</tr>
{% endif %}
{% if device.network_speeds %}
<tr>
<td>
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') }}
</td>
<td></td>
</tr>
{% endif %}
</tbody>
</table>
</div>
<h4>Actual Status</h4>
<ol>
<li>
<strong>
Lifecycle Status
</strong>
{% if device.status %}
{{ device.status.type }}
{% endif %}
</li>
<li>
<strong>
Allocate Status
</strong>
{% if device.allocated_status %}
{{ device.allocated_status.type }}
{% endif %}
</li>
<li>
<strong>
Physical Status
</strong>
{% if device.physical_status %}
{{ device.physical_status.type }}
{% endif %}
</li>
</ol>
<h4>Public traceability log of the device</h4> <section class="container mt-3">
<div class="text-right"> <div class="row">
<small>Latest one.</small>
</div>
<ol>
{% for action in device.public_actions %}
<li>
<strong>
{{ device.is_status(action) }}
{% if not device.is_status(action) %}
{{ action.type }}
{% endif %}
</strong>
{% if device.is_status(action) %}
{{ action }} {{ action.type }}
{% else %}
{{ action }}
{% endif %}
<br>
<div class="text-muted">
<small>
{{ action._date_str }}
</small>
</div>
{% if action.certificate %}
<a href="{{ action.certificate.to_text() }}">See the certificate</a>
{% endif %}
</li>
{% endfor %}
</ol>
<div class="text-right">
<small>Oldest one.</small>
</div>
{% endif %}
</div>
</div>
{% if abstract %}
<div class="row">
<div class="page-header col-md-6 col-md-offset-3">
<hr />
<h1>Abstract device {{ abstract.__format__('t') or '' }}<br>
<small>{{ abstract.__format__('s') or '' }}</small>
</h1>
</div>
</div>
<div class="row">
<div class="col-md-3">
</div>
<div class="col-md-6">
<ul>
{% for key, value in abstract.public_properties.items() %}
<li>{{ key }}: {{ value or '' }}</li>
{% endfor %}
</ul>
{% if isinstance(abstract, d.Computer) %}
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th></th>
<th>Range</th>
</tr>
</thead>
<tbody>
{% if abstract.processor_model %}
<tr>
<td>
CPU {{ abstract.processor_model }}
</td>
<td>
Processor Rate = {% if abstract.rate %}
{{ abstract.rate.processor_range }}
({{ abstract.rate.processor }})
{% endif %}
</td>
</tr>
{% endif %}
{% if abstract.ram_size %}
<tr>
<td>
RAM {{ abstract.ram_size // 1000 }} GB
{{ macros.component_type(abstract.components, 'RamModule') }}
</td>
<td>
RAM Rate = {% if abstract.rate %}
{{ abstract.rate.ram_range }}
({{ abstract.rate.ram }})
{% endif %}
</td>
</tr>
{% endif %}
{% if abstract.data_storage_size %}
<tr>
<td>
Data Storage {{ abstract.data_storage_size // 1000 }} GB
{{ macros.component_type(abstract.components, 'SolidStateDrive') }}
{{ macros.component_type(abstract.components, 'HardDrive') }}
</td>
<td>
Data Storage Rate = {% if abstract.rate %}
{{ abstract.rate.data_storage_range }}
({{ abstract.rate.data_storage }})
{% endif %}
</td>
</tr>
{% endif %}
{% if abstract.graphic_card_model %}
<tr>
<td>
Graphics {{ abstract.graphic_card_model }}
{{ macros.component_type(abstract.components, 'GraphicCard') }}
</td>
<td></td>
</tr>
{% endif %}
{% if abstract.network_speeds %}
<tr>
<td>
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') }}
</td>
<td></td>
</tr>
{% endif %}
</tbody>
</table>
</div>
<h4>Actual Status</h4>
<ol>
<li>
<strong>
Lifecycle Status
</strong>
{% if abstract.status %}
{{ abstract.status.type }}
{% endif %}
</li>
<li>
<strong>
Allocate Status
</strong>
{% if abstract.allocated_status %}
{{ abstract.allocated_status.type }}
{% endif %}
</li>
<li>
<strong>
Physical Status
</strong>
{% if abstract.physical_status %}
{{ abstract.physical_status.type }}
{% endif %}
</li>
</ol>
<h4>Public traceability log of the abstract</h4> <div class="col">
<div class="text-right"> <div class="col-xl-12">
<small>Latest one.</small>
<div class="card">
<div class="card-body">
<h3 class="nav-link mt-5" style="color: #993365">{{ device_real.type }} - {{ device_real.verbose_name }}</h3>
<div class="row">
<div class="col-6">
<h5 class="card-title">Basic</h5>
<div class="row">
<div class="col">
Usody Identifier (DHID)
</div>
<div class="col">
{{ device_real.dhid }}
</div>
</div>
<div class="row">
<div class="col">
Inventory Identifier (PHID)
</div>
<div class="col">
{{ device_real.phid() }}
</div>
</div>
<div class="row">
<div class="col">
Type
</div>
<div class="col">
{{ device_real.type or '- not detected -' }}
</div>
</div>
<div class="row">
<div class="col">
Manufacturer
</div>
<div class="col">
{{ device_real.manufacturer or '- not detected -' }}
</div>
</div>
<div class="row">
<div class="col">
Model
</div>
<div class="col">
{{ device_real.model or '- not detected -' }}
</div>
</div>
<div class="row">
<div class="col">
Part Number
</div>
<div class="col">
{{ device_real.part_number or '- not detected -' }}
</div>
</div>
<div class="row">
<div class="col">
Serial Number
</div>
<div class="col">
- anonymized -
</div>
</div>
</div> </div>
<ol> <div class="col-1">
{% for action in abstract.public_actions %}
<li>
<strong>
{{ abstract.is_status(action) }}
{% if not abstract.is_status(action) %}
{{ action.type }}
{% endif %}
</strong>
{% if abstract.is_status(action) %}
{{ action }} {{ action.type }}
{% else %}
{{ action }}
{% endif %}
<br>
<div class="text-muted">
<small>
{{ action._date_str }}
</small>
</div>
{% if action.certificate %}
<a href="{{ action.certificate.to_text() }}">See the certificate</a>
{% endif %}
</li>
{% endfor %}
</ol>
<div class="text-right">
<small>Oldest one.</small>
</div> </div>
{% endif %} <div class="col-5">
<h5 class="card-title">Status</h5>
<div class="row">
<div class="col">
<div class="label"><b>Physical</b></div>
<div>{{ device_real.physical_status and device.physical_status.type or '- not status -' }}</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="label"><b>Lifecycle</b></div>
<div>{{ device_real.status and device_real.status.type or '- not status -' }}</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="label"><b>Allocation</b></div>
<div>
{% if device_real.allocated %}
Allocated
{% else %}
Not allocated
{% endif %}
</div>
</div>
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-6">
<h5 class="card-title">Components</h5>
<div class="row">
{% if placeholder.binding %}
<div class="list-group col">
{% for component in placeholder.binding.components|sort(attribute='type') %}
<div class="list-group-item">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">{{ component.type }}</h5>
<small class="text-muted">{{ component.created.strftime('%H:%M %d-%m-%Y') }}</small>
</div>
<p class="mb-1">
{{ component.manufacturer or '- not detected -' }}<br />
{{ component.model or '- not detected -' }}<br />
</p>
<small class="text-muted">
{% if component.type in ['RamModule', 'HardDrive', 'SolidStateDrive'] %}
{{ component.size }}MB
{% endif %}
</small>
</div>
{% endfor %}
</div>
{% else %}
<div class="list-group col">
<div class="list-group-item">
- not detected -
</div>
</div>
{% endif %}
</div>
</div>
<div class="col-6">
<h5 class="card-title">Repair history</h5>
<div class="row">
<div class="list-group col">
{% for action in placeholder.actions %}
<div class="list-group-item d-flex justify-content-between align-items-center">
{{ action.type }} {{ action.severity }}
<small class="text-muted">{{ action.created.strftime('%H:%M %d-%m-%Y') }}</small>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
</div> </div>
</div>
</div>
</section>
</main>
<!-- ======= Footer ======= -->
<div class="container">
<div class="row">
<div class="col">
<footer class="footer">
<div class="copyright">
&copy; Copyright <strong><span>Usody</span></strong>. All Rights Reserved
</div>
<div class="credits">
<a href="https://help.usody.com/en/" target="_blank">Help</a> |
<a href="https://www.usody.com/legal/privacy-policy" target="_blank">Privacy</a> |
<a href="https://www.usody.com/legal/terms" target="_blank">Terms</a>
</div>
<div class="credits">
DeviceHub
</div>
</footer><!-- End Footer -->
</div> </div>
{% endif %} </div>
</div> </div>
<footer class="container-fluid footer">
<div class="row">
<div class="col-md-4">
Page generated by:<br>
<img style="height: 9em"
src="{{ url_for('Device.static', filename='usody-logo-v4.png') }}">
</div>
</div>
</footer>
</body> </body>
</html> </html>

View File

@ -148,8 +148,17 @@ class DeviceView(View):
if device.is_abstract() == 'Twin': if device.is_abstract() == 'Twin':
abstract = device.placeholder.binding 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( 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 @auth.Auth.requires_auth

View File

@ -334,6 +334,12 @@ class Severity(IntEnum):
def __format__(self, format_spec): def __format__(self, format_spec):
return str(self) return str(self)
def get_public_name(self):
if self.value == 3:
return "Failed"
return "Success"
class PhysicalErasureMethod(Enum): class PhysicalErasureMethod(Enum):
"""Methods of physically erasing the data-storage, usually """Methods of physically erasing the data-storage, usually

View File

@ -8,7 +8,7 @@ from flask_login import UserMixin
from sqlalchemy import BigInteger, Boolean, Column, Sequence from sqlalchemy import BigInteger, Boolean, Column, Sequence
from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy_utils import EmailType, PasswordType 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.db import db
from ereuse_devicehub.resources.enums import SessionType from ereuse_devicehub.resources.enums import SessionType
@ -178,3 +178,22 @@ class Session(Thing):
def __str__(self) -> str: def __str__(self) -> str:
return '{0.token}'.format(self) 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)

View File

@ -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;
}

File diff suppressed because one or more lines are too long

View File

@ -19,12 +19,14 @@
<!-- JS Files --> <!-- JS Files -->
<script src="{{ url_for('static', filename='js/jquery-3.6.0.min.js') }}"></script> <script src="{{ url_for('static', filename='js/jquery-3.6.0.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/simple-datatables-5.0.3.js') }}"></script> <script src="https://cdn.jsdelivr.net/npm/simple-datatables@5.0.3" type="text/javascript"></script>
<!-- Vendor CSS Files --> <!-- Vendor CSS Files -->
<link href="{{ url_for('static', filename='vendor/bootstrap/css/bootstrap.min.css') }}" rel="stylesheet"> <link href="{{ url_for('static', filename='vendor/bootstrap/css/bootstrap.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='vendor/bootstrap-icons/bootstrap-icons.css') }}" rel="stylesheet"> <link href="{{ url_for('static', filename='vendor/bootstrap-icons/bootstrap-icons.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/simple-datatables.css') }}" rel="stylesheet" type="text/css"> <link href="https://cdn.jsdelivr.net/npm/simple-datatables@5.0.3/dist/style.css" rel="stylesheet" type="text/css">
<!-- Template Main CSS File --> <!-- Template Main CSS File -->

View File

@ -309,8 +309,8 @@
</div> </div>
<div class="credits"> <div class="credits">
<a href="https://help.usody.com/en/" target="_blank">Help</a> | <a href="https://help.usody.com/en/" target="_blank">Help</a> |
<a href="https://pangea.org/es/politica-de-privacidad/" target="_blank">Privacy</a> | <a href="https://www.usody.com/legal/privacy-policy" target="_blank">Privacy</a> |
<a href="https://pangea.org/aviso-legal/" target="_blank">Terms</a> <a href="https://www.usody.com/legal/terms" target="_blank">Terms</a>
</div> </div>
<div class="credits"> <div class="credits">
DeviceHub {{ version }} DeviceHub {{ version }}

View File

@ -71,8 +71,8 @@
<div class="credits"> <div class="credits">
<a href="https://help.usody.com/en/getting-started/login-usody/" target="_blank">Help</a> | <a href="https://help.usody.com/en/getting-started/login-usody/" target="_blank">Help</a> |
<a href="https://pangea.org/es/politica-de-privacidad/" target="_blank">Privacy</a> | <a href="https://www.usody.com/legal/privacy-policy" target="_blank">Privacy</a> |
<a href="https://pangea.org/aviso-legal/" target="_blank">Terms</a> <a href="https://www.usody.com/legal/terms" target="_blank">Terms</a>
</div> </div>
</div> </div>

View File

@ -34,7 +34,10 @@
<!-- Bordered Tabs --> <!-- Bordered Tabs -->
<ul class="nav nav-tabs nav-tabs-bordered"> <ul class="nav nav-tabs nav-tabs-bordered">
<li class="nav-item"> <li class="nav-item">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#profile-change-password">Change Password</button> <button class="nav-link active" data-bs-toggle="tab" data-bs-target="#profile-change-password">Change Password</button>
</li>
<li class="nav-item">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#profile-sanitization-entity">Sanitization Entity</button>
</li> </li>
</ul> </ul>
<div class="tab-content pt-2"> <div class="tab-content pt-2">
@ -65,7 +68,34 @@
<button type="submit" class="btn btn-primary">Change Password</button> <button type="submit" class="btn btn-primary">Change Password</button>
</div> </div>
</form><!-- End Change Password Form --> </form><!-- End Change Password Form -->
</div>
<div class="tab-pane fade pt-3" id="profile-sanitization-entity">
<!-- Sanitization Entity datas Form -->
<form action="{{ url_for('core.set-sanitization') }}" method="post">
{% for f in sanitization_form %}
{% if f == sanitization_form.csrf_token %}
{{ f }}
{% else %}
<div class="row mb-3">
<label class="col-md-4 col-lg-3 col-form-label">{{ f.label }}</label>
<div class="col-md-8 col-lg-9">
{{ f }}
{% if f.errors %}
<p class="text-danger">
{% for error in f.errors %}
{{ error }}<br/>
{% endfor %}
</p>
{% endif %}
</div>
</div>
{% endif %}
{% endfor %}
<div class="text-center">
<button type="submit" class="btn btn-primary">Change sanitization data</button>
</div>
</form><!-- End Sanitization Entity datas Form -->
</div> </div>
</div><!-- End Bordered Tabs --> </div><!-- End Bordered Tabs -->

View File

@ -93,6 +93,11 @@
Receiver Note Receiver Note
</button> </button>
</li> </li>
<li class="nav-item">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#edit-customer-details">
Customer Details
</button>
</li>
{% endif %} {% endif %}
{% endif %} {% endif %}
@ -656,6 +661,37 @@
{% endif %} {% endif %}
</form> </form>
</div> </div>
<div id="edit-customer-details" class="tab-pane fade edit-customer-details">
<h5 class="card-title">Customer Details</h5>
<form method="post" action="{{ url_for('inventory.customer_details', lot_id=lot.id) }}" class="row g-3 needs-validation" novalidate>
{{ form_customer_details.csrf_token }}
{% for field in form_customer_details %}
{% if field != form_customer_details.csrf_token %}
<div class="col-12">
{% if field != form_customer_details.type %}
{{ field.label(class_="form-label") }}
{{ field }}
<small class="text-muted">{{ field.description }}</small>
{% if field.errors %}
<p class="text-danger">
{% for error in field.errors %}
{{ error }}<br/>
{% endfor %}
</p>
{% endif %}
{% endif %}
</div>
{% endif %}
{% endfor %}
<div>
<a href="{{ url_for('inventory.lotdevicelist', lot_id=lot.id) }}" class="btn btn-danger">Cancel</a>
<button class="btn btn-primary" type="submit">Save</button>
</div>
</form>
</div>
{% endif %} {% endif %}
</div><!-- End Bordered Tabs --> </div><!-- End Bordered Tabs -->

View File

@ -1,80 +1,398 @@
{% extends "documents/layout.html" %} <!DOCTYPE html>
{% block body %} <html>
<div> <head>
<h2>Summary</h2> <title>Data Sanitization Certificate</title>
<table class="table table-bordered"> <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
<thead> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<tr> <link href="https://stackpath.bootstrapcdn.com/bootswatch/3.3.7/flatly/bootstrap.min.css"
<th>S/N Data Storage</th> rel="stylesheet"
<th>Type of erasure</th> integrity="sha384-+ENW/yibaokMnme+vBLnHMphUYxHs34h9lpdbSLuAwGkOKFRl4C34WkjazBtb7eT"
<th>Result</th> crossorigin="anonymous">
<th>Date</th> <style type="text/css" media="all">
</tr> @page {
</thead> size: A4 portrait; /* can use also 'landscape' for orientation */
<tbody> margin: 1.0cm 1.5cm 3.5cm 1.5cm;
{% for erasure in erasures %} font-family: "Source Sans Pro", Calibri, Candra, Sans serif;
<tr>
<td>
{{ erasure.device.serial_number.upper() }}
</td>
<td>
{{ erasure.type }}
</td>
<td>
{{ erasure.severity }}
</td>
<td>
{{ erasure.date_str }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="page-break row">
<h2>Details</h2>
{% for erasure in erasures %}
<div class="col-md-6 no-page-break">
<h4>{{ erasure.device.__format__('t') }}</h4>
<dl>
<dt>Data storage:</dt>
<dd>{{ erasure.device.__format__('ts') }}</dd>
<dt>Erasure:</dt> @top {
<dd>{{ erasure.__format__('ts') }}</dd> content: element(header);
{% if erasure.steps %} }
<dt>Erasure steps:</dt>
<dd> @bottom {
<ol> content: element(footer);
{% for step in erasure.steps %} }
<li>{{ step.__format__('') }}</li>
{% endfor %} }
</ol> body {
</dd> width: 100% !important;
{% endif %} height: 100%;
</dl> background: #fff;
</div> color: black;
{% endfor %} font-size: 100%;
line-height: 1.65;
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
}
header {
position: running(header);
/*height: 100px;*/
font-size: 12px;
/* color: #000; */
font-family: Arial;
width: 100%;
/* position: relative;*/
}
footer {
position: running(footer);
/*height: 150px;*/
}
.body_content {
position: relative;
page-break-inside: auto;
width: 100%;
/*overflow: hidden;*/
}
img {max-height: 150px; width: auto;}
.company-logo {float: left;}
.customer-logo {float: right;}
.page-break:not(section:first-of-type) {
page-break-before: always
}
}
</style>
</head>
<body>
<header class="page-header">
<div class="col" style="background-color: #d5a6bd;">
<p style="margin-left: 10px;">{{ date_report }}, {{ software }}</p>
</div> </div>
<div class="no-page-break"> </header>
<h2>Glossary</h2> <div class="container">
<dl> <div class="row">
<dt>Erase Basic</dt> <div class="col-6">
<dd> <img class="company-logo" src="{{ customer_details and customer_details.logo.to_text() or '' }}" />
A software-based fast non-100%-secured way of erasing data storage,
using <a href="https://en.wikipedia.org/wiki/Shred_(Unix)">shred</a>.
</dd>
<dt>Erase Sectors</dt>
<dd>
A secured-way of erasing data storages, checking sector-by-sector
the erasure, using <a href="https://en.wikipedia.org/wiki/Badblocks">badblocks</a>.
</dd>
</dl>
</div> </div>
<div class="no-print"> <div class="col-6">
<a href="{{ url_pdf }}">Click here to download the PDF.</a> <img class="customer-logo" src="{{ my_data and my_data.logo.to_text() }}" />
</div> </div>
<div class="print-only"> </div>
<a href="{{ url_for('Document.StampsView', _external=True) }}">Verify on-line the integrity of this document</a> </div>
<div class="container body-content">
<div class="row mt-3">
<div class="col">
<h1>Data Sanitization Certificate</h1>
</div> </div>
{% endblock %} </div>
<div class="row mt-3">
<div class="col-12">
<strong>Entity Information</strong>
</div>
<div class="col-12">
<table class="body_content">
<tbody>
<tr style="padding-top:5px;">
<td style="width:20%;">
Name:
</td>
<td style="width:80%;">
<span>{{ customer_details and customer_details.company_name or ''}}</span>
</td>
</tr>
<tr style="padding-top:5px;">
<td style="width:20%;">
Location:
</td>
<td style="width:80%;">
<span>{{ customer_details and customer_details.location or '' }}</span>
</td>
</tr>
</table>
</div>
</div>
<div class="row" style="padding-top: 20px;">
<div class="col-12">
<strong>Responsible Sanitization Entity</strong>
</div>
<div class="col-12">
<table class="body_content">
<tbody>
<tr style="padding-top:5px;">
<td style="width:20%;">
<span>Name:</span>
</td>
<td style="width:80%;">
<span>{{ my_data and my_data.company_name or '' }}</span>
</td>
</tr>
<tr style="padding-top:5px;">
<td style="width:20%;">
<span>Responsible Person</span>
</td>
<td style="width:80%;">
<span>{{ my_data and my_data.responsable_person or '' }}</span>
</td>
</tr>
<tr style="padding-top:5px;">
<td style="width:20%;">
<span>Location:</span>
</td>
<td style="width:80%;">
<span>{{ my_data and my_data.location or '' }}</span>
</td>
</tr>
</table>
</div>
</div>
<div class="row" style="padding-top: 20px;">
<div class="col-12">
<strong>Summary</strong>
</div>
<div class="col-12">
<table class="body_content">
<tbody>
{% if erasure_hosts %}
{% for e in erasure_hosts %}
<tr style="padding-top:5px;">
<td style="width:20%;">
<span>N&deg; of sanitization server ({{ loop.index }}/{{ erasure_hosts|length }}):</span>
</td>
<td style="width:80%;">
{% if e.serial_number %}
<span>{{ e.serial_number.upper() }}</span>
{% endif %}
</td>
</tr>
{% endfor %}
{% else %}
<tr style="padding-top:5px;">
<td style="width:20%;">
<span>N&deg; of computers:</span>
</td>
<td style="width:80%;">
<span>{{ n_computers }}</span>
</td>
</tr>
{% endif %}
<tr style="padding-top:5px;">
<td style="width:20%;">
<span>N&deg; of data storage unit(s):</span>
</td>
<td style="width:80%;">
<span>{{ erasures | length }}</span>
</td>
</tr>
<tr style="padding-top:5px;">
<td style="width:20%;">
<span>Sanitization result:</span>
</td>
<td style="width:80%;">
<span>{{ result }}</span>
</td>
</tr>
</table>
</div>
</div>
<div class="row" style="padding-top: 20px;">
<div class="col-12">
<strong>Report Details</strong>
</div>
<div class="col-12">
<table class="body_content">
<tbody>
<tr style="padding-top:5px;">
<td style="width:20%;">
<span>Report UUID:</span>
</td>
<td style="width:80%;">
<span>{{ uuid_report }}</span>
</td>
</tr>
<tr style="padding-top:5px;">
<td style="width:20%;">
<span>Report Date:</span>
</td>
<td style="width:80%;">
<span>{{ date_report }}</span>
</td>
</tr>
<tr style="padding-top:5px;">
<td style="width:20%;">
<span>Software Version:</span>
</td>
<td style="width:80%;">
<span>{{ software }}</span>
</td>
</tr>
</table>
</div>
</div>
<div class="row" style="margin-top:25px;">
<div class="col">
<p>
I hereby declare that the data erasure process has been carried
out in accordance with the instructions received.
</p>
</div>
</div>
<div class="row" style="margin-top:225px;">
<div class="col-12">
<table class="body_content" style="border-top: 1px solid #000;">
<tbody>
<tr style="padding-top:5px;">
<td style="width:50%; text-align: center;">
<span>Data Responsable</span>
<br />
<span>{{ my_data and my_data.responsable_person or '' }}</span>
</td>
<td style="width:50%; text-align: center;">
<span>Data Supervisor</span>
<br />
<span>{{ my_data and my_data.supervisor_person or '' }}</span>
</td>
</tr>
</table>
</div>
</div>
{% if erasures %}
{% if erasure_hosts %}
{% for server in erasure_hosts %}
<div class="row mt-3 page-break">
<div class="col">
<h1>Server Summary</h1>
</div>
<div class="col">
<h4>SN Server {{ server.serial_number and server.serial_number.upper() }}</h4>
</div>
</div>
<div class="row mt-3">
<div class="col">
<table class="table" style="width: 100%; text-align: center;">
<thead style="border-bottom: 1px solid #000;">
<tr>
<th scope="col" style="text-align: center;">SN Storage</th>
<th scope="col" style="text-align: center;">Method</th>
<th scope="col" style="text-align: center;">Result</th>
<th scope="col" style="text-align: center;">Date</th>
</tr>
</thead>
<tbody>
{% for erasure in erasures %}
{% if erasure.parent == server %}
<tr style="border-bottom: 1px dashed #000;">
<td>
{{ erasure.device.serial_number.upper() }}
</td>
<td>
{{ erasure.get_public_name() }}
</td>
<td>
{{ erasure.severity.get_public_name() }}
</td>
<td>
{{ erasure.date_str }}
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endfor %}
{% endif %}
{% if erasures_normal %}
<div class="row mt-3 page-break">
<div class="col">
<h1>Devices Summary</h1>
</div>
</div>
<div class="row mt-3">
<div class="col">
<table class="table" style="width: 100%; text-align: center;">
<thead style="border-bottom: 1px solid #000;">
<tr>
<th scope="col" style="text-align: center;">SN Storage</th>
<th scope="col" style="text-align: center;">Method</th>
<th scope="col" style="text-align: center;">Result</th>
<th scope="col" style="text-align: center;">Date</th>
</tr>
</thead>
<tbody>
{% for erasure in erasures_normal %}
<tr style="border-bottom: 1px dashed #000;">
<td>
{{ erasure.device.serial_number.upper() }}
</td>
<td>
{{ erasure.get_public_name() }}
</td>
<td>
{{ erasure.severity.get_public_name() }}
</td>
<td>
{{ erasure.date_str }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
</div>
{% for erasure in erasures %}
<div class="container mb-5 page-break">
<h4>{{ erasure.device.serial_number.upper() }}</h4>
<dl>
<dt>Data storage:</dt>
<dd>{{ erasure.device.__format__('ts') }}</dd>
{% if erasure.parent %}
<dt>Computer where was erase:</dt>
<dd>Title: {{ erasure.parent.__format__('ts') }}</dd>
<dd>DevicehubID: {{ erasure.parent.dhid }}</dd>
<dd>Hid: {{ erasure.parent.hid }}</dd>
<dd>Tags: {{ erasure.parent.tags }}</dd>
<dt>Computer where it resides:</dt>
<dd>Title: {{ erasure.device.parent.__format__('ts') }}</dd>
<dd>DevicehubID: {{ erasure.device.parent.dhid }}</dd>
<dd>Hid: {{ erasure.device.parent.hid }}</dd>
<dd>Tags: {{ erasure.device.parent.tags }}</dd>
{% endif %}
<dt>Erasure:</dt>
<dd>{{ erasure.__format__('ts') }}</dd>
{% if erasure.steps %}
<dt>Erasure steps:</dt>
<dd>
<ol>
{% for step in erasure.steps %}
<li>{{ step.__format__('') }}</li>
{% endfor %}
</ol>
</dd>
{% endif %}
</dl>
</div>
{% endfor %}
{% endif %}
<footer class="page-header">
<div>
<a href="{{ url_for('Document.StampsView', _external=True) }}">Verify on-line the integrity of this document</a>
</div>
</footer>
</body>
</html>

View File

@ -7,7 +7,7 @@ DH_HOST = {{ api_host }}
DH_DATABASE = {{ schema }} DH_DATABASE = {{ schema }}
DEVICEHUB_URL = https://${DB_HOST}/${DB_DATABASE}/ DEVICEHUB_URL = https://${DB_HOST}/${DB_DATABASE}/
WB_BENCHMARK = True WB_BENCHMARK = False
WB_STRESS_TEST = 0 WB_STRESS_TEST = 0
WB_SMART_TEST = short WB_SMART_TEST = short
@ -21,7 +21,7 @@ DH_HOST = {{ api_host }}
DH_DATABASE = {{ schema }} DH_DATABASE = {{ schema }}
DEVICEHUB_URL = https://${DB_HOST}/${DB_DATABASE}/ DEVICEHUB_URL = https://${DB_HOST}/${DB_DATABASE}/
WB_BENCHMARK = True WB_BENCHMARK = False
WB_STRESS_TEST = 0 WB_STRESS_TEST = 0
WB_SMART_TEST = short WB_SMART_TEST = short

View File

@ -1,14 +1,15 @@
import flask import flask
from decouple import config
from flask import Blueprint from flask import Blueprint
from flask import current_app as app from flask import current_app as app
from flask import g, session from flask import g
from flask.views import View from flask.views import View
from flask_login import current_user, login_required, login_user, logout_user from flask_login import current_user, login_required, login_user, logout_user
from sqlalchemy import or_ from sqlalchemy import or_
from ereuse_devicehub import __version__, messages from ereuse_devicehub import __version__, messages
from ereuse_devicehub.db import db 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.action.models import Trade
from ereuse_devicehub.resources.lot.models import Lot from ereuse_devicehub.resources.lot.models import Lot
from ereuse_devicehub.resources.user.models import User from ereuse_devicehub.resources.user.models import User
@ -46,7 +47,7 @@ class LoginView(View):
url_reset_password = "#" url_reset_password = "#"
if 'register' in app.blueprints.keys(): 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(): if 'reset_password' in app.blueprints.keys():
url_reset_password = flask.url_for('reset_password.reset-password') url_reset_password = flask.url_for('reset_password.reset-password')
@ -99,10 +100,15 @@ class UserProfileView(GenericMixin):
def dispatch_request(self): def dispatch_request(self):
self.get_context() 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( self.context.update(
{ {
'current_user': current_user, 'current_user': current_user,
'password_form': PasswordForm(), 'password_form': PasswordForm(),
'sanitization_form': sanitization_form,
} }
) )
@ -126,7 +132,27 @@ class UserPasswordView(View):
return flask.redirect(flask.url_for('core.user-profile')) 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('/login/', view_func=LoginView.as_view('login'))
core.add_url_rule('/logout/', view_func=LogoutView.as_view('logout')) 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('/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_password/', view_func=UserPasswordView.as_view('set-password'))
core.add_url_rule(
'/set_sanitization/', view_func=SanitizationEntityView.as_view('set-sanitization')
)

View File

@ -8,7 +8,7 @@ isos = {
'url': 'https://releases.usody.com/2022/', 'url': 'https://releases.usody.com/2022/',
}, },
"erease": { "erease": {
'iso': "USODY_14.0.0.iso", 'iso': "USODY_14.2.0.iso",
'url': 'https://releases.usody.com/v14/', 'url': 'https://releases.usody.com/v14/',
}, },
} }

View File

@ -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')] in [y.device.id for y in x.actions if hasattr(y, 'device')]
] ]
) )
== 2 == 0
) )

View File

@ -71,6 +71,7 @@ def test_api_docs(client: Client):
'/inventory/lot/{lot_id}/transfer/', '/inventory/lot/{lot_id}/transfer/',
'/inventory/lot/transfer/{type_id}/', '/inventory/lot/transfer/{type_id}/',
'/inventory/lot/{lot_id}/upload-snapshot/', '/inventory/lot/{lot_id}/upload-snapshot/',
'/inventory/lot/{lot_id}/customerdetails/',
'/inventory/snapshots/{snapshot_uuid}/', '/inventory/snapshots/{snapshot_uuid}/',
'/inventory/snapshots/', '/inventory/snapshots/',
'/inventory/tag/devices/{dhid}/add/', '/inventory/tag/devices/{dhid}/add/',
@ -98,6 +99,7 @@ def test_api_docs(client: Client):
'/metrics/', '/metrics/',
'/profile/', '/profile/',
'/set_password/', '/set_password/',
'/set_sanitization/',
'/tags/', '/tags/',
'/tags/{tag_id}/device/{device_id}', '/tags/{tag_id}/device/{device_id}',
'/trade-documents/', '/trade-documents/',

View File

@ -417,7 +417,6 @@ def test_get_device_permissions(
html, _ = client.get(res=d.Device, item=s['device']['devicehubID'], accept=ANY) html, _ = client.get(res=d.Device, item=s['device']['devicehubID'], accept=ANY)
assert 'intel atom cpu n270 @ 1.60ghz' in html 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) pc2, res2 = user2.get(res=d.Device, item=s['device']['devicehubID'], accept=ANY)
assert res2.status_code == 200 assert res2.status_code == 200
assert pc2 == html 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) s, _ = user.post(file('asus-eee-1000h.snapshot.11'), res=m.Snapshot)
html, _ = client.get(res=d.Device, item=s['device']['devicehubID'], accept=ANY) html, _ = client.get(res=d.Device, item=s['device']['devicehubID'], accept=ANY)
assert 'intel atom cpu n270 @ 1.60ghz' in html assert 'intel atom cpu n270 @ 1.60ghz' in html
assert '00:24:8C:7F:CF:2D 100 Mbps' in html
@pytest.mark.mvp @pytest.mark.mvp

View File

@ -320,7 +320,7 @@ def test_export_certificates(user3: UserClientFlask):
body = str(next(body)) body = str(next(body))
assert status == '200 OK' assert status == '200 OK'
assert "PDF-1.5" in body assert "PDF-1.5" in body
assert 'hts54322' in body assert 'e2024242cv86mm'.upper() in body
@pytest.mark.mvp @pytest.mark.mvp
@ -2718,7 +2718,7 @@ def test_unreliable_device(user3: UserClientFlask):
snapshots = Snapshot.query.all() snapshots = Snapshot.query.all()
assert snapshot2 not in snapshots assert snapshot2 not in snapshots
assert snapshots[0].device != snapshots[1].device 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[1].device.components) == 9
assert len(snapshots[0].device.actions) == 11 assert len(snapshots[0].device.actions) == 11
assert len(snapshots[1].device.actions) == 10 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 Device.query.filter_by(hid=snapshot.device.hid).count() == 2
assert Snapshot.query.count() == 1 assert Snapshot.query.count() == 1
assert Snapshot.query.first() == snapshot assert Snapshot.query.first() == snapshot
assert len(snapshot.device.components) == 4 assert len(snapshot.device.components) == 8
assert len(snapshot.device.actions) == 4 assert len(snapshot.device.actions) == 7

View File

@ -158,12 +158,6 @@ def test_snapshot_update_timefield_updated(user: UserClient):
perform_second_snapshot=False, perform_second_snapshot=False,
) )
computer2 = yaml2json('2-second-device-with-components-of-first.snapshot') 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_devicehub_id = snapshot['device']['devicehubID']
pc1, _ = user.get(res=m.Device, item=pc1_devicehub_id) pc1, _ = user.get(res=m.Device, item=pc1_devicehub_id)
assert pc1['updated'] != snapshot['device']['updated'] 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) pc1, _ = user.get(res=m.Device, item=pc1_devicehub_id)
pc2, _ = user.get(res=m.Device, item=pc2_devicehub_id) pc2, _ = user.get(res=m.Device, item=pc2_devicehub_id)
# Check if the update_timestamp is updated # Check if the update_timestamp is updated
update1_pc2 = pc2['updated']
update2_pc1 = pc1['updated']
assert update1_pc1 != update2_pc1
# 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 all(c['parent'] == pc1_id for c in pc1['components'])
assert tuple(e['type'] for e in pc1['actions']) == ( assert tuple(e['type'] for e in pc1['actions']) == (
'BenchmarkProcessor', 'BenchmarkProcessor',
'Snapshot', 'Snapshot',
'Remove',
) )
# PC2 # 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 all(c['parent'] == pc2_id for c in pc2['components'])
assert tuple(e['type'] for e in pc2['actions']) == ('Snapshot',) assert tuple(e['type'] for e in pc2['actions']) == ('Snapshot',)
# p1c2s has two Snapshots, a Remove and an Add # 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) p1c2s, _ = user.get(res=m.Device, item=p1c2s_dev.devicehub_id)
assert tuple(e['type'] for e in p1c2s['actions']) == ( assert tuple(e['type'] for e in p1c2s['actions']) == ('Snapshot',)
'BenchmarkProcessor',
'Snapshot',
'Snapshot',
'Remove',
)
# We register the first device again, but removing motherboard # We register the first device again, but removing motherboard
# and moving processor from the second device to the first. # and moving processor from the second device to the first.
@ -296,42 +285,29 @@ def test_snapshot_component_add_remove(user: UserClient):
s3 = yaml2json( s3 = yaml2json(
'3-first-device-but-removing-motherboard-and-adding-processor-from-2.snapshot' '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) pc1, _ = user.get(res=m.Device, item=pc1_devicehub_id)
pc2, _ = user.get(res=m.Device, item=pc2_devicehub_id) pc2, _ = user.get(res=m.Device, item=pc2_devicehub_id)
# Check if the update_timestamp is updated # Check if the update_timestamp is updated
update2_pc2 = pc2['updated'] update2_pc2 = pc2['updated']
update3_pc1 = pc1['updated'] update3_pc1 = pc1['updated']
assert not update3_pc1 in [update1_pc1, update2_pc1]
assert update1_pc2 != update2_pc2
# PC1 # PC1
assert {c['serialNumber'] for c in pc1['components']} == {'p1c2s', 'p1c3s'} assert {c['serialNumber'] for c in pc1['components']} == {'p1c1s', 'p1c3s', 'p1c2s'}
assert all(c['parent'] == pc1_id for c in pc1['components']) assert all(c['parent'] == pc1['id'] for c in pc1['components'])
assert tuple(get_actions_info(pc1['actions'])) == ( assert tuple(get_actions_info(pc1['actions'])) == (
# id, type, components, snapshot # id, type, components, snapshot
('BenchmarkProcessor', []), # first BenchmarkProcessor ('BenchmarkProcessor', []), # first BenchmarkProcessor
('Snapshot', ['p1c1s', 'p1c2s', 'p1c3s', 'p1c2s']), # first Snapshot1 ('Snapshot', ['p1c1s', 'p1c2s', 'p1c3s']), # first Snapshot1
('Remove', ['p1c2s', 'p1c2s']), # Remove Processor in Snapshot2
('Snapshot', ['p1c2s', 'p1c3s']), # This Snapshot3
) )
# PC2 # PC2
assert tuple(c['serialNumber'] for c in pc2['components']) == ('p2c1s',) assert tuple(c['serialNumber'] for c in pc2['components']) == ('p2c1s', 'p1c2s')
assert all(c['parent'] == pc2_id for c in pc2['components']) assert all(c['parent'] == pc2['id'] for c in pc2['components'])
assert tuple(e['type'] for e in pc2['actions']) == ( assert tuple(e['type'] for e in pc2['actions']) == ('Snapshot',) # Second Snapshot
'Snapshot', # Second Snapshot
'Remove', # the processor we added in 2.
)
# p1c2s has Snapshot, Remove and Add # p1c2s has Snapshot, Remove and Add
p1c2s_dev = m.Device.query.filter_by(id=pc1['components'][0]['id']).one() p1c2s_dev = m.Device.query.filter_by(id=pc1['components'][0]['id']).one()
p1c2s, _ = user.get(res=m.Device, item=p1c2s_dev.devicehub_id) p1c2s, _ = user.get(res=m.Device, item=p1c2s_dev.devicehub_id)
assert tuple(get_actions_info(p1c2s['actions'])) == ( assert tuple(get_actions_info(p1c2s['actions'])) == (
('BenchmarkProcessor', []), # first BenchmarkProcessor ('Snapshot', ['p1c1s', 'p1c2s', 'p1c3s']), # First Snapshot to PC1
('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
) )
# We register the first device but without the processor, # 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 # Check if the update_timestamp is updated
update3_pc2 = pc2['updated'] update3_pc2 = pc2['updated']
update4_pc1 = pc1['updated'] update4_pc1 = pc1['updated']
assert update4_pc1 in [update1_pc1, update2_pc1, update3_pc1]
assert update3_pc2 == update2_pc2 assert update3_pc2 == update2_pc2
# PC 0: p1c3s, p1c4s. PC1: p2c1s # PC 0: p1c3s, p1c4s. PC1: p2c1s
assert {c['serialNumber'] for c in pc1['components']} == {'p1c2s', 'p1c3s'} assert {c['serialNumber'] for c in pc1['components']} == {'p1c1s', 'p1c2s', 'p1c3s'}
assert all(c['parent'] == pc1_id for c in pc1['components']) assert all(c['parent'] == pc1['id'] for c in pc1['components'])
# This last Action only # This last Action only
# PC2 # PC2
# We haven't changed PC2 # We haven't changed PC2
assert tuple(c['serialNumber'] for c in pc2['components']) == ('p2c1s',) assert tuple(c['serialNumber'] for c in pc2['components']) == ('p2c1s', 'p1c2s')
assert all(c['parent'] == pc2_id for c in pc2['components']) assert all(c['parent'] == pc2['id'] for c in pc2['components'])
@pytest.mark.mvp @pytest.mark.mvp
@ -454,7 +429,7 @@ def test_ram_remove(user: UserClient):
dev1 = m.Device.query.filter_by(id=snap1['device']['id']).one() dev1 = m.Device.query.filter_by(id=snap1['device']['id']).one()
dev2 = m.Device.query.filter_by(id=snap2['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 assert len(dev2.components) == 3
ssd = [x for x in dev2.components if x.t == 'SolidStateDrive'][0] ssd = [x for x in dev2.components if x.t == 'SolidStateDrive'][0]
remove = [x for x in ssd.actions if x.t == 'Remove'][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() db.session.commit()
assert dev2.components[2].parent == dev2 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( doc1, response = user.get(
res=documents.DocumentDef.t, item='erasures/{}'.format(dev1.id), accept=ANY 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/") bodyLite, res = user.post(snapshot_lite, uri="/api/inventory/")
assert res.status_code == 201 assert res.status_code == 201
dev = m.Device.query.filter_by(devicehub_id=bodyLite['dhid']).one() dev = m.Device.query.filter_by(devicehub_id=bodyLite['dhid']).one()
dev = dev.placeholder.binding
assert dev.placeholder is None assert dev.placeholder is None
assert dev.binding.phid == '12' assert dev.binding.phid == '12'
assert len(dev.binding.device.components) == 11 assert len(dev.binding.device.components) == 11
@ -1380,6 +1356,7 @@ def test_system_uuid_motherboard(user: UserClient):
if c['type'] == 'Motherboard': if c['type'] == 'Motherboard':
c['serialNumber'] = 'ABee0123456720' c['serialNumber'] = 'ABee0123456720'
s['uuid'] = str(uuid.uuid4())
snap2, _ = user.post(s, res=Snapshot, status=422) snap2, _ = user.post(s, res=Snapshot, status=422)
txt = "We have detected that a there is a device in your inventory" txt = "We have detected that a there is a device in your inventory"
assert txt in snap2['message'][0] 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 components1]
assert '' not in [c.phid() for c in components2] assert '' not in [c.phid() for c in components2]
assert len(components1) == len(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() assert m.Placeholder.query.count() * 2 == m.Device.query.count()
for c in m.Placeholder.query.filter(): for c in m.Placeholder.query.filter():
assert c.binding assert c.binding