Merge pull request #430 from eReuse/features/4251-delete-certificate

Features/4251 delete certificate
This commit is contained in:
cayop 2023-02-28 17:46:42 +01:00 committed by GitHub
commit ea2d446595
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 843 additions and 400 deletions

View File

@ -1,10 +1,18 @@
from boltons.urlutils import URL
from flask import g from flask import g
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):
@ -101,3 +109,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
@ -1518,6 +1523,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: 93daff872771
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 = '93daff872771'
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

@ -481,6 +481,9 @@ class EraseBasic(JoinedWithOneDeviceMixin, ActionWithOneDevice):
return self.snapshot.device.phid() return self.snapshot.device.phid()
return '' return ''
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)
@ -510,12 +513,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

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

@ -5,7 +5,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
@ -119,3 +119,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

@ -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,27 +1,345 @@
{% 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"/>
<link href="https://stackpath.bootstrapcdn.com/bootswatch/3.3.7/flatly/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-+ENW/yibaokMnme+vBLnHMphUYxHs34h9lpdbSLuAwGkOKFRl4C34WkjazBtb7eT"
crossorigin="anonymous">
<style type="text/css" media="all">
@page {
size: A4 portrait; /* can use also 'landscape' for orientation */
margin: 1.0cm 1.5cm 3.5cm 1.5cm;
font-family: "Source Sans Pro", Calibri, Candra, Sans serif;
@top {
content: element(header);
}
@bottom {
content: element(footer);
}
}
body {
width: 100% !important;
height: 100%;
background: #fff;
color: black;
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>
</header>
<div class="container">
<div class="row">
<div class="col-6">
<img class="company-logo" src="{{ customer_details and customer_details.logo.to_text() or '' }}" />
</div>
<div class="col-6">
<img class="customer-logo" src="{{ my_data and my_data.logo.to_text() }}" />
</div>
</div>
</div>
<div class="container body-content">
<div class="row mt-3">
<div class="col">
<h1>Data Sanitization Certificate</h1>
</div>
</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>{{ ustomer_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> <tr>
<th>S/N Data Storage</th> <th scope="col" style="text-align: center;">SN Storage</th>
<th>Type of erasure</th> <th scope="col" style="text-align: center;">Method</th>
<th>Result</th> <th scope="col" style="text-align: center;">Result</th>
<th>Date</th> <th scope="col" style="text-align: center;">Date</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for erasure in erasures %} {% for erasure in erasures %}
<tr> {% if erasure.parent == server %}
<tr style="border-bottom: 1px dashed #000;">
<td> <td>
{{ erasure.device.serial_number.upper() }} {{ erasure.device.serial_number.upper() }}
</td> </td>
<td> <td>
{{ erasure.type }} {{ erasure.get_public_name() }}
</td> </td>
<td> <td>
{{ erasure.severity }} {{ 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>
<td> <td>
{{ erasure.date_str }} {{ erasure.date_str }}
@ -31,15 +349,28 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="page-break row"> </div>
<h2>Details</h2> {% endif %}
{% for erasure in erasures %} </div>
<div class="col-md-6 no-page-break"> {% for erasure in erasures %}
<h4>{{ erasure.device.__format__('t') }}</h4> <div class="container mb-5 page-break">
<h4>{{ erasure.device.serial_number.upper() }}</h4>
<dl> <dl>
<dt>Data storage:</dt> <dt>Data storage:</dt>
<dd>{{ erasure.device.__format__('ts') }}</dd> <dd>{{ erasure.device.__format__('ts') }}</dd>
<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>
<dt>Erasure:</dt> <dt>Erasure:</dt>
<dd>{{ erasure.__format__('ts') }}</dd> <dd>{{ erasure.__format__('ts') }}</dd>
{% if erasure.steps %} {% if erasure.steps %}
@ -53,28 +384,13 @@
</dd> </dd>
{% endif %} {% endif %}
</dl> </dl>
</div> </div>
{% endfor %} {% endfor %}
</div> {% endif %}
<div class="no-page-break"> <footer class="page-header">
<h2>Glossary</h2> <div>
<dl>
<dt>Erase Basic</dt>
<dd>
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 class="no-print">
<a href="{{ url_pdf }}">Click here to download the PDF.</a>
</div>
<div class="print-only">
<a href="{{ url_for('Document.StampsView', _external=True) }}">Verify on-line the integrity of this document</a> <a href="{{ url_for('Document.StampsView', _external=True) }}">Verify on-line the integrity of this document</a>
</div> </div>
{% endblock %} </footer>
</body>
</html>

View File

@ -9,7 +9,7 @@ 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
@ -100,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,
} }
) )
@ -127,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

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

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