Merge branch 'feature/server-side-render' into feature/server-side-render-UX-improvements
This commit is contained in:
commit
b6cf1f1e3e
|
@ -4,35 +4,39 @@ from json.decoder import JSONDecodeError
|
||||||
from requests.exceptions import ConnectionError
|
from requests.exceptions import ConnectionError
|
||||||
|
|
||||||
from boltons.urlutils import URL
|
from boltons.urlutils import URL
|
||||||
|
from flask import g, request
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from sqlalchemy.util import OrderedSet
|
||||||
|
from wtforms import (
|
||||||
|
BooleanField, DateField, FileField, FloatField, Form, HiddenField,
|
||||||
|
IntegerField, MultipleFileField, SelectField, StringField, TextAreaField,
|
||||||
|
URLField, validators)
|
||||||
|
from wtforms.fields import FormField
|
||||||
|
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
from ereuse_devicehub.resources.action.models import RateComputer, Snapshot
|
from ereuse_devicehub.resources.action.models import RateComputer, Snapshot
|
||||||
from ereuse_devicehub.resources.action.rate.v1_0 import CannotRate
|
from ereuse_devicehub.resources.action.rate.v1_0 import CannotRate
|
||||||
from ereuse_devicehub.resources.action.schemas import \
|
from ereuse_devicehub.resources.action.schemas import \
|
||||||
Snapshot as SnapshotSchema
|
Snapshot as SnapshotSchema
|
||||||
from ereuse_devicehub.resources.action.views.snapshot import move_json, save_json
|
from ereuse_devicehub.resources.action.views.snapshot import (
|
||||||
from ereuse_devicehub.resources.device.models import (SAI, Cellphone, Computer,
|
move_json, save_json)
|
||||||
Device, Keyboard, MemoryCardReader,
|
from ereuse_devicehub.resources.device.models import (
|
||||||
Monitor, Mouse, Smartphone, Tablet)
|
SAI, Cellphone, Computer, Device, Keyboard, MemoryCardReader, Monitor,
|
||||||
from flask import g, request
|
Mouse, Smartphone, Tablet)
|
||||||
from flask_wtf import FlaskForm
|
|
||||||
from sqlalchemy.util import OrderedSet
|
|
||||||
from wtforms import (BooleanField, DateField, FileField, FloatField, Form,
|
|
||||||
HiddenField, IntegerField, MultipleFileField, SelectField,
|
|
||||||
StringField, TextAreaField, URLField, validators)
|
|
||||||
from wtforms.fields import FormField
|
|
||||||
|
|
||||||
from ereuse_devicehub.resources.device.sync import Sync
|
from ereuse_devicehub.resources.device.sync import Sync
|
||||||
from ereuse_devicehub.resources.documents.models import DataWipeDocument
|
from ereuse_devicehub.resources.documents.models import DataWipeDocument
|
||||||
from ereuse_devicehub.resources.enums import Severity, SnapshotSoftware
|
from ereuse_devicehub.resources.enums import Severity, SnapshotSoftware
|
||||||
from ereuse_devicehub.resources.hash_reports import insert_hash
|
from ereuse_devicehub.resources.hash_reports import insert_hash
|
||||||
from ereuse_devicehub.resources.lot.models import Lot
|
from ereuse_devicehub.resources.lot.models import Lot
|
||||||
from ereuse_devicehub.resources.tag.model import Tag
|
from ereuse_devicehub.resources.tag.model import Tag
|
||||||
|
from ereuse_devicehub.resources.tradedocument.models import TradeDocument
|
||||||
from ereuse_devicehub.resources.user.exceptions import InsufficientPermission
|
from ereuse_devicehub.resources.user.exceptions import InsufficientPermission
|
||||||
|
from ereuse_devicehub.resources.user.models import User
|
||||||
|
|
||||||
|
|
||||||
class LotDeviceForm(FlaskForm):
|
class LotDeviceForm(FlaskForm):
|
||||||
lot = StringField(u'Lot', [validators.UUID()])
|
lot = StringField('Lot', [validators.UUID()])
|
||||||
devices = StringField(u'Devices', [validators.length(min=1)])
|
devices = StringField('Devices', [validators.length(min=1)])
|
||||||
|
|
||||||
def validate(self, extra_validators=None):
|
def validate(self, extra_validators=None):
|
||||||
is_valid = super().validate(extra_validators)
|
is_valid = super().validate(extra_validators)
|
||||||
|
@ -40,16 +44,29 @@ class LotDeviceForm(FlaskForm):
|
||||||
if not is_valid:
|
if not is_valid:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self._lot = Lot.query.filter(Lot.id == self.lot.data).filter(
|
self._lot = (
|
||||||
Lot.owner_id == g.user.id).one()
|
Lot.query.filter(Lot.id == self.lot.data)
|
||||||
|
.filter(Lot.owner_id == g.user.id)
|
||||||
|
.one()
|
||||||
|
)
|
||||||
|
|
||||||
devices = set(self.devices.data.split(","))
|
devices = set(self.devices.data.split(","))
|
||||||
self._devices = Device.query.filter(Device.id.in_(devices)).filter(
|
self._devices = (
|
||||||
Device.owner_id == g.user.id).distinct().all()
|
Device.query.filter(Device.id.in_(devices))
|
||||||
|
.filter(Device.owner_id == g.user.id)
|
||||||
|
.distinct()
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
return bool(self._devices)
|
return bool(self._devices)
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
|
trade = self._lot.trade
|
||||||
|
if trade:
|
||||||
|
for dev in self._devices:
|
||||||
|
if trade not in dev.actions:
|
||||||
|
trade.devices.add(dev)
|
||||||
|
|
||||||
self._lot.devices.update(self._devices)
|
self._lot.devices.update(self._devices)
|
||||||
db.session.add(self._lot)
|
db.session.add(self._lot)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
@ -61,14 +78,17 @@ class LotDeviceForm(FlaskForm):
|
||||||
|
|
||||||
|
|
||||||
class LotForm(FlaskForm):
|
class LotForm(FlaskForm):
|
||||||
name = StringField(u'Name', [validators.length(min=1)])
|
name = StringField('Name', [validators.length(min=1)])
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.id = kwargs.pop('id', None)
|
self.id = kwargs.pop('id', None)
|
||||||
self.instance = None
|
self.instance = None
|
||||||
if self.id:
|
if self.id:
|
||||||
self.instance = Lot.query.filter(Lot.id == self.id).filter(
|
self.instance = (
|
||||||
Lot.owner_id == g.user.id).one()
|
Lot.query.filter(Lot.id == self.id)
|
||||||
|
.filter(Lot.owner_id == g.user.id)
|
||||||
|
.one()
|
||||||
|
)
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if self.instance and not self.name.data:
|
if self.instance and not self.name.data:
|
||||||
self.name.data = self.instance.name
|
self.name.data = self.instance.name
|
||||||
|
@ -96,7 +116,7 @@ class LotForm(FlaskForm):
|
||||||
|
|
||||||
|
|
||||||
class UploadSnapshotForm(FlaskForm):
|
class UploadSnapshotForm(FlaskForm):
|
||||||
snapshot = MultipleFileField(u'Select a Snapshot File', [validators.DataRequired()])
|
snapshot = MultipleFileField('Select a Snapshot File', [validators.DataRequired()])
|
||||||
|
|
||||||
def validate(self, extra_validators=None):
|
def validate(self, extra_validators=None):
|
||||||
is_valid = super().validate(extra_validators)
|
is_valid = super().validate(extra_validators)
|
||||||
|
@ -164,8 +184,10 @@ class UploadSnapshotForm(FlaskForm):
|
||||||
# this is a copy adaptated from ereuse_devicehub.resources.action.views.snapshot
|
# this is a copy adaptated from ereuse_devicehub.resources.action.views.snapshot
|
||||||
device = snapshot_json.pop('device') # type: Computer
|
device = snapshot_json.pop('device') # type: Computer
|
||||||
components = None
|
components = None
|
||||||
if snapshot_json['software'] == (SnapshotSoftware.Workbench or SnapshotSoftware.WorkbenchAndroid):
|
if snapshot_json['software'] == (
|
||||||
components = snapshot_json.pop('components', None) # type: List[Component]
|
SnapshotSoftware.Workbench or SnapshotSoftware.WorkbenchAndroid
|
||||||
|
):
|
||||||
|
components = snapshot_json.pop('components', None)
|
||||||
if isinstance(device, Computer) and device.hid:
|
if isinstance(device, Computer) and device.hid:
|
||||||
device.add_mac_to_hid(components_snap=components)
|
device.add_mac_to_hid(components_snap=components)
|
||||||
snapshot = Snapshot(**snapshot_json)
|
snapshot = Snapshot(**snapshot_json)
|
||||||
|
@ -174,7 +196,9 @@ class UploadSnapshotForm(FlaskForm):
|
||||||
actions_device = set(e for e in device.actions_one)
|
actions_device = set(e for e in device.actions_one)
|
||||||
device.actions_one.clear()
|
device.actions_one.clear()
|
||||||
if components:
|
if components:
|
||||||
actions_components = tuple(set(e for e in c.actions_one) for c in components)
|
actions_components = tuple(
|
||||||
|
set(e for e in c.actions_one) for c in components
|
||||||
|
)
|
||||||
for component in components:
|
for component in components:
|
||||||
component.actions_one.clear()
|
component.actions_one.clear()
|
||||||
|
|
||||||
|
@ -220,38 +244,40 @@ class UploadSnapshotForm(FlaskForm):
|
||||||
|
|
||||||
|
|
||||||
class NewDeviceForm(FlaskForm):
|
class NewDeviceForm(FlaskForm):
|
||||||
type = StringField(u'Type', [validators.DataRequired()])
|
type = StringField('Type', [validators.DataRequired()])
|
||||||
label = StringField(u'Label')
|
label = StringField('Label')
|
||||||
serial_number = StringField(u'Seria Number', [validators.DataRequired()])
|
serial_number = StringField('Seria Number', [validators.DataRequired()])
|
||||||
model = StringField(u'Model', [validators.DataRequired()])
|
model = StringField('Model', [validators.DataRequired()])
|
||||||
manufacturer = StringField(u'Manufacturer', [validators.DataRequired()])
|
manufacturer = StringField('Manufacturer', [validators.DataRequired()])
|
||||||
appearance = StringField(u'Appearance', [validators.Optional()])
|
appearance = StringField('Appearance', [validators.Optional()])
|
||||||
functionality = StringField(u'Functionality', [validators.Optional()])
|
functionality = StringField('Functionality', [validators.Optional()])
|
||||||
brand = StringField(u'Brand')
|
brand = StringField('Brand')
|
||||||
generation = IntegerField(u'Generation')
|
generation = IntegerField('Generation')
|
||||||
version = StringField(u'Version')
|
version = StringField('Version')
|
||||||
weight = FloatField(u'Weight', [validators.DataRequired()])
|
weight = FloatField('Weight', [validators.DataRequired()])
|
||||||
width = FloatField(u'Width', [validators.DataRequired()])
|
width = FloatField('Width', [validators.DataRequired()])
|
||||||
height = FloatField(u'Height', [validators.DataRequired()])
|
height = FloatField('Height', [validators.DataRequired()])
|
||||||
depth = FloatField(u'Depth', [validators.DataRequired()])
|
depth = FloatField('Depth', [validators.DataRequired()])
|
||||||
variant = StringField(u'Variant', [validators.Optional()])
|
variant = StringField('Variant', [validators.Optional()])
|
||||||
sku = StringField(u'SKU', [validators.Optional()])
|
sku = StringField('SKU', [validators.Optional()])
|
||||||
image = StringField(u'Image', [validators.Optional(), validators.URL()])
|
image = StringField('Image', [validators.Optional(), validators.URL()])
|
||||||
imei = IntegerField(u'IMEI', [validators.Optional()])
|
imei = IntegerField('IMEI', [validators.Optional()])
|
||||||
meid = StringField(u'MEID', [validators.Optional()])
|
meid = StringField('MEID', [validators.Optional()])
|
||||||
resolution = IntegerField(u'Resolution width', [validators.Optional()])
|
resolution = IntegerField('Resolution width', [validators.Optional()])
|
||||||
screen = FloatField(u'Screen size', [validators.Optional()])
|
screen = FloatField('Screen size', [validators.Optional()])
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.devices = {"Smartphone": Smartphone,
|
self.devices = {
|
||||||
"Tablet": Tablet,
|
"Smartphone": Smartphone,
|
||||||
"Cellphone": Cellphone,
|
"Tablet": Tablet,
|
||||||
"Monitor": Monitor,
|
"Cellphone": Cellphone,
|
||||||
"Mouse": Mouse,
|
"Monitor": Monitor,
|
||||||
"Keyboard": Keyboard,
|
"Mouse": Mouse,
|
||||||
"SAI": SAI,
|
"Keyboard": Keyboard,
|
||||||
"MemoryCardReader": MemoryCardReader}
|
"SAI": SAI,
|
||||||
|
"MemoryCardReader": MemoryCardReader,
|
||||||
|
}
|
||||||
|
|
||||||
if not self.generation.data:
|
if not self.generation.data:
|
||||||
self.generation.data = 1
|
self.generation.data = 1
|
||||||
|
@ -328,29 +354,31 @@ class NewDeviceForm(FlaskForm):
|
||||||
'software': 'Web',
|
'software': 'Web',
|
||||||
'version': '11.0',
|
'version': '11.0',
|
||||||
'device': {
|
'device': {
|
||||||
'type': self.type.data,
|
'type': self.type.data,
|
||||||
'model': self.model.data,
|
'model': self.model.data,
|
||||||
'manufacturer': self.manufacturer.data,
|
'manufacturer': self.manufacturer.data,
|
||||||
'serialNumber': self.serial_number.data,
|
'serialNumber': self.serial_number.data,
|
||||||
'brand': self.brand.data,
|
'brand': self.brand.data,
|
||||||
'version': self.version.data,
|
'version': self.version.data,
|
||||||
'generation': self.generation.data,
|
'generation': self.generation.data,
|
||||||
'sku': self.sku.data,
|
'sku': self.sku.data,
|
||||||
'weight': self.weight.data,
|
'weight': self.weight.data,
|
||||||
'width': self.width.data,
|
'width': self.width.data,
|
||||||
'height': self.height.data,
|
'height': self.height.data,
|
||||||
'depth': self.depth.data,
|
'depth': self.depth.data,
|
||||||
'variant': self.variant.data,
|
'variant': self.variant.data,
|
||||||
'image': self.image.data
|
'image': self.image.data,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.appearance.data or self.functionality.data:
|
if self.appearance.data or self.functionality.data:
|
||||||
json_snapshot['device']['actions'] = [{
|
json_snapshot['device']['actions'] = [
|
||||||
'type': 'VisualTest',
|
{
|
||||||
'appearanceRange': self.appearance.data,
|
'type': 'VisualTest',
|
||||||
'functionalityRange': self.functionality.data
|
'appearanceRange': self.appearance.data,
|
||||||
}]
|
'functionalityRange': self.functionality.data,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
upload_form = UploadSnapshotForm()
|
upload_form = UploadSnapshotForm()
|
||||||
upload_form.sync = Sync()
|
upload_form.sync = Sync()
|
||||||
|
@ -380,14 +408,14 @@ class NewDeviceForm(FlaskForm):
|
||||||
|
|
||||||
|
|
||||||
class TagForm(FlaskForm):
|
class TagForm(FlaskForm):
|
||||||
code = StringField(u'Code', [validators.length(min=1)])
|
code = StringField('Code', [validators.length(min=1)])
|
||||||
|
|
||||||
def validate(self, extra_validators=None):
|
def validate(self, extra_validators=None):
|
||||||
error = ["This value is being used"]
|
error = ["This value is being used"]
|
||||||
is_valid = super().validate(extra_validators)
|
is_valid = super().validate(extra_validators)
|
||||||
if not is_valid:
|
if not is_valid:
|
||||||
return False
|
return False
|
||||||
tag = Tag.query.filter(Tag.id==self.code.data).all()
|
tag = Tag.query.filter(Tag.id == self.code.data).all()
|
||||||
if tag:
|
if tag:
|
||||||
self.code.errors = error
|
self.code.errors = error
|
||||||
return False
|
return False
|
||||||
|
@ -408,7 +436,7 @@ class TagForm(FlaskForm):
|
||||||
|
|
||||||
|
|
||||||
class TagUnnamedForm(FlaskForm):
|
class TagUnnamedForm(FlaskForm):
|
||||||
amount = IntegerField(u'amount')
|
amount = IntegerField('amount')
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
num = self.amount.data
|
num = self.amount.data
|
||||||
|
@ -424,8 +452,8 @@ class TagUnnamedForm(FlaskForm):
|
||||||
|
|
||||||
|
|
||||||
class TagDeviceForm(FlaskForm):
|
class TagDeviceForm(FlaskForm):
|
||||||
tag = SelectField(u'Tag', choices=[])
|
tag = SelectField('Tag', choices=[])
|
||||||
device = StringField(u'Device', [validators.Optional()])
|
device = StringField('Device', [validators.Optional()])
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.delete = kwargs.pop('delete', None)
|
self.delete = kwargs.pop('delete', None)
|
||||||
|
@ -434,9 +462,11 @@ class TagDeviceForm(FlaskForm):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
if self.delete:
|
if self.delete:
|
||||||
tags = Tag.query.filter(Tag.owner_id==g.user.id).filter(Tag.device_id==self.device_id)
|
tags = Tag.query.filter(Tag.owner_id == g.user.id).filter_by(
|
||||||
|
device_id=self.device_id
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
tags = Tag.query.filter(Tag.owner_id==g.user.id).filter(Tag.device_id==None)
|
tags = Tag.query.filter(Tag.owner_id == g.user.id).filter_by(device_id=None)
|
||||||
|
|
||||||
self.tag.choices = [(tag.id, tag.id) for tag in tags]
|
self.tag.choices = [(tag.id, tag.id) for tag in tags]
|
||||||
|
|
||||||
|
@ -446,8 +476,11 @@ class TagDeviceForm(FlaskForm):
|
||||||
if not is_valid:
|
if not is_valid:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self._tag = Tag.query.filter(Tag.id == self.tag.data).filter(
|
self._tag = (
|
||||||
Tag.owner_id == g.user.id).one()
|
Tag.query.filter(Tag.id == self.tag.data)
|
||||||
|
.filter(Tag.owner_id == g.user.id)
|
||||||
|
.one()
|
||||||
|
)
|
||||||
|
|
||||||
if not self.delete and self._tag.device_id:
|
if not self.delete and self._tag.device_id:
|
||||||
self.tag.errors = [("This tag is actualy in use.")]
|
self.tag.errors = [("This tag is actualy in use.")]
|
||||||
|
@ -461,8 +494,11 @@ class TagDeviceForm(FlaskForm):
|
||||||
|
|
||||||
if self.device_id or self.device.data:
|
if self.device_id or self.device.data:
|
||||||
self.device_id = self.device_id or self.device.data
|
self.device_id = self.device_id or self.device.data
|
||||||
self._device = Device.query.filter(Device.id == self.device_id).filter(
|
self._device = (
|
||||||
Device.owner_id == g.user.id).one()
|
Device.query.filter(Device.id == self.device_id)
|
||||||
|
.filter(Device.owner_id == g.user.id)
|
||||||
|
.one()
|
||||||
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -478,38 +514,55 @@ class TagDeviceForm(FlaskForm):
|
||||||
|
|
||||||
|
|
||||||
class NewActionForm(FlaskForm):
|
class NewActionForm(FlaskForm):
|
||||||
name = StringField(u'Name', [validators.length(max=50)],
|
name = StringField(
|
||||||
description="A name or title of the event. Something to look for.")
|
'Name',
|
||||||
|
[validators.length(max=50)],
|
||||||
|
description="A name or title of the event. Something to look for.",
|
||||||
|
)
|
||||||
devices = HiddenField()
|
devices = HiddenField()
|
||||||
date = DateField(u'Date', [validators.Optional()],
|
date = DateField(
|
||||||
description="""When the action ends. For some actions like booking
|
'Date',
|
||||||
|
[validators.Optional()],
|
||||||
|
description="""When the action ends. For some actions like booking
|
||||||
the time when it expires, for others like renting the
|
the time when it expires, for others like renting the
|
||||||
time that the end rents. For specific actions, it is the
|
time that the end rents. For specific actions, it is the
|
||||||
time in which they are carried out; differs from created
|
time in which they are carried out; differs from created
|
||||||
in that created is where the system receives the action.""")
|
in that created is where the system receives the action.""",
|
||||||
severity = SelectField(u'Severity', choices=[(v.name, v.name) for v in Severity],
|
)
|
||||||
description="""An indicator that evaluates the execution of the event.
|
severity = SelectField(
|
||||||
For example, failed events are set to Error""")
|
'Severity',
|
||||||
description = TextAreaField(u'Description')
|
choices=[(v.name, v.name) for v in Severity],
|
||||||
|
description="""An indicator that evaluates the execution of the event.
|
||||||
|
For example, failed events are set to Error""",
|
||||||
|
)
|
||||||
|
description = TextAreaField('Description')
|
||||||
lot = HiddenField()
|
lot = HiddenField()
|
||||||
type = HiddenField()
|
type = HiddenField()
|
||||||
|
|
||||||
def validate(self, extra_validators=None):
|
def validate(self, extra_validators=None):
|
||||||
is_valid = super().validate(extra_validators)
|
is_valid = self.generic_validation(extra_validators=extra_validators)
|
||||||
|
|
||||||
if not is_valid:
|
if not is_valid:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
self._devices = OrderedSet()
|
||||||
if self.devices.data:
|
if self.devices.data:
|
||||||
devices = set(self.devices.data.split(","))
|
devices = set(self.devices.data.split(","))
|
||||||
self._devices = OrderedSet(Device.query.filter(Device.id.in_(devices)).filter(
|
self._devices = OrderedSet(
|
||||||
Device.owner_id == g.user.id).all())
|
Device.query.filter(Device.id.in_(devices))
|
||||||
|
.filter(Device.owner_id == g.user.id)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
if not self._devices:
|
if not self._devices:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def generic_validation(self, extra_validators=None):
|
||||||
|
# Some times we want check validations without devices list
|
||||||
|
return super().validate(extra_validators)
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
Model = db.Model._decl_class_registry.data[self.type.data]()
|
Model = db.Model._decl_class_registry.data[self.type.data]()
|
||||||
self.instance = Model()
|
self.instance = Model()
|
||||||
|
@ -527,13 +580,20 @@ class NewActionForm(FlaskForm):
|
||||||
|
|
||||||
return self.instance
|
return self.instance
|
||||||
|
|
||||||
|
def check_valid(self):
|
||||||
|
if self.type.data in ['', None]:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.validate():
|
||||||
|
return self.type.data
|
||||||
|
|
||||||
|
|
||||||
class AllocateForm(NewActionForm):
|
class AllocateForm(NewActionForm):
|
||||||
start_time = DateField(u'Start time')
|
start_time = DateField('Start time')
|
||||||
end_time = DateField(u'End time')
|
end_time = DateField('End time')
|
||||||
final_user_code = StringField(u'Final user code', [validators.length(max=50)])
|
final_user_code = StringField('Final user code', [validators.length(max=50)])
|
||||||
transaction = StringField(u'Transaction', [validators.length(max=50)])
|
transaction = StringField('Transaction', [validators.length(max=50)])
|
||||||
end_users = IntegerField(u'End users')
|
end_users = IntegerField('End users')
|
||||||
|
|
||||||
def validate(self, extra_validators=None):
|
def validate(self, extra_validators=None):
|
||||||
is_valid = super().validate(extra_validators)
|
is_valid = super().validate(extra_validators)
|
||||||
|
@ -554,19 +614,31 @@ class AllocateForm(NewActionForm):
|
||||||
|
|
||||||
|
|
||||||
class DataWipeDocumentForm(Form):
|
class DataWipeDocumentForm(Form):
|
||||||
date = DateField(u'Date', [validators.Optional()],
|
date = DateField(
|
||||||
description="Date when was data wipe")
|
'Date', [validators.Optional()], description="Date when was data wipe"
|
||||||
url = URLField(u'Url', [validators.Optional()],
|
)
|
||||||
description="Url where the document resides")
|
url = URLField(
|
||||||
success = BooleanField(u'Success', [validators.Optional()],
|
'Url', [validators.Optional()], description="Url where the document resides"
|
||||||
description="The erase was success or not?")
|
)
|
||||||
software = StringField(u'Software', [validators.Optional()],
|
success = BooleanField(
|
||||||
description="Which software has you use for erase the disks")
|
'Success', [validators.Optional()], description="The erase was success or not?"
|
||||||
id_document = StringField(u'Document Id', [validators.Optional()],
|
)
|
||||||
description="Identification number of document")
|
software = StringField(
|
||||||
file_name = FileField(u'File', [validators.DataRequired()],
|
'Software',
|
||||||
description="""This file is not stored on our servers, it is only used to
|
[validators.Optional()],
|
||||||
generate a digital signature and obtain the name of the file.""")
|
description="Which software has you use for erase the disks",
|
||||||
|
)
|
||||||
|
id_document = StringField(
|
||||||
|
'Document Id',
|
||||||
|
[validators.Optional()],
|
||||||
|
description="Identification number of document",
|
||||||
|
)
|
||||||
|
file_name = FileField(
|
||||||
|
'File',
|
||||||
|
[validators.DataRequired()],
|
||||||
|
description="""This file is not stored on our servers, it is only used to
|
||||||
|
generate a digital signature and obtain the name of the file.""",
|
||||||
|
)
|
||||||
|
|
||||||
def validate(self, extra_validators=None):
|
def validate(self, extra_validators=None):
|
||||||
is_valid = super().validate(extra_validators)
|
is_valid = super().validate(extra_validators)
|
||||||
|
@ -619,3 +691,230 @@ class DataWipeForm(NewActionForm):
|
||||||
self.document = document
|
self.document = document
|
||||||
|
|
||||||
return self.instance
|
return self.instance
|
||||||
|
|
||||||
|
|
||||||
|
class TradeForm(NewActionForm):
|
||||||
|
user_from = StringField(
|
||||||
|
'Supplier',
|
||||||
|
[validators.Optional()],
|
||||||
|
description="Please enter the supplier's email address",
|
||||||
|
render_kw={'data-email': ""},
|
||||||
|
)
|
||||||
|
user_to = StringField(
|
||||||
|
'Receiver',
|
||||||
|
[validators.Optional()],
|
||||||
|
description="Please enter the receiver's email address",
|
||||||
|
render_kw={'data-email': ""},
|
||||||
|
)
|
||||||
|
confirm = BooleanField(
|
||||||
|
'Confirm',
|
||||||
|
[validators.Optional()],
|
||||||
|
default=True,
|
||||||
|
description="I need confirmation from the other user for every device and document.",
|
||||||
|
)
|
||||||
|
code = StringField(
|
||||||
|
'Code',
|
||||||
|
[validators.Optional()],
|
||||||
|
description="If you don't need confirm, you need put a code for trace the user in the statistics.",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.user_from.render_kw['data-email'] = g.user.email
|
||||||
|
self.user_to.render_kw['data-email'] = g.user.email
|
||||||
|
self._lot = (
|
||||||
|
Lot.query.filter(Lot.id == self.lot.data)
|
||||||
|
.filter(Lot.owner_id == g.user.id)
|
||||||
|
.one()
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate(self, extra_validators=None):
|
||||||
|
is_valid = self.generic_validation(extra_validators=extra_validators)
|
||||||
|
email_from = self.user_from.data
|
||||||
|
email_to = self.user_to.data
|
||||||
|
|
||||||
|
if not self.confirm.data and not self.code.data:
|
||||||
|
self.code.errors = ["If you don't want to confirm, you need a code"]
|
||||||
|
is_valid = False
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.confirm.data
|
||||||
|
and not (email_from and email_to)
|
||||||
|
or email_to == email_from
|
||||||
|
or g.user.email not in [email_from, email_to]
|
||||||
|
):
|
||||||
|
|
||||||
|
errors = ["If you want confirm, you need a correct email"]
|
||||||
|
self.user_to.errors = errors
|
||||||
|
self.user_from.errors = errors
|
||||||
|
|
||||||
|
is_valid = False
|
||||||
|
|
||||||
|
if self.confirm.data and is_valid:
|
||||||
|
user_to = User.query.filter_by(email=email_to).first() or g.user
|
||||||
|
user_from = User.query.filter_by(email=email_from).first() or g.user
|
||||||
|
if user_to == user_from:
|
||||||
|
is_valid = False
|
||||||
|
else:
|
||||||
|
self.db_user_to = user_to
|
||||||
|
self.db_user_from = user_from
|
||||||
|
|
||||||
|
self.has_errors = not is_valid
|
||||||
|
return is_valid
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
if self.has_errors:
|
||||||
|
raise ValueError(
|
||||||
|
"The %s could not be saved because the data didn't validate."
|
||||||
|
% (self.instance._meta.object_name)
|
||||||
|
)
|
||||||
|
if not self.confirm.data:
|
||||||
|
self.create_phantom_account()
|
||||||
|
self.prepare_instance()
|
||||||
|
self.create_automatic_trade()
|
||||||
|
|
||||||
|
if commit:
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return self.instance
|
||||||
|
|
||||||
|
def prepare_instance(self):
|
||||||
|
Model = db.Model._decl_class_registry.data['Trade']()
|
||||||
|
self.instance = Model()
|
||||||
|
self.instance.user_from = self.db_user_from
|
||||||
|
self.instance.user_to = self.db_user_to
|
||||||
|
self.instance.lot_id = self._lot.id
|
||||||
|
self.instance.devices = self._lot.devices
|
||||||
|
self.instance.code = self.code.data
|
||||||
|
self.instance.confirm = self.confirm.data
|
||||||
|
self.instance.date = self.date.data
|
||||||
|
self.instance.name = self.name.data
|
||||||
|
self.instance.description = self.description.data
|
||||||
|
db.session.add(self.instance)
|
||||||
|
|
||||||
|
def create_phantom_account(self):
|
||||||
|
"""
|
||||||
|
If exist both users not to do nothing
|
||||||
|
If exist from but not to:
|
||||||
|
search if exist in the DB
|
||||||
|
if exist use it
|
||||||
|
else create new one
|
||||||
|
The same if exist to but not from
|
||||||
|
|
||||||
|
"""
|
||||||
|
user_from = self.user_from.data
|
||||||
|
user_to = self.user_to.data
|
||||||
|
code = self.code.data
|
||||||
|
|
||||||
|
if user_from and user_to:
|
||||||
|
# both users exist, no further action is necessary
|
||||||
|
return
|
||||||
|
|
||||||
|
# Create receiver (to) phantom account
|
||||||
|
if user_from and not user_to:
|
||||||
|
assert g.user.email == user_from
|
||||||
|
|
||||||
|
self.user_from = g.user
|
||||||
|
self.user_to = self.get_or_create_user(code)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Create supplier (from) phantom account
|
||||||
|
if not user_from and user_to:
|
||||||
|
assert g.user.email == user_to
|
||||||
|
|
||||||
|
self.user_from = self.get_or_create_user(code)
|
||||||
|
self.user_to = g.user
|
||||||
|
|
||||||
|
def get_or_create_user(self, code):
|
||||||
|
email = "{}_{}@dhub.com".format(str(g.user.id), code)
|
||||||
|
user = User.query.filter_by(email=email).first()
|
||||||
|
if not user:
|
||||||
|
user = User(email=email, password='', active=False, phantom=True)
|
||||||
|
db.session.add(user)
|
||||||
|
return user
|
||||||
|
|
||||||
|
def create_automatic_trade(self):
|
||||||
|
# This method change the ownership of devices
|
||||||
|
# do nothing if an explicit confirmation is required
|
||||||
|
if self.confirm.data:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Change the owner for every devices
|
||||||
|
for dev in self._lot.devices:
|
||||||
|
dev.change_owner(self.db_user_to)
|
||||||
|
|
||||||
|
def check_valid(self):
|
||||||
|
if self.user_from.data == self.user_to.data:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.user_from.data == g.user.email:
|
||||||
|
return 'user_to'
|
||||||
|
|
||||||
|
if self.user_to.data == g.user.email:
|
||||||
|
return 'user_from'
|
||||||
|
|
||||||
|
|
||||||
|
class TradeDocumentForm(FlaskForm):
|
||||||
|
url = URLField(
|
||||||
|
'Url',
|
||||||
|
[validators.Optional()],
|
||||||
|
render_kw={'class': "form-control"},
|
||||||
|
description="Url where the document resides",
|
||||||
|
)
|
||||||
|
description = StringField(
|
||||||
|
'Description',
|
||||||
|
[validators.Optional()],
|
||||||
|
render_kw={'class': "form-control"},
|
||||||
|
description="",
|
||||||
|
)
|
||||||
|
id_document = StringField(
|
||||||
|
'Document Id',
|
||||||
|
[validators.Optional()],
|
||||||
|
render_kw={'class': "form-control"},
|
||||||
|
description="Identification number of document",
|
||||||
|
)
|
||||||
|
date = DateField(
|
||||||
|
'Date',
|
||||||
|
[validators.Optional()],
|
||||||
|
render_kw={'class': "form-control"},
|
||||||
|
description="",
|
||||||
|
)
|
||||||
|
file_name = FileField(
|
||||||
|
'File',
|
||||||
|
[validators.DataRequired()],
|
||||||
|
render_kw={'class': "form-control"},
|
||||||
|
description="""This file is not stored on our servers, it is only used to
|
||||||
|
generate a digital signature and obtain the name of the file.""",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
lot_id = kwargs.pop('lot')
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self._lot = Lot.query.filter(Lot.id == lot_id).one()
|
||||||
|
|
||||||
|
def validate(self, extra_validators=None):
|
||||||
|
is_valid = super().validate(extra_validators)
|
||||||
|
|
||||||
|
if g.user not in [self._lot.trade.user_from, self._lot.trade.user_to]:
|
||||||
|
is_valid = False
|
||||||
|
|
||||||
|
return is_valid
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
file_name = ''
|
||||||
|
file_hash = ''
|
||||||
|
if self.file_name.data:
|
||||||
|
file_name = self.file_name.data.filename
|
||||||
|
file_hash = insert_hash(self.file_name.data.read(), commit=False)
|
||||||
|
|
||||||
|
self.url.data = URL(self.url.data)
|
||||||
|
self._obj = TradeDocument(lot_id=self._lot.id)
|
||||||
|
self.populate_obj(self._obj)
|
||||||
|
self._obj.file_name = file_name
|
||||||
|
self._obj.file_hash = file_hash
|
||||||
|
db.session.add(self._obj)
|
||||||
|
self._lot.trade.documents.add(self._obj)
|
||||||
|
if commit:
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return self._obj
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
|
import csv
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import Blueprint, request, url_for
|
import flask_weasyprint
|
||||||
|
from flask import Blueprint, g, make_response, request, url_for
|
||||||
from flask.views import View
|
from flask.views import View
|
||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
from ereuse_devicehub import messages
|
from ereuse_devicehub import messages
|
||||||
from ereuse_devicehub.inventory.forms import (
|
from ereuse_devicehub.inventory.forms import (
|
||||||
|
@ -14,9 +19,14 @@ from ereuse_devicehub.inventory.forms import (
|
||||||
TagDeviceForm,
|
TagDeviceForm,
|
||||||
TagForm,
|
TagForm,
|
||||||
TagUnnamedForm,
|
TagUnnamedForm,
|
||||||
|
TradeDocumentForm,
|
||||||
|
TradeForm,
|
||||||
UploadSnapshotForm,
|
UploadSnapshotForm,
|
||||||
)
|
)
|
||||||
from ereuse_devicehub.resources.device.models import Device
|
from ereuse_devicehub.resources.action.models import Trade
|
||||||
|
from ereuse_devicehub.resources.device.models import Computer, DataStorage, Device
|
||||||
|
from ereuse_devicehub.resources.documents.device_row import ActionRow, DeviceRow
|
||||||
|
from ereuse_devicehub.resources.hash_reports import insert_hash
|
||||||
from ereuse_devicehub.resources.lot.models import Lot
|
from ereuse_devicehub.resources.lot.models import Lot
|
||||||
from ereuse_devicehub.resources.tag.model import Tag
|
from ereuse_devicehub.resources.tag.model import Tag
|
||||||
|
|
||||||
|
@ -36,7 +46,7 @@ class DeviceListMix(View):
|
||||||
lot = None
|
lot = None
|
||||||
tags = (
|
tags = (
|
||||||
Tag.query.filter(Tag.owner_id == current_user.id)
|
Tag.query.filter(Tag.owner_id == current_user.id)
|
||||||
.filter(Tag.device_id == None)
|
.filter(Tag.device_id.is_(None))
|
||||||
.order_by(Tag.created.desc())
|
.order_by(Tag.created.desc())
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -47,17 +57,22 @@ class DeviceListMix(View):
|
||||||
form_new_action = NewActionForm(lot=lot.id)
|
form_new_action = NewActionForm(lot=lot.id)
|
||||||
form_new_allocate = AllocateForm(lot=lot.id)
|
form_new_allocate = AllocateForm(lot=lot.id)
|
||||||
form_new_datawipe = DataWipeForm(lot=lot.id)
|
form_new_datawipe = DataWipeForm(lot=lot.id)
|
||||||
|
form_new_trade = TradeForm(
|
||||||
|
lot=lot.id,
|
||||||
|
user_to=g.user.email,
|
||||||
|
user_from=g.user.email,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
devices = (
|
devices = (
|
||||||
Device.query.filter(Device.owner_id == current_user.id)
|
Device.query.filter(Device.owner_id == current_user.id)
|
||||||
.filter(Device.type.in_(filter_types))
|
.filter(Device.type.in_(filter_types))
|
||||||
.filter(Device.lots == None)
|
.filter_by(lots=None)
|
||||||
.order_by(Device.updated.desc())
|
.order_by(Device.updated.desc())
|
||||||
)
|
)
|
||||||
form_new_action = NewActionForm()
|
form_new_action = NewActionForm()
|
||||||
form_new_allocate = AllocateForm()
|
form_new_allocate = AllocateForm()
|
||||||
form_new_datawipe = DataWipeForm()
|
form_new_datawipe = DataWipeForm()
|
||||||
|
form_new_trade = ''
|
||||||
action_devices = form_new_action.devices.data
|
action_devices = form_new_action.devices.data
|
||||||
list_devices = []
|
list_devices = []
|
||||||
if action_devices:
|
if action_devices:
|
||||||
|
@ -71,6 +86,7 @@ class DeviceListMix(View):
|
||||||
'form_new_action': form_new_action,
|
'form_new_action': form_new_action,
|
||||||
'form_new_allocate': form_new_allocate,
|
'form_new_allocate': form_new_allocate,
|
||||||
'form_new_datawipe': form_new_datawipe,
|
'form_new_datawipe': form_new_datawipe,
|
||||||
|
'form_new_trade': form_new_trade,
|
||||||
'lot': lot,
|
'lot': lot,
|
||||||
'tags': tags,
|
'tags': tags,
|
||||||
'list_devices': list_devices,
|
'list_devices': list_devices,
|
||||||
|
@ -325,8 +341,10 @@ class NewActionView(View):
|
||||||
self.form = self.form_class()
|
self.form = self.form_class()
|
||||||
|
|
||||||
if self.form.validate_on_submit():
|
if self.form.validate_on_submit():
|
||||||
instance = self.form.save()
|
self.form.save()
|
||||||
messages.success('Action "{}" created successfully!'.format(instance.type))
|
messages.success(
|
||||||
|
'Action "{}" created successfully!'.format(self.form.type.data)
|
||||||
|
)
|
||||||
|
|
||||||
next_url = self.get_next_url()
|
next_url = self.get_next_url()
|
||||||
return flask.redirect(next_url)
|
return flask.redirect(next_url)
|
||||||
|
@ -348,8 +366,10 @@ class NewAllocateView(NewActionView, DeviceListMix):
|
||||||
self.form = self.form_class()
|
self.form = self.form_class()
|
||||||
|
|
||||||
if self.form.validate_on_submit():
|
if self.form.validate_on_submit():
|
||||||
instance = self.form.save()
|
self.form.save()
|
||||||
messages.success('Action "{}" created successfully!'.format(instance.type))
|
messages.success(
|
||||||
|
'Action "{}" created successfully!'.format(self.form.type.data)
|
||||||
|
)
|
||||||
|
|
||||||
next_url = self.get_next_url()
|
next_url = self.get_next_url()
|
||||||
return flask.redirect(next_url)
|
return flask.redirect(next_url)
|
||||||
|
@ -368,8 +388,10 @@ class NewDataWipeView(NewActionView, DeviceListMix):
|
||||||
self.form = self.form_class()
|
self.form = self.form_class()
|
||||||
|
|
||||||
if self.form.validate_on_submit():
|
if self.form.validate_on_submit():
|
||||||
instance = self.form.save()
|
self.form.save()
|
||||||
messages.success('Action "{}" created successfully!'.format(instance.type))
|
messages.success(
|
||||||
|
'Action "{}" created successfully!'.format(self.form.type.data)
|
||||||
|
)
|
||||||
|
|
||||||
next_url = self.get_next_url()
|
next_url = self.get_next_url()
|
||||||
return flask.redirect(next_url)
|
return flask.redirect(next_url)
|
||||||
|
@ -380,13 +402,186 @@ class NewDataWipeView(NewActionView, DeviceListMix):
|
||||||
return flask.render_template(self.template_name, **self.context)
|
return flask.render_template(self.template_name, **self.context)
|
||||||
|
|
||||||
|
|
||||||
|
class NewTradeView(NewActionView, DeviceListMix):
|
||||||
|
methods = ['POST']
|
||||||
|
form_class = TradeForm
|
||||||
|
|
||||||
|
def dispatch_request(self):
|
||||||
|
self.form = self.form_class()
|
||||||
|
|
||||||
|
if self.form.validate_on_submit():
|
||||||
|
self.form.save()
|
||||||
|
messages.success(
|
||||||
|
'Action "{}" created successfully!'.format(self.form.type.data)
|
||||||
|
)
|
||||||
|
|
||||||
|
next_url = self.get_next_url()
|
||||||
|
return flask.redirect(next_url)
|
||||||
|
|
||||||
|
lot_id = self.form.lot.data
|
||||||
|
self.get_context(lot_id)
|
||||||
|
self.context['form_new_trade'] = self.form
|
||||||
|
return flask.render_template(self.template_name, **self.context)
|
||||||
|
|
||||||
|
|
||||||
|
class NewTradeDocumentView(View):
|
||||||
|
methods = ['POST', 'GET']
|
||||||
|
decorators = [login_required]
|
||||||
|
template_name = 'inventory/trade_document.html'
|
||||||
|
form_class = TradeDocumentForm
|
||||||
|
title = "Add new document"
|
||||||
|
|
||||||
|
def dispatch_request(self, lot_id):
|
||||||
|
self.form = self.form_class(lot=lot_id)
|
||||||
|
|
||||||
|
if self.form.validate_on_submit():
|
||||||
|
self.form.save()
|
||||||
|
messages.success('Document created successfully!')
|
||||||
|
next_url = url_for('inventory.devices.lotdevicelist', lot_id=lot_id)
|
||||||
|
return flask.redirect(next_url)
|
||||||
|
|
||||||
|
return flask.render_template(
|
||||||
|
self.template_name, form=self.form, title=self.title
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ExportsView(View):
|
||||||
|
methods = ['GET']
|
||||||
|
decorators = [login_required]
|
||||||
|
|
||||||
|
def dispatch_request(self, export_id):
|
||||||
|
export_ids = {
|
||||||
|
'metrics': self.metrics,
|
||||||
|
'devices': self.devices_list,
|
||||||
|
'certificates': self.erasure,
|
||||||
|
'links': self.public_links,
|
||||||
|
}
|
||||||
|
|
||||||
|
if export_id not in export_ids:
|
||||||
|
return NotFound()
|
||||||
|
return export_ids[export_id]()
|
||||||
|
|
||||||
|
def find_devices(self):
|
||||||
|
args = request.args.get('ids')
|
||||||
|
ids = args.split(',') if args else []
|
||||||
|
query = Device.query.filter(Device.owner == g.user)
|
||||||
|
return query.filter(Device.devicehub_id.in_(ids))
|
||||||
|
|
||||||
|
def response_csv(self, data, name):
|
||||||
|
bfile = data.getvalue().encode('utf-8')
|
||||||
|
# insert proof
|
||||||
|
insert_hash(bfile)
|
||||||
|
output = make_response(bfile)
|
||||||
|
output.headers['Content-Disposition'] = 'attachment; filename={}'.format(name)
|
||||||
|
output.headers['Content-type'] = 'text/csv'
|
||||||
|
return output
|
||||||
|
|
||||||
|
def devices_list(self):
|
||||||
|
"""Get device query and put information in csv format."""
|
||||||
|
data = StringIO()
|
||||||
|
cw = csv.writer(data, delimiter=';', lineterminator="\n", quotechar='"')
|
||||||
|
first = True
|
||||||
|
|
||||||
|
for device in self.find_devices():
|
||||||
|
d = DeviceRow(device, {})
|
||||||
|
if first:
|
||||||
|
cw.writerow(d.keys())
|
||||||
|
first = False
|
||||||
|
cw.writerow(d.values())
|
||||||
|
|
||||||
|
return self.response_csv(data, "export.csv")
|
||||||
|
|
||||||
|
def metrics(self):
|
||||||
|
"""Get device query and put information in csv format."""
|
||||||
|
data = StringIO()
|
||||||
|
cw = csv.writer(data, delimiter=';', lineterminator="\n", quotechar='"')
|
||||||
|
first = True
|
||||||
|
devs_id = []
|
||||||
|
# Get the allocate info
|
||||||
|
for device in self.find_devices():
|
||||||
|
devs_id.append(device.id)
|
||||||
|
for allocate in device.get_metrics():
|
||||||
|
d = ActionRow(allocate)
|
||||||
|
if first:
|
||||||
|
cw.writerow(d.keys())
|
||||||
|
first = False
|
||||||
|
cw.writerow(d.values())
|
||||||
|
|
||||||
|
# Get the trade info
|
||||||
|
query_trade = Trade.query.filter(
|
||||||
|
Trade.devices.any(Device.id.in_(devs_id))
|
||||||
|
).all()
|
||||||
|
|
||||||
|
lot_id = request.args.get('lot')
|
||||||
|
if lot_id and not query_trade:
|
||||||
|
lot = Lot.query.filter_by(id=lot_id).one()
|
||||||
|
if hasattr(lot, "trade") and lot.trade:
|
||||||
|
if g.user in [lot.trade.user_from, lot.trade.user_to]:
|
||||||
|
query_trade = [lot.trade]
|
||||||
|
|
||||||
|
for trade in query_trade:
|
||||||
|
data_rows = trade.get_metrics()
|
||||||
|
for row in data_rows:
|
||||||
|
d = ActionRow(row)
|
||||||
|
if first:
|
||||||
|
cw.writerow(d.keys())
|
||||||
|
first = False
|
||||||
|
cw.writerow(d.values())
|
||||||
|
|
||||||
|
return self.response_csv(data, "actions_export.csv")
|
||||||
|
|
||||||
|
def public_links(self):
|
||||||
|
# get a csv with the publink links of this devices
|
||||||
|
data = StringIO()
|
||||||
|
cw = csv.writer(data, delimiter=';', lineterminator="\n", quotechar='"')
|
||||||
|
cw.writerow(['links'])
|
||||||
|
host_url = request.host_url
|
||||||
|
for dev in self.find_devices():
|
||||||
|
code = dev.devicehub_id
|
||||||
|
link = [f"{host_url}devices/{code}"]
|
||||||
|
cw.writerow(link)
|
||||||
|
|
||||||
|
return self.response_csv(data, "links.csv")
|
||||||
|
|
||||||
|
def erasure(self):
|
||||||
|
template = self.build_erasure_certificate()
|
||||||
|
res = flask_weasyprint.render_pdf(
|
||||||
|
flask_weasyprint.HTML(string=template),
|
||||||
|
download_filename='erasure-certificate.pdf',
|
||||||
|
)
|
||||||
|
insert_hash(res.data)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def build_erasure_certificate(self):
|
||||||
|
erasures = []
|
||||||
|
for device in self.find_devices():
|
||||||
|
if isinstance(device, Computer):
|
||||||
|
for privacy in device.privacy:
|
||||||
|
erasures.append(privacy)
|
||||||
|
elif isinstance(device, DataStorage):
|
||||||
|
if device.privacy:
|
||||||
|
erasures.append(device.privacy)
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'title': 'Erasure Certificate',
|
||||||
|
'erasures': tuple(erasures),
|
||||||
|
'url_pdf': '',
|
||||||
|
}
|
||||||
|
return flask.render_template('inventory/erasure.html', **params)
|
||||||
|
|
||||||
|
|
||||||
devices.add_url_rule('/action/add/', view_func=NewActionView.as_view('action_add'))
|
devices.add_url_rule('/action/add/', view_func=NewActionView.as_view('action_add'))
|
||||||
|
devices.add_url_rule('/action/trade/add/', view_func=NewTradeView.as_view('trade_add'))
|
||||||
devices.add_url_rule(
|
devices.add_url_rule(
|
||||||
'/action/allocate/add/', view_func=NewAllocateView.as_view('allocate_add')
|
'/action/allocate/add/', view_func=NewAllocateView.as_view('allocate_add')
|
||||||
)
|
)
|
||||||
devices.add_url_rule(
|
devices.add_url_rule(
|
||||||
'/action/datawipe/add/', view_func=NewDataWipeView.as_view('datawipe_add')
|
'/action/datawipe/add/', view_func=NewDataWipeView.as_view('datawipe_add')
|
||||||
)
|
)
|
||||||
|
devices.add_url_rule(
|
||||||
|
'/lot/<string:lot_id>/trade-document/add/',
|
||||||
|
view_func=NewTradeDocumentView.as_view('trade_document_add'),
|
||||||
|
)
|
||||||
devices.add_url_rule('/device/', view_func=DeviceListView.as_view('devicelist'))
|
devices.add_url_rule('/device/', view_func=DeviceListView.as_view('devicelist'))
|
||||||
devices.add_url_rule(
|
devices.add_url_rule(
|
||||||
'/device/<string:id>/', view_func=DeviceDetailView.as_view('device_details')
|
'/device/<string:id>/', view_func=DeviceDetailView.as_view('device_details')
|
||||||
|
@ -424,3 +619,6 @@ devices.add_url_rule(
|
||||||
'/tag/devices/<int:id>/del/',
|
'/tag/devices/<int:id>/del/',
|
||||||
view_func=TagUnlinkDeviceView.as_view('tag_devices_del'),
|
view_func=TagUnlinkDeviceView.as_view('tag_devices_del'),
|
||||||
)
|
)
|
||||||
|
devices.add_url_rule(
|
||||||
|
'/export/<string:export_id>/', view_func=ExportsView.as_view('export')
|
||||||
|
)
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
var show_allocate_form = $("#allocateModal").data('show-action-form');
|
var show_allocate_form = $("#allocateModal").data('show-action-form');
|
||||||
var show_datawipe_form = $("#datawipeModal").data('show-action-form');
|
var show_datawipe_form = $("#datawipeModal").data('show-action-form');
|
||||||
|
var show_trade_form = $("#tradeLotModal").data('show-action-form');
|
||||||
if (show_allocate_form != "None") {
|
if (show_allocate_form != "None") {
|
||||||
$("#allocateModal .btn-primary").show();
|
$("#allocateModal .btn-primary").show();
|
||||||
newAllocate(show_allocate_form);
|
newAllocate(show_allocate_form);
|
||||||
} else if (show_datawipe_form != "None") {
|
} else if (show_datawipe_form != "None") {
|
||||||
$("#datawipeModal .btn-primary").show();
|
$("#datawipeModal .btn-primary").show();
|
||||||
newDataWipe(show_datawipe_form);
|
newDataWipe(show_datawipe_form);
|
||||||
|
} else if (show_trade_form != "None") {
|
||||||
|
$("#tradeLotModal .btn-primary").show();
|
||||||
|
newTrade(show_trade_form);
|
||||||
} else {
|
} else {
|
||||||
$(".deviceSelect").on("change", deviceSelect);
|
$(".deviceSelect").on("change", deviceSelect);
|
||||||
}
|
}
|
||||||
|
@ -15,6 +19,7 @@ $(document).ready(function() {
|
||||||
|
|
||||||
function deviceSelect() {
|
function deviceSelect() {
|
||||||
var devices_count = $(".deviceSelect").filter(':checked').length;
|
var devices_count = $(".deviceSelect").filter(':checked').length;
|
||||||
|
get_device_list();
|
||||||
if (devices_count == 0) {
|
if (devices_count == 0) {
|
||||||
$("#addingLotModal .pol").show();
|
$("#addingLotModal .pol").show();
|
||||||
$("#addingLotModal .btn-primary").hide();
|
$("#addingLotModal .btn-primary").hide();
|
||||||
|
@ -50,23 +55,48 @@ function deviceSelect() {
|
||||||
$("#datawipeModal .btn-primary").show();
|
$("#datawipeModal .btn-primary").show();
|
||||||
|
|
||||||
$("#addingTagModal .pol").hide();
|
$("#addingTagModal .pol").hide();
|
||||||
|
$("#addingTagModal .btn-primary").show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeTag() {
|
function removeTag() {
|
||||||
var devices = $(".deviceSelect").filter(':checked');
|
var devices = $(".deviceSelect").filter(':checked');
|
||||||
var devices_id = $.map(devices, function(x) { return $(x).attr('data')});
|
var devices_id = $.map(devices, function(x) { return $(x).attr('data')});
|
||||||
console.log(devices_id);
|
|
||||||
if (devices_id.length > 0) {
|
if (devices_id.length > 0) {
|
||||||
var url = "/inventory/tag/devices/"+devices_id[0]+"/del/";
|
var url = "/inventory/tag/devices/"+devices_id[0]+"/del/";
|
||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addTag() {
|
||||||
|
deviceSelect();
|
||||||
|
$("#addingTagModal").click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function newTrade(action) {
|
||||||
|
var title = "Trade "
|
||||||
|
var user_to = $("#user_to").data("email");
|
||||||
|
var user_from = $("#user_from").data("email");
|
||||||
|
if (action == 'user_from') {
|
||||||
|
title = 'Trade Incoming';
|
||||||
|
$("#user_to").attr('readonly', 'readonly');
|
||||||
|
$("#user_from").prop('readonly', false);
|
||||||
|
$("#user_from").val('');
|
||||||
|
$("#user_to").val(user_to);
|
||||||
|
} else if (action == 'user_to') {
|
||||||
|
title = 'Trade Outgoing';
|
||||||
|
$("#user_from").attr('readonly', 'readonly');
|
||||||
|
$("#user_to").prop('readonly', false);
|
||||||
|
$("#user_to").val('');
|
||||||
|
$("#user_from").val(user_from);
|
||||||
|
}
|
||||||
|
$("#tradeLotModal #title-action").html(title);
|
||||||
|
$("#activeTradeModal").click();
|
||||||
|
}
|
||||||
|
|
||||||
function newAction(action) {
|
function newAction(action) {
|
||||||
$("#actionModal #type").val(action);
|
$("#actionModal #type").val(action);
|
||||||
$("#actionModal #title-action").html(action);
|
$("#actionModal #title-action").html(action);
|
||||||
get_device_list();
|
|
||||||
deviceSelect();
|
deviceSelect();
|
||||||
$("#activeActionModal").click();
|
$("#activeActionModal").click();
|
||||||
}
|
}
|
||||||
|
@ -74,7 +104,6 @@ function newAction(action) {
|
||||||
function newAllocate(action) {
|
function newAllocate(action) {
|
||||||
$("#allocateModal #type").val(action);
|
$("#allocateModal #type").val(action);
|
||||||
$("#allocateModal #title-action").html(action);
|
$("#allocateModal #title-action").html(action);
|
||||||
get_device_list();
|
|
||||||
deviceSelect();
|
deviceSelect();
|
||||||
$("#activeAllocateModal").click();
|
$("#activeAllocateModal").click();
|
||||||
}
|
}
|
||||||
|
@ -82,7 +111,6 @@ function newAllocate(action) {
|
||||||
function newDataWipe(action) {
|
function newDataWipe(action) {
|
||||||
$("#datawipeModal #type").val(action);
|
$("#datawipeModal #type").val(action);
|
||||||
$("#datawipeModal #title-action").html(action);
|
$("#datawipeModal #title-action").html(action);
|
||||||
get_device_list();
|
|
||||||
deviceSelect();
|
deviceSelect();
|
||||||
$("#activeDatawipeModal").click();
|
$("#activeDatawipeModal").click();
|
||||||
}
|
}
|
||||||
|
@ -120,3 +148,14 @@ function get_device_list() {
|
||||||
description = $.map(list_devices, function(x) { return x }).join(", ");
|
description = $.map(list_devices, function(x) { return x }).join(", ");
|
||||||
$(".enumeration-devices").html(description);
|
$(".enumeration-devices").html(description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function export_file(type_file) {
|
||||||
|
var devices = $(".deviceSelect").filter(':checked');
|
||||||
|
var devices_id = $.map(devices, function(x) { return $(x).attr('data-device-dhid')}).join(",");
|
||||||
|
if (devices_id){
|
||||||
|
var url = "/inventory/export/"+type_file+"/?ids="+devices_id;
|
||||||
|
window.location.href = url;
|
||||||
|
} else {
|
||||||
|
$("#exportAlertModal").click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
<input type="submit" class="btn btn-primary d-none" value="Save changes" />
|
<input type="submit" class="btn btn-primary" style="display: none;" value="Save changes" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
<div class="modal fade" id="exportErrorModal" tabindex="-1" style="display: none;" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Error export</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<p class="text-danger pol">
|
||||||
|
You need select first some device for use export file
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="modal fade" id="allocateModal" tabindex="-1" style="display: none;" aria-hidden="true"
|
<div class="modal fade" id="allocateModal" tabindex="-1" style="display: none;" aria-hidden="true"
|
||||||
data-show-action-form="{{ form_new_allocate.type.data }}">
|
data-show-action-form="{{ form_new_allocate.check_valid() }}">
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-dialog modal-lg">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="modal fade" id="datawipeModal" tabindex="-1" style="display: none;" aria-hidden="true"
|
<div class="modal fade" id="datawipeModal" tabindex="-1" style="display: none;" aria-hidden="true"
|
||||||
data-show-action-form="{{ form_new_datawipe.type.data }}">
|
data-show-action-form="{{ form_new_datawipe.check_valid() }}">
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-dialog modal-lg">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
|
|
||||||
|
|
|
@ -68,185 +68,243 @@
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body pt-3" style="min-height: 650px;">
|
<div class="card-body pt-3" style="min-height: 650px;">
|
||||||
<!-- Bordered Tabs -->
|
<!-- Bordered Tabs -->
|
||||||
|
{% if lot and not lot.is_temporary %}
|
||||||
|
<ul class="nav nav-tabs nav-tabs-bordered">
|
||||||
|
|
||||||
<div class="btn-group dropdown ml-1">
|
<li class="nav-item">
|
||||||
{% if lot and lot.is_temporary and not lot.devices %}
|
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#devices-list">Devices</button>
|
||||||
<button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="modal" data-bs-target="#btnRemoveLots">
|
</li>
|
||||||
<i class="bi bi-trash"></i>
|
|
||||||
Remove Lot
|
<li class="nav-item">
|
||||||
<span class="caret"></span>
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#trade-documents-list">Documents</button>
|
||||||
</button>
|
</li>
|
||||||
{% else %}
|
|
||||||
<button id="btnLots" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
</ul>
|
||||||
<i class="bi bi-folder2"></i>
|
{% endif %}
|
||||||
Lots
|
<div class="tab-content pt-5">
|
||||||
<span class="caret"></span>
|
<div id="devices-list" class="tab-pane fade devices-list active show">
|
||||||
</button>
|
|
||||||
{% endif %}
|
<div class="btn-group dropdown ml-1">
|
||||||
<ul class="dropdown-menu" aria-labelledby="btnLots">
|
<button id="btnLots" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<li>
|
<i class="bi bi-folder2"></i>
|
||||||
<a href="javascript:void()" class="dropdown-item" data-bs-toggle="modal" data-bs-target="#addingLotModal">
|
Lots
|
||||||
|
<span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<span class="d-none" id="activeTradeModal" data-bs-toggle="modal" data-bs-target="#tradeLotModal"></span>
|
||||||
|
<ul class="dropdown-menu" aria-labelledby="btnLots">
|
||||||
|
{% if lot and lot.is_temporary and not lot.devices %}
|
||||||
|
<li>
|
||||||
|
<a href="javascript:newAction('Use')" class="dropdown-item"
|
||||||
|
data-bs-toggle="modal" data-bs-target="#btnRemoveLots">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
Remove Lot
|
||||||
|
<span class="caret"></span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
<li>
|
||||||
|
<a href="javascript:void()" class="dropdown-item" data-bs-toggle="modal" data-bs-target="#addingLotModal">
|
||||||
|
<i class="bi bi-plus"></i>
|
||||||
|
Add selected Devices to a lot
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="javascript:void()" class="dropdown-item" data-bs-toggle="modal" data-bs-target="#removeLotModal">
|
||||||
|
<i class="bi bi-x"></i>
|
||||||
|
Remove selected devices from a lot
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% if lot.is_temporary %}
|
||||||
|
<li>
|
||||||
|
<a href="javascript:newTrade('user_from')" class="dropdown-item">
|
||||||
|
<i class="bi bi-plus"></i>
|
||||||
|
Add supplier
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="javascript:newTrade('user_to')" class="dropdown-item">
|
||||||
|
<i class="bi bi-plus"></i>
|
||||||
|
Add receiver
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if lot and not lot.is_temporary %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_for('inventory.devices.trade_document_add', lot_id=lot.id)}}" class="dropdown-item">
|
||||||
|
<i class="bi bi-plus"></i>
|
||||||
|
Add new document
|
||||||
|
<span class="caret"></span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="btn-group dropdown ml-1" uib-dropdown="">
|
||||||
|
<button id="btnActions" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<i class="bi bi-plus"></i>
|
<i class="bi bi-plus"></i>
|
||||||
Add selected Devices to a lot
|
New Actions
|
||||||
</a>
|
</button>
|
||||||
</li>
|
<span class="d-none" id="activeActionModal" data-bs-toggle="modal" data-bs-target="#actionModal"></span>
|
||||||
<li>
|
<span class="d-none" id="activeAllocateModal" data-bs-toggle="modal" data-bs-target="#allocateModal"></span>
|
||||||
<a href="javascript:void()" class="dropdown-item" data-bs-toggle="modal" data-bs-target="#removeLotModal">
|
<span class="d-none" id="activeDatawipeModal" data-bs-toggle="modal" data-bs-target="#datawipeModal"></span>
|
||||||
<i class="bi bi-x"></i>
|
<ul class="dropdown-menu" aria-labelledby="btnActions">
|
||||||
Remove selected devices from a lot
|
<li>
|
||||||
</a>
|
Status actions
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
<li>
|
||||||
</div>
|
<a href="javascript:newAction('Recycling')" class="dropdown-item">
|
||||||
|
<i class="bi bi-recycle"></i>
|
||||||
|
Recycling
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="javascript:newAction('Use')" class="dropdown-item">
|
||||||
|
<i class="bi bi-play-circle-fill"></i>
|
||||||
|
Use
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="javascript:newAction('Refurbish')" class="dropdown-item">
|
||||||
|
<i class="bi bi-tools"></i>
|
||||||
|
Refurbish
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="javascript:newAction('Management')" class="dropdown-item">
|
||||||
|
<i class="bi bi-mastodon"></i>
|
||||||
|
Management
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Allocation
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="javascript:newAllocate('Allocate')" class="dropdown-item">
|
||||||
|
<i class="bi bi-house-fill"></i>
|
||||||
|
Allocate
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="javascript:newAllocate('Deallocate')" class="dropdown-item">
|
||||||
|
<i class="bi bi-house"></i>
|
||||||
|
Deallocate
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Physical actions
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="javascript:newAction('ToPrepare')" class="dropdown-item">
|
||||||
|
<i class="bi bi-tools"></i>
|
||||||
|
ToPrepare
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="javascript:newAction('Prepare')" class="dropdown-item">
|
||||||
|
<i class="bi bi-egg"></i>
|
||||||
|
Prepare
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="javascript:newDataWipe('DataWipe')" class="dropdown-item">
|
||||||
|
<i class="bi bi-eraser-fill"></i>
|
||||||
|
DataWipe
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="javascript:newAction('ToRepair')" class="dropdown-item">
|
||||||
|
<i class="bi bi-screwdriver"></i>
|
||||||
|
ToRepair
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="javascript:newAction('Ready')" class="dropdown-item">
|
||||||
|
<i class="bi bi-check2-all"></i>
|
||||||
|
Ready
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="btn-group dropdown ml-1" uib-dropdown="">
|
<div class="btn-group dropdown ml-1" uib-dropdown="">
|
||||||
<button id="btnActions" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
<button id="btnExport" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<i class="bi bi-plus"></i>
|
<i class="bi bi-reply"></i>
|
||||||
New Actions
|
Exports
|
||||||
</button>
|
</button>
|
||||||
<span class="d-none" id="activeActionModal" data-bs-toggle="modal" data-bs-target="#actionModal"></span>
|
<span class="d-none" id="exportAlertModal" data-bs-toggle="modal" data-bs-target="#exportErrorModal"></span>
|
||||||
<span class="d-none" id="activeAllocateModal" data-bs-toggle="modal" data-bs-target="#allocateModal"></span>
|
<ul class="dropdown-menu" aria-labelledby="btnExport">
|
||||||
<span class="d-none" id="activeDatawipeModal" data-bs-toggle="modal" data-bs-target="#datawipeModal"></span>
|
<li>
|
||||||
<ul class="dropdown-menu" aria-labelledby="btnActions">
|
<a href="javascript:export_file('devices')" class="dropdown-item">
|
||||||
<li>
|
<i class="bi bi-file-spreadsheet"></i>
|
||||||
Status actions
|
Devices Spreadsheet
|
||||||
</li>
|
</a>
|
||||||
<li>
|
</li>
|
||||||
<a href="javascript:newAction('Recycling')" class="dropdown-item">
|
<li>
|
||||||
<i class="bi bi-recycle"></i>
|
<a href="javascript:export_file('metrics')" class="dropdown-item">
|
||||||
Recycling
|
<i class="bi bi-file-spreadsheet"></i>
|
||||||
</a>
|
Metrics Spreadsheet
|
||||||
</li>
|
</a>
|
||||||
<li>
|
</li>
|
||||||
<a href="javascript:newAction('Use')" class="dropdown-item">
|
<li>
|
||||||
<i class="bi bi-play-circle-fill"></i>
|
<a href="javascript:export_file('links')" class="dropdown-item">
|
||||||
Use
|
<i class="bi bi-link-45deg"></i>
|
||||||
</a>
|
Public Links
|
||||||
</li>
|
</a>
|
||||||
<li>
|
</li>
|
||||||
<a href="javascript:newAction('Refurbish')" class="dropdown-item">
|
<li>
|
||||||
<i class="bi bi-tools"></i>
|
<a href="javascript:export_file('certificates')" class="dropdown-item">
|
||||||
Refurbish
|
<i class="bi bi-eraser-fill"></i>
|
||||||
</a>
|
Erasure Certificate
|
||||||
</li>
|
</a>
|
||||||
<li>
|
</li>
|
||||||
<a href="javascript:newAction('Management')" class="dropdown-item">
|
</ul>
|
||||||
<i class="bi bi-mastodon"></i>
|
</div>
|
||||||
Management
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Allocation
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="javascript:newAllocate('Allocate')" class="dropdown-item">
|
|
||||||
<i class="bi bi-house-fill"></i>
|
|
||||||
Allocate
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="javascript:newAllocate('Deallocate')" class="dropdown-item">
|
|
||||||
<i class="bi bi-house"></i>
|
|
||||||
Deallocate
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Physical actions
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="javascript:newAction('ToPrepare')" class="dropdown-item">
|
|
||||||
<i class="bi bi-tools"></i>
|
|
||||||
ToPrepare
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="javascript:newAction('Prepare')" class="dropdown-item">
|
|
||||||
<i class="bi bi-egg"></i>
|
|
||||||
Prepare
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="javascript:newDataWipe('DataWipe')" class="dropdown-item">
|
|
||||||
<i class="bi bi-eraser-fill"></i>
|
|
||||||
DataWipe
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="javascript:newAction('ToRepair')" class="dropdown-item">
|
|
||||||
<i class="bi bi-screwdriver"></i>
|
|
||||||
ToRepair
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="javascript:newAction('Ready')" class="dropdown-item">
|
|
||||||
<i class="bi bi-check2-all"></i>
|
|
||||||
Ready
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="btn-group dropdown ml-1" uib-dropdown="">
|
<div class="btn-group dropdown ml-1" uib-dropdown="">
|
||||||
<button id="btnExport" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
<button id="btnTags" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<i class="bi bi-reply"></i>
|
<i class="bi bi-tag"></i>
|
||||||
Exports
|
Tags
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu" aria-labelledby="btnExport">
|
<ul class="dropdown-menu" aria-labelledby="btnTags">
|
||||||
<li>
|
<li>
|
||||||
<a href="#" class="dropdown-item">
|
<a href="javascript:addTag()" class="dropdown-item" data-bs-toggle="modal" data-bs-target="#addingTagModal">
|
||||||
TODO: Not implemented
|
<i class="bi bi-plus"></i>
|
||||||
</a>
|
Add Tag to selected Devices
|
||||||
</li>
|
</a>
|
||||||
</ul>
|
</li>
|
||||||
</div>
|
<li>
|
||||||
|
<a href="javascript:removeTag()" class="dropdown-item">
|
||||||
|
<i class="bi bi-x"></i>
|
||||||
|
Remove Tag from selected Devices
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="btn-group dropdown ml-1" uib-dropdown="">
|
<div class="btn-group dropdown ml-1" uib-dropdown="">
|
||||||
<button id="btnTags" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
<button id="btnSnapshot" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<i class="bi bi-tag"></i>
|
<i class="bi bi-tag"></i>
|
||||||
Tags
|
New Device
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu" aria-labelledby="btnTags">
|
<ul class="dropdown-menu" aria-labelledby="btnSnapshot">
|
||||||
<li>
|
<li>
|
||||||
<a href="javascript:void()" class="dropdown-item" data-bs-toggle="modal" data-bs-target="#addingTagModal">
|
<a href="{{ url_for('inventory.devices.upload_snapshot') }}" class="dropdown-item">
|
||||||
<i class="bi bi-plus"></i>
|
<i class="bi bi-plus"></i>
|
||||||
Add Tag to selected Devices
|
Upload a new Snapshot
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="javascript:removeTag()" class="dropdown-item">
|
<a href="{{ url_for('inventory.devices.device_add') }}" class="dropdown-item">
|
||||||
<i class="bi bi-x"></i>
|
<i class="bi bi-plus"></i>
|
||||||
Remove Tag from selected Devices
|
Create a new Device
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="btn-group dropdown ml-1" uib-dropdown="">
|
<div class="tab-content pt-2">
|
||||||
<button id="btnSnapshot" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
|
||||||
<i class="bi bi-tag"></i>
|
|
||||||
New Device
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu" aria-labelledby="btnSnapshot">
|
|
||||||
<li>
|
|
||||||
<a href="{{ url_for('inventory.devices.upload_snapshot') }}" class="dropdown-item">
|
|
||||||
<i class="bi bi-plus"></i>
|
|
||||||
Upload a new Snapshot
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="{{ url_for('inventory.devices.device_add') }}" class="dropdown-item">
|
|
||||||
<i class="bi bi-plus"></i>
|
|
||||||
Create a new Device
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tab-content pt-2">
|
<h5 class="card-title">Computers</h5>
|
||||||
|
|
||||||
<div class="tab-pane fade show active profile-overview" id="profile-overview">
|
|
||||||
|
|
||||||
<h5 class="card-title">Computers</h5>
|
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -293,8 +351,40 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
</div><!-- End Bordered Tabs -->
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if lot and not lot.is_temporary %}
|
||||||
|
<div id="trade-documents-list" class="tab-pane fade trade-documents-list">
|
||||||
|
<h5 class="card-title">Documents</h5>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">File</th>
|
||||||
|
<th scope="col" data-type="date" data-format="DD-MM-YYYY">Uploaded on</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for doc in lot.trade.documents %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{% if doc.url.to_text() %}
|
||||||
|
<a href="{{ doc.url.to_text() }}" target="_blank">{{ doc.file_name}}</a>
|
||||||
|
{% else %}
|
||||||
|
{{ doc.file_name}}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ doc.created.strftime('%H:%M %d-%m-%Y')}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div><!-- End Bordered Tabs -->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -308,6 +398,8 @@
|
||||||
{% include "inventory/actions.html" %}
|
{% include "inventory/actions.html" %}
|
||||||
{% include "inventory/allocate.html" %}
|
{% include "inventory/allocate.html" %}
|
||||||
{% include "inventory/data_wipe.html" %}
|
{% include "inventory/data_wipe.html" %}
|
||||||
|
{% include "inventory/trade.html" %}
|
||||||
|
{% include "inventory/alert_export_error.html" %}
|
||||||
|
|
||||||
<!-- CDN -->
|
<!-- CDN -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/simple-datatables@latest"></script>
|
<script src="https://cdn.jsdelivr.net/npm/simple-datatables@latest"></script>
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
{% extends "documents/layout.html" %}
|
||||||
|
{% block body %}
|
||||||
|
<div>
|
||||||
|
<h2>Summary</h2>
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>S/N Data Storage</th>
|
||||||
|
<th>Type of erasure</th>
|
||||||
|
<th>Result</th>
|
||||||
|
<th>Date</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for erasure in erasures %}
|
||||||
|
<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>Computer where was erase:</dt>
|
||||||
|
<dd>Title: {{ erasure.parent.__format__('ts') }}</dd>
|
||||||
|
<dd>DevicehubID: {{ erasure.parent.devicehub_id }}</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.devicehub_id }}</dd>
|
||||||
|
<dd>Hid: {{ erasure.device.parent.hid }}</dd>
|
||||||
|
<dd>Tags: {{ erasure.device.parent.tags }}</dd>
|
||||||
|
|
||||||
|
<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 %}
|
||||||
|
</div>
|
||||||
|
<div class="no-page-break">
|
||||||
|
<h2>Glossary</h2>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,62 @@
|
||||||
|
{% if form_new_trade %}
|
||||||
|
<div class="modal fade" id="tradeLotModal" tabindex="-1" style="display: none;" aria-hidden="true"
|
||||||
|
data-show-action-form="{{ form_new_trade.check_valid() }}">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">New Action <span id="title-action"></span></h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="{{ url_for('inventory.devices.trade_add') }}" method="post">
|
||||||
|
{{ form_new_trade.csrf_token }}
|
||||||
|
<div class="modal-body">
|
||||||
|
{% for field in form_new_trade %}
|
||||||
|
{% if field != form_new_trade.csrf_token %}
|
||||||
|
{% if field == form_new_trade.devices %}
|
||||||
|
{{ field }}
|
||||||
|
{% elif field == form_new_trade.lot %}
|
||||||
|
{{ field }}
|
||||||
|
{% elif field == form_new_trade.type %}
|
||||||
|
{{ field }}
|
||||||
|
{% else %}
|
||||||
|
<div class="col-12">
|
||||||
|
{{ field.label(class_="form-label") }}
|
||||||
|
{% if field == form_new_trade.confirm %}
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
{{ field(class_="form-check-input") }}
|
||||||
|
<small class="text-muted">{{ field.description }}</small>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{{ field(class_="form-control") }}
|
||||||
|
<small class="text-muted">{{ field.description }}</small>
|
||||||
|
{% endif %}
|
||||||
|
{% if field.errors %}
|
||||||
|
<p class="text-danger">
|
||||||
|
{% for error in field.errors %}
|
||||||
|
{{ error }}<br/>
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
<input type="submit" class="btn btn-primary" value="Create" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="modal fade" id="tradeLotModal" tabindex="-1" style="display: none;" aria-hidden="true"
|
||||||
|
data-show-action-form="None">
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
|
@ -0,0 +1,65 @@
|
||||||
|
{% extends "ereuse_devicehub/base_site.html" %}
|
||||||
|
{% block main %}
|
||||||
|
|
||||||
|
<div class="pagetitle">
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<nav>
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<!-- TODO@slamora replace with lot list URL when exists -->
|
||||||
|
<li class="breadcrumb-item"><a href="#TODO-lot-list">Lots</a></li>
|
||||||
|
<li class="breadcrumb-item">Trade Document</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
</div><!-- End Page Title -->
|
||||||
|
|
||||||
|
<section class="section profile">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xl-4">
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
|
<div class="pt-4 pb-2">
|
||||||
|
<h5 class="card-title text-center pb-0 fs-4">{{ title }}</h5>
|
||||||
|
{% if form.form_errors %}
|
||||||
|
<p class="text-danger">
|
||||||
|
{% for error in form.form_errors %}
|
||||||
|
{{ error }}<br/>
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="{{ url_for('inventory.devices.trade_document_add', lot_id=form._lot.id) }}" method="post"
|
||||||
|
class="row g-3 needs-validation" enctype="multipart/form-data">
|
||||||
|
{{ form.csrf_token }}
|
||||||
|
{% for field in form %}
|
||||||
|
{% if field != form.csrf_token %}
|
||||||
|
<div class="col-12">
|
||||||
|
{{ field.label(class_="form-label") }}
|
||||||
|
{{ field }}
|
||||||
|
<small class="text-muted">{{ field.description }}</small>
|
||||||
|
{% endif %}
|
||||||
|
{% if field.errors %}
|
||||||
|
<p class="text-danger">
|
||||||
|
{% for error in field.errors %}
|
||||||
|
{{ error }}<br/>
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
<a href="{{ url_for('inventory.devices.lotdevicelist', lot_id=form._lot.id) }}" class="btn btn-danger">Cancel</a>
|
||||||
|
<button class="btn btn-primary" type="submit">Save</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock main %}
|
Reference in New Issue