Merge branch 'testing' into changes/4025-new-hid
This commit is contained in:
commit
39f19f676b
29
CHANGELOG.md
29
CHANGELOG.md
|
@ -6,6 +6,35 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
ml).
|
||||
|
||||
## testing
|
||||
- [added] #414 add new vars in the settings file for wb.
|
||||
|
||||
## [2.5.0] - 2022-11-30
|
||||
- [added] #407 erasure section with tabs in top.
|
||||
- [added] #411 add new generic device as Other.
|
||||
- [changed] #409 add backend pagination instead of javascript.
|
||||
- [changed] #410 change teh top search for advanced search.
|
||||
- [fixed] #412 show in snapshots log, type upload correctly.
|
||||
- [fixed] #413 put order in documents.
|
||||
- [fixed] #415 put prefix of lot in result of search.
|
||||
|
||||
## [2.4.3] - 2022-11-18
|
||||
- [added] #386 add registration module.
|
||||
- [added] #387 add template settings for Secure Erasure.
|
||||
- [added] #397 add obada standard export.
|
||||
- [added] #402 add reset password module.
|
||||
- [added] #406 add orphans disks page.
|
||||
- [changed] #391 add dhid in table and export of Erasure section.
|
||||
- [changed] #395 change response for the new api to workbench.
|
||||
- [changed] #396 modularize commands.
|
||||
- [fixed] #388 lock update different motherboard with the same id.
|
||||
- [fixed] #389 some datastorage without placeholder.
|
||||
- [fixed] #390 fix image in form edit device.
|
||||
- [fixed] #398 placeholder in new components.
|
||||
- [fixed] #399 add api_host in config.
|
||||
- [fixed] #401 db_host need to be api address.
|
||||
- [fixed] #403 change delimiter in obada export.
|
||||
- [fixed] #404 javascript select all devices.
|
||||
- [fixed] #405 update pillow.
|
||||
|
||||
## [2.4.2] - 2022-10-18
|
||||
- [added] #373 Enhancement - UX Lots.
|
||||
|
|
42
docs/conf.py
42
docs/conf.py
|
@ -30,7 +30,6 @@ from teal.enums import Country, Currency, Layouts, Subdivision
|
|||
from teal.marshmallow import EnumField
|
||||
|
||||
from ereuse_devicehub.marshmallow import NestedOn
|
||||
from ereuse_devicehub.resources.schemas import Thing
|
||||
|
||||
project = 'Devicehub'
|
||||
copyright = '2020, eReuse.org team'
|
||||
|
@ -56,7 +55,7 @@ extensions = [
|
|||
'sphinx.ext.viewcode',
|
||||
'sphinxcontrib.plantuml',
|
||||
'sphinx.ext.autosectionlabel',
|
||||
'sphinx.ext.autodoc'
|
||||
'sphinx.ext.autodoc',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
|
@ -126,15 +125,12 @@ latex_elements = {
|
|||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#
|
||||
# 'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#
|
||||
# 'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#
|
||||
# 'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#
|
||||
# 'figure_align': 'htbp',
|
||||
|
@ -144,18 +140,20 @@ latex_elements = {
|
|||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'Devicehub.tex', 'Devicehub Documentation',
|
||||
'eReuse.org team', 'manual'),
|
||||
(
|
||||
master_doc,
|
||||
'Devicehub.tex',
|
||||
'Devicehub Documentation',
|
||||
'eReuse.org team',
|
||||
'manual',
|
||||
),
|
||||
]
|
||||
|
||||
# -- Options for manual page output ------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'devicehub', 'Devicehub Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
man_pages = [(master_doc, 'devicehub', 'Devicehub Documentation', [author], 1)]
|
||||
|
||||
# -- Options for Texinfo output ----------------------------------------------
|
||||
|
||||
|
@ -163,9 +161,15 @@ man_pages = [
|
|||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'Devicehub', 'Devicehub Documentation',
|
||||
author, 'Devicehub', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
(
|
||||
master_doc,
|
||||
'Devicehub',
|
||||
'Devicehub Documentation',
|
||||
author,
|
||||
'Devicehub',
|
||||
'One line description of project.',
|
||||
'Miscellaneous',
|
||||
),
|
||||
]
|
||||
|
||||
# -- Extension configuration -------------------------------------------------
|
||||
|
@ -199,6 +203,7 @@ class DhlistDirective(Directive):
|
|||
This requires :py:class:`ereuse_devicehub.resources.schemas.SchemaMeta`.
|
||||
You will find in that module more information.
|
||||
"""
|
||||
|
||||
has_content = False
|
||||
|
||||
# Definition of passed-in options
|
||||
|
@ -216,7 +221,7 @@ class DhlistDirective(Directive):
|
|||
|
||||
sections = []
|
||||
sections.append(self.links(things)) # Make index
|
||||
for thng in things: # type: Thing
|
||||
for thng in things:
|
||||
# Generate a section for each class, with a title,
|
||||
# fields description and a paragraph
|
||||
section = n.section(ids=[self._id(thng)])
|
||||
|
@ -228,7 +233,9 @@ class DhlistDirective(Directive):
|
|||
for key, f in thng._own:
|
||||
name = n.field_name(text=f.data_key or key)
|
||||
body = [
|
||||
self.parse('{} {}'.format(self.type(f), f.metadata.get('description', '')))
|
||||
self.parse(
|
||||
'{} {}'.format(self.type(f), f.metadata.get('description', ''))
|
||||
)
|
||||
]
|
||||
if isinstance(f, EnumField):
|
||||
body.append(self._parse_enum_field(f))
|
||||
|
@ -244,6 +251,7 @@ class DhlistDirective(Directive):
|
|||
|
||||
def _parse_enum_field(self, f):
|
||||
from ereuse_devicehub.resources.device import states
|
||||
|
||||
if issubclass(f.enum, (Subdivision, Currency, Country, Layouts, states.State)):
|
||||
return self.parse(f.enum.__doc__)
|
||||
else:
|
||||
|
@ -298,7 +306,7 @@ class DhlistDirective(Directive):
|
|||
|
||||
def parse(self, text) -> n.container:
|
||||
"""Parses text possibly containing ReST stuff and adds it in
|
||||
a node."""
|
||||
a node."""
|
||||
p = n.container('')
|
||||
self.state.nested_parse(StringList(string2lines(inspect.cleandoc(text))), 0, p)
|
||||
return p
|
||||
|
|
|
@ -1 +1 @@
|
|||
__version__ = "2.4.2"
|
||||
__version__ = "2.5.0"
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from distutils.version import StrictVersion
|
||||
from itertools import chain
|
||||
from typing import Set
|
||||
|
||||
from decouple import config
|
||||
from teal.auth import TokenAuth
|
||||
|
@ -44,7 +43,7 @@ class DevicehubConfig(Config):
|
|||
import_resource(metric_def),
|
||||
),
|
||||
)
|
||||
PASSWORD_SCHEMES = {'pbkdf2_sha256'} # type: Set[str]
|
||||
PASSWORD_SCHEMES = {'pbkdf2_sha256'}
|
||||
SECRET_KEY = config('SECRET_KEY')
|
||||
DB_USER = config('DB_USER', 'dhub')
|
||||
DB_PASSWORD = config('DB_PASSWORD', 'ereuse')
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import itertools
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Set
|
||||
|
||||
import click
|
||||
import click_spinner
|
||||
|
@ -109,7 +108,7 @@ class Dummy:
|
|||
files = tuple(Path(__file__).parent.joinpath('files').iterdir())
|
||||
print('done.')
|
||||
sample_pc = None # We treat this one as a special sample for demonstrations
|
||||
pcs = set() # type: Set[int]
|
||||
pcs = set()
|
||||
with click.progressbar(files, label='Creating devices...'.ljust(28)) as bar:
|
||||
for path in bar:
|
||||
with path.open() as f:
|
||||
|
|
|
@ -50,11 +50,15 @@ from ereuse_devicehub.resources.device.models import (
|
|||
Keyboard,
|
||||
Laptop,
|
||||
MemoryCardReader,
|
||||
Monitor,
|
||||
Mouse,
|
||||
Other,
|
||||
Placeholder,
|
||||
Projector,
|
||||
Server,
|
||||
Smartphone,
|
||||
Tablet,
|
||||
TelevisionSet,
|
||||
)
|
||||
from ereuse_devicehub.resources.documents.models import DataWipeDocument
|
||||
from ereuse_devicehub.resources.enums import Severity
|
||||
|
@ -81,16 +85,23 @@ DEVICES = {
|
|||
],
|
||||
"Mobile, tablet & smartphone": [
|
||||
"All Mobile",
|
||||
"Mobile",
|
||||
"Tablet",
|
||||
"Smartphone",
|
||||
"Cellphone",
|
||||
],
|
||||
"Drives & Storage": [
|
||||
"All DataStorage",
|
||||
"HardDrives",
|
||||
"HardDrive",
|
||||
"SolidStageDrive",
|
||||
],
|
||||
"Accessories": [
|
||||
"All Accessories",
|
||||
"Mouse",
|
||||
"MemoryCardReader",
|
||||
"SAI",
|
||||
"Keyboard",
|
||||
],
|
||||
"Other Devices": ["Other"],
|
||||
}
|
||||
|
||||
COMPUTERS = ['Desktop', 'Laptop', 'Server', 'Computer']
|
||||
|
@ -98,6 +109,8 @@ COMPUTERS = ['Desktop', 'Laptop', 'Server', 'Computer']
|
|||
MONITORS = ["ComputerMonitor", "Monitor", "TelevisionSet", "Projector"]
|
||||
MOBILE = ["Mobile", "Tablet", "Smartphone", "Cellphone"]
|
||||
STORAGE = ["HardDrive", "SolidStateDrive"]
|
||||
ACCESSORIES = ["Mouse", "MemoryCardReader", "SAI", "Keyboard"]
|
||||
OTHERS = ["Other"]
|
||||
|
||||
|
||||
class AdvancedSearchForm(FlaskForm):
|
||||
|
@ -170,7 +183,7 @@ class FilterForm(FlaskForm):
|
|||
|
||||
# Generic Filters
|
||||
if "All Devices" == self.device_type:
|
||||
filter_type = COMPUTERS + MONITORS + MOBILE
|
||||
filter_type = COMPUTERS + MONITORS + MOBILE + OTHERS
|
||||
|
||||
elif "All Computers" == self.device_type:
|
||||
filter_type = COMPUTERS
|
||||
|
@ -184,6 +197,9 @@ class FilterForm(FlaskForm):
|
|||
elif "All DataStorage" == self.device_type:
|
||||
filter_type = STORAGE
|
||||
|
||||
elif "All Accessories" == self.device_type:
|
||||
filter_type = ACCESSORIES
|
||||
|
||||
if filter_type:
|
||||
self.devices = self.devices.filter(Device.type.in_(filter_type))
|
||||
|
||||
|
@ -374,10 +390,14 @@ class NewDeviceForm(FlaskForm):
|
|||
"Tablet": Tablet,
|
||||
"Cellphone": Cellphone,
|
||||
"ComputerMonitor": ComputerMonitor,
|
||||
"Monitor": Monitor,
|
||||
"TelevisionSet": TelevisionSet,
|
||||
"Projector": Projector,
|
||||
"Mouse": Mouse,
|
||||
"Keyboard": Keyboard,
|
||||
"SAI": SAI,
|
||||
"MemoryCardReader": MemoryCardReader,
|
||||
"Other": Other,
|
||||
}
|
||||
|
||||
def reset_from_obj(self):
|
||||
|
|
|
@ -55,15 +55,26 @@ devices = Blueprint('inventory', __name__, url_prefix='/inventory')
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
PER_PAGE = 20
|
||||
|
||||
|
||||
class DeviceListMixin(GenericMixin):
|
||||
template_name = 'inventory/device_list.html'
|
||||
|
||||
def get_context(self, lot_id=None, all_devices=False):
|
||||
super().get_context()
|
||||
|
||||
page = int(request.args.get('page', 1))
|
||||
per_page = int(request.args.get('per_page', PER_PAGE))
|
||||
filter = request.args.get('filter', "All+Computers")
|
||||
# import pdb; pdb.set_trace()
|
||||
|
||||
lots = self.context['lots']
|
||||
form_filter = FilterForm(lots, lot_id, all_devices=all_devices)
|
||||
devices = form_filter.search()
|
||||
devices = form_filter.search().paginate(page=page, per_page=per_page)
|
||||
devices.first = per_page * devices.page - per_page + 1
|
||||
devices.last = len(devices.items) + devices.first - 1
|
||||
|
||||
lot = None
|
||||
form_transfer = ''
|
||||
form_delivery = ''
|
||||
|
@ -92,6 +103,7 @@ class DeviceListMixin(GenericMixin):
|
|||
'tags': self.get_user_tags(),
|
||||
'list_devices': self.get_selected_devices(form_new_action),
|
||||
'all_devices': all_devices,
|
||||
'filter': filter,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -115,15 +127,43 @@ class DeviceListMixin(GenericMixin):
|
|||
class ErasureListView(DeviceListMixin):
|
||||
template_name = 'inventory/erasure_list.html'
|
||||
|
||||
def dispatch_request(self):
|
||||
def dispatch_request(self, orphans=0):
|
||||
self.get_context()
|
||||
self.get_devices()
|
||||
self.get_devices(orphans)
|
||||
return flask.render_template(self.template_name, **self.context)
|
||||
|
||||
def get_devices(self):
|
||||
def get_devices(self, orphans):
|
||||
page = int(request.args.get('page', 1))
|
||||
per_page = int(request.args.get('per_page', PER_PAGE))
|
||||
|
||||
erasure = EraseBasic.query.filter_by(author=g.user).order_by(
|
||||
EraseBasic.created.desc()
|
||||
)
|
||||
if orphans:
|
||||
schema = app.config.get('SCHEMA')
|
||||
_user = g.user.id
|
||||
sql = f"""
|
||||
select action.id from {schema}.action as action
|
||||
inner join {schema}.erase_basic as erase
|
||||
on action.id=erase.id
|
||||
inner join {schema}.device as device
|
||||
on device.id=action.parent_id
|
||||
inner join {schema}.placeholder as placeholder
|
||||
on placeholder.binding_id=device.id
|
||||
where (action.parent_id is null or placeholder.kangaroo=true)
|
||||
and action.author_id='{_user}'
|
||||
"""
|
||||
ids = (e[0] for e in db.session.execute(sql))
|
||||
erasure = (
|
||||
EraseBasic.query.filter(EraseBasic.id.in_(ids))
|
||||
.filter_by(author=g.user)
|
||||
.order_by(EraseBasic.created.desc())
|
||||
)
|
||||
self.context['orphans'] = True
|
||||
|
||||
erasure = erasure.paginate(page=page, per_page=per_page)
|
||||
erasure.first = per_page * erasure.page - per_page + 1
|
||||
erasure.last = len(erasure.items) + erasure.first - 1
|
||||
self.context['erasure'] = erasure
|
||||
|
||||
|
||||
|
@ -1174,43 +1214,17 @@ class SnapshotListView(GenericMixin):
|
|||
return flask.render_template(self.template_name, **self.context)
|
||||
|
||||
def get_snapshots_log(self):
|
||||
page = int(request.args.get('page', 1))
|
||||
per_page = int(request.args.get('per_page', PER_PAGE))
|
||||
|
||||
snapshots_log = SnapshotsLog.query.filter(
|
||||
SnapshotsLog.owner == g.user
|
||||
).order_by(SnapshotsLog.created.desc())
|
||||
logs = {}
|
||||
for snap in snapshots_log:
|
||||
try:
|
||||
system_uuid = snap.snapshot.device.system_uuid or ''
|
||||
except AttributeError:
|
||||
system_uuid = ''
|
||||
|
||||
if snap.snapshot_uuid not in logs:
|
||||
logs[snap.snapshot_uuid] = {
|
||||
'sid': snap.sid,
|
||||
'snapshot_uuid': snap.snapshot_uuid,
|
||||
'version': snap.version,
|
||||
'device': snap.get_device(),
|
||||
'system_uuid': system_uuid,
|
||||
'status': snap.get_status(),
|
||||
'severity': snap.severity,
|
||||
'created': snap.created,
|
||||
'type_device': snap.get_type_device(),
|
||||
'original_dhid': snap.get_original_dhid(),
|
||||
'new_device': snap.get_new_device(),
|
||||
}
|
||||
continue
|
||||
|
||||
if snap.created > logs[snap.snapshot_uuid]['created']:
|
||||
logs[snap.snapshot_uuid]['created'] = snap.created
|
||||
|
||||
if snap.severity > logs[snap.snapshot_uuid]['severity']:
|
||||
logs[snap.snapshot_uuid]['severity'] = snap.severity
|
||||
logs[snap.snapshot_uuid]['status'] = snap.get_status()
|
||||
|
||||
result = sorted(logs.values(), key=lambda d: d['created'])
|
||||
result.reverse()
|
||||
|
||||
return result
|
||||
snapshots_log = snapshots_log.paginate(page=page, per_page=per_page)
|
||||
snapshots_log.first = per_page * snapshots_log.page - per_page + 1
|
||||
snapshots_log.last = len(snapshots_log.items) + snapshots_log.first - 1
|
||||
return snapshots_log
|
||||
|
||||
|
||||
class SnapshotDetailView(GenericMixin):
|
||||
|
@ -1340,10 +1354,17 @@ class PlaceholderLogListView(GenericMixin):
|
|||
return flask.render_template(self.template_name, **self.context)
|
||||
|
||||
def get_placeholders_log(self):
|
||||
page = int(request.args.get('page', 1))
|
||||
per_page = int(request.args.get('per_page', PER_PAGE))
|
||||
|
||||
placeholder_log = PlaceholdersLog.query.filter(
|
||||
PlaceholdersLog.owner == g.user
|
||||
).order_by(PlaceholdersLog.created.desc())
|
||||
|
||||
placeholder_log = placeholder_log.paginate(page=page, per_page=per_page)
|
||||
placeholder_log.first = per_page * placeholder_log.page - per_page + 1
|
||||
placeholder_log.last = len(placeholder_log.items) + placeholder_log.first - 1
|
||||
|
||||
return placeholder_log
|
||||
|
||||
|
||||
|
@ -1452,3 +1473,7 @@ devices.add_url_rule(
|
|||
devices.add_url_rule(
|
||||
'/device/erasure/', view_func=ErasureListView.as_view('device_erasure_list')
|
||||
)
|
||||
devices.add_url_rule(
|
||||
'/device/erasure/<int:orphans>/',
|
||||
view_func=ErasureListView.as_view('device_erasure_list_orphans'),
|
||||
)
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
"""device other
|
||||
|
||||
Revision ID: 410aadae7652
|
||||
Revises: d65745749e34
|
||||
Create Date: 2022-11-29 12:00:40.272121
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
from alembic import context, op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '410aadae7652'
|
||||
down_revision = 'd65745749e34'
|
||||
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(
|
||||
'other',
|
||||
sa.Column('id', sa.BigInteger(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
['id'],
|
||||
[f'{get_inv()}.device.id'],
|
||||
),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
schema=f'{get_inv()}',
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_table('other', schema=f'{get_inv()}')
|
|
@ -0,0 +1,35 @@
|
|||
"""add settings_version to snapshots
|
||||
|
||||
Revision ID: af038a8a388c
|
||||
Revises: 410aadae7652
|
||||
Create Date: 2022-11-30 16:21:05.768024
|
||||
|
||||
"""
|
||||
import citext
|
||||
import sqlalchemy as sa
|
||||
from alembic import context, op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'af038a8a388c'
|
||||
down_revision = '410aadae7652'
|
||||
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.add_column(
|
||||
'snapshot',
|
||||
sa.Column('settings_version', citext.CIText(), nullable=True),
|
||||
schema=f'{get_inv()}',
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_column('snapshot', 'settings_version', schema=f'{get_inv()}')
|
|
@ -4,7 +4,7 @@ from contextlib import suppress
|
|||
from datetime import datetime
|
||||
from fractions import Fraction
|
||||
from math import hypot
|
||||
from typing import Iterator, List, Optional, Type, TypeVar
|
||||
from typing import Iterator, List, Optional, TypeVar
|
||||
|
||||
import dateutil.parser
|
||||
from ereuse_utils import getter, text
|
||||
|
@ -404,7 +404,7 @@ class Computer(Device):
|
|||
chassis value.
|
||||
"""
|
||||
|
||||
COMPONENTS = list(Component.__subclasses__()) # type: List[Type[Component]]
|
||||
COMPONENTS = list(Component.__subclasses__())
|
||||
COMPONENTS.remove(Motherboard)
|
||||
|
||||
def __init__(self, node: dict) -> None:
|
||||
|
|
|
@ -73,11 +73,25 @@ class SnapshotsLog(Thing):
|
|||
snapshots = []
|
||||
for s in self.snapshot.device.actions:
|
||||
if s == self.snapshot:
|
||||
continue
|
||||
break
|
||||
if s.type == self.snapshot.type:
|
||||
snapshots.append(s)
|
||||
return snapshots and 'Update' or 'New Device'
|
||||
|
||||
def get_system_uuid(self):
|
||||
try:
|
||||
return self.snapshot.device.system_uuid or ''
|
||||
except AttributeError:
|
||||
return ''
|
||||
|
||||
def get_version(self):
|
||||
if not self.snapshot:
|
||||
return self.version
|
||||
settings_version = self.snapshot.settings_version or ''
|
||||
settings_version = "".join([x[0] for x in settings_version.split(' ') if x])
|
||||
|
||||
return "{} ({})".format(self.version, settings_version)
|
||||
|
||||
|
||||
class PlaceholdersLog(Thing):
|
||||
"""A Placeholder log."""
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from datetime import datetime, timezone
|
||||
from typing import List
|
||||
|
||||
from ereuse_workbench.computer import Component, Computer, DataStorage
|
||||
from ereuse_workbench.computer import Computer, DataStorage
|
||||
from ereuse_workbench.utils import Dumpeable
|
||||
|
||||
|
||||
|
@ -24,8 +23,8 @@ class Snapshot(Dumpeable):
|
|||
self.endTime = datetime.now(timezone.utc)
|
||||
self.closed = False
|
||||
self.elapsed = None
|
||||
self.device = None # type: Computer
|
||||
self.components = None # type: List[Component]
|
||||
self.device = None
|
||||
self.components = None
|
||||
self._storages = None
|
||||
|
||||
def computer(self):
|
||||
|
|
|
@ -676,6 +676,7 @@ class Snapshot(JoinedWithOneDeviceMixin, ActionWithOneDevice):
|
|||
of time it took to complete.
|
||||
"""
|
||||
sid = Column(CIText(), nullable=True)
|
||||
settings_version = Column(CIText(), nullable=True)
|
||||
is_server_erase = Column(Boolean(), nullable=True)
|
||||
|
||||
def get_last_lifetimes(self):
|
||||
|
|
|
@ -453,6 +453,7 @@ class Snapshot(ActionWithOneDevice):
|
|||
'Order is preserved, so the component num 0 when'
|
||||
'submitting is the component num 0 when returning it back.',
|
||||
)
|
||||
settings_version = String(required=False)
|
||||
|
||||
@validates_schema
|
||||
def validate_workbench_version(self, data: dict):
|
||||
|
|
|
@ -4,7 +4,11 @@ from teal.resource import Converters, Resource
|
|||
|
||||
from ereuse_devicehub.resources.device import schemas
|
||||
from ereuse_devicehub.resources.device.models import Manufacturer
|
||||
from ereuse_devicehub.resources.device.views import DeviceView, DeviceMergeView, ManufacturerView
|
||||
from ereuse_devicehub.resources.device.views import (
|
||||
DeviceMergeView,
|
||||
DeviceView,
|
||||
ManufacturerView,
|
||||
)
|
||||
|
||||
|
||||
class DeviceDef(Resource):
|
||||
|
@ -13,25 +17,42 @@ class DeviceDef(Resource):
|
|||
ID_CONVERTER = Converters.string
|
||||
AUTH = False # We manage this at each view
|
||||
|
||||
def __init__(self, app,
|
||||
import_name=__name__,
|
||||
static_folder='static',
|
||||
static_url_path=None,
|
||||
template_folder='templates',
|
||||
url_prefix=None,
|
||||
subdomain=None,
|
||||
url_defaults=None,
|
||||
root_path=None,
|
||||
cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
|
||||
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
|
||||
url_prefix, subdomain, url_defaults, root_path, cli_commands)
|
||||
def __init__(
|
||||
self,
|
||||
app,
|
||||
import_name=__name__,
|
||||
static_folder='static',
|
||||
static_url_path=None,
|
||||
template_folder='templates',
|
||||
url_prefix=None,
|
||||
subdomain=None,
|
||||
url_defaults=None,
|
||||
root_path=None,
|
||||
cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(),
|
||||
):
|
||||
super().__init__(
|
||||
app,
|
||||
import_name,
|
||||
static_folder,
|
||||
static_url_path,
|
||||
template_folder,
|
||||
url_prefix,
|
||||
subdomain,
|
||||
url_defaults,
|
||||
root_path,
|
||||
cli_commands,
|
||||
)
|
||||
|
||||
device_merge = DeviceMergeView.as_view('merge-devices', definition=self, auth=app.auth)
|
||||
device_merge = DeviceMergeView.as_view(
|
||||
'merge-devices', definition=self, auth=app.auth
|
||||
)
|
||||
|
||||
if self.AUTH:
|
||||
device_merge = app.auth.requires_auth(device_merge)
|
||||
|
||||
path = '/<{value}:dev1_id>/merge/<{value}:dev2_id>'.format(value=self.ID_CONVERTER.value)
|
||||
path = '/<{value}:dev1_id>/merge/<{value}:dev2_id>'.format(
|
||||
value=self.ID_CONVERTER.value
|
||||
)
|
||||
|
||||
# self.add_url_rule(path, view_func=device_merge, methods={'POST'})
|
||||
|
||||
|
@ -40,11 +61,31 @@ class ComputerDef(DeviceDef):
|
|||
VIEW = None
|
||||
SCHEMA = schemas.Computer
|
||||
|
||||
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None,
|
||||
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
|
||||
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
|
||||
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
|
||||
url_prefix, subdomain, url_defaults, root_path, cli_commands)
|
||||
def __init__(
|
||||
self,
|
||||
app,
|
||||
import_name=__name__,
|
||||
static_folder=None,
|
||||
static_url_path=None,
|
||||
template_folder=None,
|
||||
url_prefix=None,
|
||||
subdomain=None,
|
||||
url_defaults=None,
|
||||
root_path=None,
|
||||
cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(),
|
||||
):
|
||||
super().__init__(
|
||||
app,
|
||||
import_name,
|
||||
static_folder,
|
||||
static_url_path,
|
||||
template_folder,
|
||||
url_prefix,
|
||||
subdomain,
|
||||
url_defaults,
|
||||
root_path,
|
||||
cli_commands,
|
||||
)
|
||||
|
||||
|
||||
class DesktopDef(ComputerDef):
|
||||
|
@ -66,11 +107,31 @@ class MonitorDef(DeviceDef):
|
|||
VIEW = None
|
||||
SCHEMA = schemas.Monitor
|
||||
|
||||
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None,
|
||||
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
|
||||
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
|
||||
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
|
||||
url_prefix, subdomain, url_defaults, root_path, cli_commands)
|
||||
def __init__(
|
||||
self,
|
||||
app,
|
||||
import_name=__name__,
|
||||
static_folder=None,
|
||||
static_url_path=None,
|
||||
template_folder=None,
|
||||
url_prefix=None,
|
||||
subdomain=None,
|
||||
url_defaults=None,
|
||||
root_path=None,
|
||||
cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(),
|
||||
):
|
||||
super().__init__(
|
||||
app,
|
||||
import_name,
|
||||
static_folder,
|
||||
static_url_path,
|
||||
template_folder,
|
||||
url_prefix,
|
||||
subdomain,
|
||||
url_defaults,
|
||||
root_path,
|
||||
cli_commands,
|
||||
)
|
||||
|
||||
|
||||
class ComputerMonitorDef(MonitorDef):
|
||||
|
@ -83,15 +144,40 @@ class TelevisionSetDef(MonitorDef):
|
|||
SCHEMA = schemas.TelevisionSet
|
||||
|
||||
|
||||
class ProjectorDef(MonitorDef):
|
||||
VIEW = None
|
||||
SCHEMA = schemas.Projector
|
||||
|
||||
|
||||
class MobileDef(DeviceDef):
|
||||
VIEW = None
|
||||
SCHEMA = schemas.Mobile
|
||||
|
||||
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None,
|
||||
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
|
||||
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
|
||||
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
|
||||
url_prefix, subdomain, url_defaults, root_path, cli_commands)
|
||||
def __init__(
|
||||
self,
|
||||
app,
|
||||
import_name=__name__,
|
||||
static_folder=None,
|
||||
static_url_path=None,
|
||||
template_folder=None,
|
||||
url_prefix=None,
|
||||
subdomain=None,
|
||||
url_defaults=None,
|
||||
root_path=None,
|
||||
cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(),
|
||||
):
|
||||
super().__init__(
|
||||
app,
|
||||
import_name,
|
||||
static_folder,
|
||||
static_url_path,
|
||||
template_folder,
|
||||
url_prefix,
|
||||
subdomain,
|
||||
url_defaults,
|
||||
root_path,
|
||||
cli_commands,
|
||||
)
|
||||
|
||||
|
||||
class SmartphoneDef(MobileDef):
|
||||
|
@ -113,11 +199,31 @@ class ComponentDef(DeviceDef):
|
|||
VIEW = None
|
||||
SCHEMA = schemas.Component
|
||||
|
||||
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None,
|
||||
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
|
||||
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
|
||||
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
|
||||
url_prefix, subdomain, url_defaults, root_path, cli_commands)
|
||||
def __init__(
|
||||
self,
|
||||
app,
|
||||
import_name=__name__,
|
||||
static_folder=None,
|
||||
static_url_path=None,
|
||||
template_folder=None,
|
||||
url_prefix=None,
|
||||
subdomain=None,
|
||||
url_defaults=None,
|
||||
root_path=None,
|
||||
cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(),
|
||||
):
|
||||
super().__init__(
|
||||
app,
|
||||
import_name,
|
||||
static_folder,
|
||||
static_url_path,
|
||||
template_folder,
|
||||
url_prefix,
|
||||
subdomain,
|
||||
url_defaults,
|
||||
root_path,
|
||||
cli_commands,
|
||||
)
|
||||
|
||||
|
||||
class GraphicCardDef(ComponentDef):
|
||||
|
@ -184,11 +290,31 @@ class ComputerAccessoryDef(DeviceDef):
|
|||
VIEW = None
|
||||
SCHEMA = schemas.ComputerAccessory
|
||||
|
||||
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None,
|
||||
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
|
||||
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
|
||||
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
|
||||
url_prefix, subdomain, url_defaults, root_path, cli_commands)
|
||||
def __init__(
|
||||
self,
|
||||
app,
|
||||
import_name=__name__,
|
||||
static_folder=None,
|
||||
static_url_path=None,
|
||||
template_folder=None,
|
||||
url_prefix=None,
|
||||
subdomain=None,
|
||||
url_defaults=None,
|
||||
root_path=None,
|
||||
cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(),
|
||||
):
|
||||
super().__init__(
|
||||
app,
|
||||
import_name,
|
||||
static_folder,
|
||||
static_url_path,
|
||||
template_folder,
|
||||
url_prefix,
|
||||
subdomain,
|
||||
url_defaults,
|
||||
root_path,
|
||||
cli_commands,
|
||||
)
|
||||
|
||||
|
||||
class MouseDef(ComputerAccessoryDef):
|
||||
|
@ -215,11 +341,31 @@ class NetworkingDef(DeviceDef):
|
|||
VIEW = None
|
||||
SCHEMA = schemas.Networking
|
||||
|
||||
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None,
|
||||
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
|
||||
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
|
||||
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
|
||||
url_prefix, subdomain, url_defaults, root_path, cli_commands)
|
||||
def __init__(
|
||||
self,
|
||||
app,
|
||||
import_name=__name__,
|
||||
static_folder=None,
|
||||
static_url_path=None,
|
||||
template_folder=None,
|
||||
url_prefix=None,
|
||||
subdomain=None,
|
||||
url_defaults=None,
|
||||
root_path=None,
|
||||
cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(),
|
||||
):
|
||||
super().__init__(
|
||||
app,
|
||||
import_name,
|
||||
static_folder,
|
||||
static_url_path,
|
||||
template_folder,
|
||||
url_prefix,
|
||||
subdomain,
|
||||
url_defaults,
|
||||
root_path,
|
||||
cli_commands,
|
||||
)
|
||||
|
||||
|
||||
class RouterDef(NetworkingDef):
|
||||
|
@ -246,11 +392,31 @@ class PrinterDef(DeviceDef):
|
|||
VIEW = None
|
||||
SCHEMA = schemas.Printer
|
||||
|
||||
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None,
|
||||
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
|
||||
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
|
||||
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
|
||||
url_prefix, subdomain, url_defaults, root_path, cli_commands)
|
||||
def __init__(
|
||||
self,
|
||||
app,
|
||||
import_name=__name__,
|
||||
static_folder=None,
|
||||
static_url_path=None,
|
||||
template_folder=None,
|
||||
url_prefix=None,
|
||||
subdomain=None,
|
||||
url_defaults=None,
|
||||
root_path=None,
|
||||
cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(),
|
||||
):
|
||||
super().__init__(
|
||||
app,
|
||||
import_name,
|
||||
static_folder,
|
||||
static_url_path,
|
||||
template_folder,
|
||||
url_prefix,
|
||||
subdomain,
|
||||
url_defaults,
|
||||
root_path,
|
||||
cli_commands,
|
||||
)
|
||||
|
||||
|
||||
class LabelPrinterDef(PrinterDef):
|
||||
|
@ -262,11 +428,31 @@ class SoundDef(DeviceDef):
|
|||
VIEW = None
|
||||
SCHEMA = schemas.Sound
|
||||
|
||||
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None,
|
||||
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
|
||||
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
|
||||
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
|
||||
url_prefix, subdomain, url_defaults, root_path, cli_commands)
|
||||
def __init__(
|
||||
self,
|
||||
app,
|
||||
import_name=__name__,
|
||||
static_folder=None,
|
||||
static_url_path=None,
|
||||
template_folder=None,
|
||||
url_prefix=None,
|
||||
subdomain=None,
|
||||
url_defaults=None,
|
||||
root_path=None,
|
||||
cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(),
|
||||
):
|
||||
super().__init__(
|
||||
app,
|
||||
import_name,
|
||||
static_folder,
|
||||
static_url_path,
|
||||
template_folder,
|
||||
url_prefix,
|
||||
subdomain,
|
||||
url_defaults,
|
||||
root_path,
|
||||
cli_commands,
|
||||
)
|
||||
|
||||
|
||||
class MicrophoneDef(SoundDef):
|
||||
|
@ -278,11 +464,31 @@ class VideoDef(DeviceDef):
|
|||
VIEW = None
|
||||
SCHEMA = schemas.Video
|
||||
|
||||
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None,
|
||||
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
|
||||
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
|
||||
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
|
||||
url_prefix, subdomain, url_defaults, root_path, cli_commands)
|
||||
def __init__(
|
||||
self,
|
||||
app,
|
||||
import_name=__name__,
|
||||
static_folder=None,
|
||||
static_url_path=None,
|
||||
template_folder=None,
|
||||
url_prefix=None,
|
||||
subdomain=None,
|
||||
url_defaults=None,
|
||||
root_path=None,
|
||||
cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(),
|
||||
):
|
||||
super().__init__(
|
||||
app,
|
||||
import_name,
|
||||
static_folder,
|
||||
static_url_path,
|
||||
template_folder,
|
||||
url_prefix,
|
||||
subdomain,
|
||||
url_defaults,
|
||||
root_path,
|
||||
cli_commands,
|
||||
)
|
||||
|
||||
|
||||
class VideoScalerDef(VideoDef):
|
||||
|
@ -299,11 +505,31 @@ class CookingDef(DeviceDef):
|
|||
VIEW = None
|
||||
SCHEMA = schemas.Cooking
|
||||
|
||||
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None,
|
||||
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
|
||||
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
|
||||
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
|
||||
url_prefix, subdomain, url_defaults, root_path, cli_commands)
|
||||
def __init__(
|
||||
self,
|
||||
app,
|
||||
import_name=__name__,
|
||||
static_folder=None,
|
||||
static_url_path=None,
|
||||
template_folder=None,
|
||||
url_prefix=None,
|
||||
subdomain=None,
|
||||
url_defaults=None,
|
||||
root_path=None,
|
||||
cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(),
|
||||
):
|
||||
super().__init__(
|
||||
app,
|
||||
import_name,
|
||||
static_folder,
|
||||
static_url_path,
|
||||
template_folder,
|
||||
url_prefix,
|
||||
subdomain,
|
||||
url_defaults,
|
||||
root_path,
|
||||
cli_commands,
|
||||
)
|
||||
|
||||
|
||||
class Mixer(CookingDef):
|
||||
|
@ -315,11 +541,31 @@ class DIYAndGardeningDef(DeviceDef):
|
|||
VIEW = None
|
||||
SCHEMA = schemas.DIYAndGardening
|
||||
|
||||
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None,
|
||||
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
|
||||
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
|
||||
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
|
||||
url_prefix, subdomain, url_defaults, root_path, cli_commands)
|
||||
def __init__(
|
||||
self,
|
||||
app,
|
||||
import_name=__name__,
|
||||
static_folder=None,
|
||||
static_url_path=None,
|
||||
template_folder=None,
|
||||
url_prefix=None,
|
||||
subdomain=None,
|
||||
url_defaults=None,
|
||||
root_path=None,
|
||||
cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(),
|
||||
):
|
||||
super().__init__(
|
||||
app,
|
||||
import_name,
|
||||
static_folder,
|
||||
static_url_path,
|
||||
template_folder,
|
||||
url_prefix,
|
||||
subdomain,
|
||||
url_defaults,
|
||||
root_path,
|
||||
cli_commands,
|
||||
)
|
||||
|
||||
|
||||
class DrillDef(DIYAndGardeningDef):
|
||||
|
@ -331,22 +577,62 @@ class PackOfScrewdriversDef(DIYAndGardeningDef):
|
|||
VIEW = None
|
||||
SCHEMA = schemas.PackOfScrewdrivers
|
||||
|
||||
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None,
|
||||
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
|
||||
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
|
||||
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
|
||||
url_prefix, subdomain, url_defaults, root_path, cli_commands)
|
||||
def __init__(
|
||||
self,
|
||||
app,
|
||||
import_name=__name__,
|
||||
static_folder=None,
|
||||
static_url_path=None,
|
||||
template_folder=None,
|
||||
url_prefix=None,
|
||||
subdomain=None,
|
||||
url_defaults=None,
|
||||
root_path=None,
|
||||
cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(),
|
||||
):
|
||||
super().__init__(
|
||||
app,
|
||||
import_name,
|
||||
static_folder,
|
||||
static_url_path,
|
||||
template_folder,
|
||||
url_prefix,
|
||||
subdomain,
|
||||
url_defaults,
|
||||
root_path,
|
||||
cli_commands,
|
||||
)
|
||||
|
||||
|
||||
class HomeDef(DeviceDef):
|
||||
VIEW = None
|
||||
SCHEMA = schemas.Home
|
||||
|
||||
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None,
|
||||
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
|
||||
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
|
||||
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
|
||||
url_prefix, subdomain, url_defaults, root_path, cli_commands)
|
||||
def __init__(
|
||||
self,
|
||||
app,
|
||||
import_name=__name__,
|
||||
static_folder=None,
|
||||
static_url_path=None,
|
||||
template_folder=None,
|
||||
url_prefix=None,
|
||||
subdomain=None,
|
||||
url_defaults=None,
|
||||
root_path=None,
|
||||
cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(),
|
||||
):
|
||||
super().__init__(
|
||||
app,
|
||||
import_name,
|
||||
static_folder,
|
||||
static_url_path,
|
||||
template_folder,
|
||||
url_prefix,
|
||||
subdomain,
|
||||
url_defaults,
|
||||
root_path,
|
||||
cli_commands,
|
||||
)
|
||||
|
||||
|
||||
class DehumidifierDef(HomeDef):
|
||||
|
@ -363,11 +649,31 @@ class RecreationDef(DeviceDef):
|
|||
VIEW = None
|
||||
SCHEMA = schemas.Recreation
|
||||
|
||||
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None,
|
||||
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None,
|
||||
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
|
||||
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
|
||||
url_prefix, subdomain, url_defaults, root_path, cli_commands)
|
||||
def __init__(
|
||||
self,
|
||||
app,
|
||||
import_name=__name__,
|
||||
static_folder=None,
|
||||
static_url_path=None,
|
||||
template_folder=None,
|
||||
url_prefix=None,
|
||||
subdomain=None,
|
||||
url_defaults=None,
|
||||
root_path=None,
|
||||
cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(),
|
||||
):
|
||||
super().__init__(
|
||||
app,
|
||||
import_name,
|
||||
static_folder,
|
||||
static_url_path,
|
||||
template_folder,
|
||||
url_prefix,
|
||||
subdomain,
|
||||
url_defaults,
|
||||
root_path,
|
||||
cli_commands,
|
||||
)
|
||||
|
||||
|
||||
class BikeDef(RecreationDef):
|
||||
|
@ -389,3 +695,35 @@ class ManufacturerDef(Resource):
|
|||
"""Loads the manufacturers to the database."""
|
||||
if exclude_schema != 'common':
|
||||
Manufacturer.add_all_to_session(db.session)
|
||||
|
||||
|
||||
class OtherDef(DeviceDef):
|
||||
VIEW = None
|
||||
SCHEMA = schemas.Computer
|
||||
SCHEMA = schemas.Other
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
app,
|
||||
import_name=__name__,
|
||||
static_folder=None,
|
||||
static_url_path=None,
|
||||
template_folder=None,
|
||||
url_prefix=None,
|
||||
subdomain=None,
|
||||
url_defaults=None,
|
||||
root_path=None,
|
||||
cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(),
|
||||
):
|
||||
super().__init__(
|
||||
app,
|
||||
import_name,
|
||||
static_folder,
|
||||
static_url_path,
|
||||
template_folder,
|
||||
url_prefix,
|
||||
subdomain,
|
||||
url_defaults,
|
||||
root_path,
|
||||
cli_commands,
|
||||
)
|
||||
|
|
|
@ -635,6 +635,14 @@ class Device(Thing):
|
|||
return self.binding.device.devicehub_id
|
||||
return self.devicehub_id
|
||||
|
||||
@property
|
||||
def my_partner(self):
|
||||
if self.placeholder and self.placeholder.binding:
|
||||
return self.placeholder.binding
|
||||
if self.binding:
|
||||
return self.binding.device
|
||||
return self
|
||||
|
||||
@property
|
||||
def get_updated(self):
|
||||
if self.placeholder and self.placeholder.binding:
|
||||
|
@ -1394,6 +1402,19 @@ class DataStorage(JoinedComponentTableMixin, Component):
|
|||
except LookupError:
|
||||
return None
|
||||
|
||||
@property
|
||||
def orphan(self):
|
||||
if not self.parent:
|
||||
return True
|
||||
|
||||
if self.parent.placeholder and self.parent.placeholder.kangaroo:
|
||||
return True
|
||||
|
||||
if self.parent.binding and self.parent.binding.kangaroo:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class HardDrive(DataStorage):
|
||||
pass
|
||||
|
@ -1684,3 +1705,11 @@ def create_code_tag(mapper, connection, device):
|
|||
|
||||
# from flask_sqlalchemy import event
|
||||
# event.listen(Device, 'after_insert', create_code_tag, propagate=True)
|
||||
|
||||
|
||||
class Other(Device):
|
||||
"""
|
||||
Used for put in there all devices than not have actualy a class
|
||||
"""
|
||||
|
||||
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||
|
|
|
@ -270,6 +270,10 @@ class TelevisionSet(Monitor):
|
|||
__doc__ = m.TelevisionSet.__doc__
|
||||
|
||||
|
||||
class Projector(Monitor):
|
||||
__doc__ = m.Projector.__doc__
|
||||
|
||||
|
||||
class Mobile(Device):
|
||||
__doc__ = m.Mobile.__doc__
|
||||
|
||||
|
@ -578,3 +582,7 @@ class Bike(Recreation):
|
|||
|
||||
class Racket(Recreation):
|
||||
pass
|
||||
|
||||
|
||||
class Other(Device):
|
||||
pass
|
||||
|
|
|
@ -306,7 +306,7 @@ class LotDeviceView(LotBaseChildrenView):
|
|||
dev_qry = Device.query.filter(Device.id.in_(ids)).filter(Device.owner == g.user)
|
||||
|
||||
for dev in dev_qry:
|
||||
if isinstance(dev, DataStorage) and dev.parent:
|
||||
if isinstance(dev, DataStorage) and not dev.orphan:
|
||||
continue
|
||||
devices.add(dev)
|
||||
|
||||
|
|
|
@ -172,7 +172,9 @@ class TradeDocument(Thing):
|
|||
return sorted(ev for ev in actions if ev.severity >= Severity.Warning)
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.id < other.id
|
||||
if self.id and other.id:
|
||||
return self.id < other.id
|
||||
return False
|
||||
|
||||
def __str__(self) -> str:
|
||||
return '{0.file_name}'.format(self)
|
||||
|
|
|
@ -48,12 +48,12 @@
|
|||
|
||||
/**
|
||||
* Search bar toggle
|
||||
*/
|
||||
if (select(".search-bar-toggle")) {
|
||||
on("click", ".search-bar-toggle", (e) => {
|
||||
select(".search-bar").classList.toggle("search-bar-show")
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Navbar links active state on scroll
|
||||
|
|
|
@ -8,6 +8,12 @@
|
|||
</div><!-- End Logo -->
|
||||
|
||||
<div class="search-bar">
|
||||
<form class="search-form d-flex align-items-center" method="get" action="/inventory/search/">
|
||||
<input class="" type="text" name="q" placeholder="Search" title="Enter search keyword">
|
||||
<button type="submit" title="Search"><i class="bi bi-search"></i></button>
|
||||
</form>
|
||||
</div><!-- End Search Bar -->
|
||||
<div class="search-bar d-none">
|
||||
<form class="search-form d-flex align-items-center" method="" id="SearchForm" action="#">
|
||||
<input class="dropdown-toggle" type="text" name="query" placeholder="Search" title="Enter search keyword"
|
||||
autocomplete="off" id="dropdownSearch" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
|
|
|
@ -46,7 +46,13 @@
|
|||
</optgroup>
|
||||
<optgroup label="Monitor">
|
||||
<option value="ComputerMonitor"
|
||||
{% if form.type.data == 'Monitor' %} selected="selected"{% endif %}>Computer Monitor</option>
|
||||
{% if form.type.data == 'ComputerMonitor' %} selected="selected"{% endif %}>Computer Monitor</option>
|
||||
<option value="Monitor"
|
||||
{% if form.type.data == 'Monitor' %} selected="selected"{% endif %}>Monitor</option>
|
||||
<option value="TelevisionSet"
|
||||
{% if form.type.data == 'TelevisionSet' %} selected="selected"{% endif %}>TelevisionSet</option>
|
||||
<option value="Projector"
|
||||
{% if form.type.data == 'Projector' %} selected="selected"{% endif %}>Projector</option>
|
||||
</optgroup>
|
||||
<optgroup label="Mobile">
|
||||
<option value="Smartphone"
|
||||
|
@ -66,6 +72,10 @@
|
|||
<option value="Keyboard"
|
||||
{% if form.type.data == 'Keyboard' %} selected="selected"{% endif %}>Keyboard</option>
|
||||
</optgroup>
|
||||
<optgroup label="Other Type of Device">
|
||||
<option value="Other"
|
||||
{% if form.type.data == 'Other' %} selected="selected"{% endif %}>Other</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<small class="text-muted form-text">Type of devices</small>
|
||||
{% if form.type.errors %}
|
||||
|
|
|
@ -335,6 +335,8 @@
|
|||
{% for f in form_filter %}
|
||||
{{ f }}
|
||||
{% endfor %}
|
||||
<input type="hidden" class="d-none" value="1" name="page" />
|
||||
<input type="hidden" class="d-none" value="{{ devices.per_page }}" name="per_page" />
|
||||
<input type="submit" class="ms-2 btn btn-primary" value="Filter" />
|
||||
</div>
|
||||
</form>
|
||||
|
@ -344,6 +346,38 @@
|
|||
<em>{{ form_filter.filter.data or "Computer" }}</em>
|
||||
</p>
|
||||
|
||||
<div class="dataTable-top" style="float: left;">
|
||||
<div class="dataTable-dropdown">
|
||||
<label>
|
||||
<select class="dataTable-selector">
|
||||
<option value="5"{% if devices.per_page == 5 %} selected="selected"{% endif %}>
|
||||
5
|
||||
</option>
|
||||
<option value="10"{% if devices.per_page == 10 %} selected="selected"{% endif %}>
|
||||
10
|
||||
</option>
|
||||
<option value="15"{% if devices.per_page == 15 %} selected="selected"{% endif %}>
|
||||
15
|
||||
</option>
|
||||
<option value="20"{% if devices.per_page == 20 %} selected="selected"{% endif %}>
|
||||
20
|
||||
</option>
|
||||
<option value="25"{% if devices.per_page == 25 %} selected="selected"{% endif %}>
|
||||
25
|
||||
</option>
|
||||
<option value="50"{% if devices.per_page == 50 %} selected="selected"{% endif %}>
|
||||
50
|
||||
</option>
|
||||
<option value="100"{% if devices.per_page == 100 %} selected="selected"{% endif %}>
|
||||
100
|
||||
</option>
|
||||
</select> entries per page
|
||||
</label>
|
||||
</div>
|
||||
<div class="dataTable-search">
|
||||
</div>
|
||||
</div>
|
||||
<div class="dataTable-container">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -362,7 +396,7 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for dev in devices %}
|
||||
{% for dev in devices.items %}
|
||||
{% if dev.placeholder and (not dev.parent_id or dev.parent.placeholder.kangaroo) %}
|
||||
<tr>
|
||||
<td>
|
||||
|
@ -421,6 +455,57 @@
|
|||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="dataTable-bottom">
|
||||
<div class="dataTable-info">
|
||||
Showing {{ devices.first }} to {{ devices.last }} of {{ devices.total }} entries
|
||||
</div>
|
||||
<nav class="dataTable-pagination">
|
||||
<ul class="dataTable-pagination-list">
|
||||
{% if devices.has_prev %}
|
||||
<li class="pager">
|
||||
{% if all_devices %}
|
||||
<a href="{{ url_for('inventory.alldevicelist', page=devices.prev_num, per_page=devices.per_page, filter=filter) }}">‹</a>
|
||||
{% elif lot %}
|
||||
<a href="{{ url_for('inventory.lotdevicelist', lot_id=lot.id, page=devices.prev_num, per_page=devices.per_page, filter=filter) }}">‹</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('inventory.devicelist', page=devices.prev_num, per_page=devices.per_page, filter=filter) }}">‹</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% for page in devices.iter_pages() %}
|
||||
{% if page %}
|
||||
{% if page == devices.page %}
|
||||
<li class="active"><a href="javascript:void()">{{ page }}</a></li>
|
||||
{% else %}
|
||||
<li class="">
|
||||
{% if all_devices %}
|
||||
<a href="{{ url_for('inventory.alldevicelist', page=page, per_page=devices.per_page, filter=filter) }}">
|
||||
{% elif lot %}
|
||||
<a href="{{ url_for('inventory.lotdevicelist', lot_id=lot.id, page=page, per_page=devices.per_page, filter=filter) }}">
|
||||
{% else %}
|
||||
<a href="{{ url_for('inventory.devicelist', page=page, per_page=devices.per_page, filter=filter) }}">
|
||||
{% endif %}
|
||||
{{ page }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if devices.has_next %}
|
||||
<li class="pager">
|
||||
{% if all_devices %}
|
||||
<a href="{{ url_for('inventory.alldevicelist', page=devices.next_num, per_page=devices.per_page, filter=filter) }}">›</a>
|
||||
{% elif lot %}
|
||||
<a href="{{ url_for('inventory.lotdevicelist', lot_id=lot.id, page=devices.next_num, per_page=devices.per_page, filter=filter) }}">›</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('inventory.devicelist', page=devices.next_num, per_page=devices.per_page, filter=filter) }}">›</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -592,10 +677,25 @@
|
|||
{% include "inventory/alert_lots_changes.html" %}
|
||||
|
||||
<!-- Custom Code -->
|
||||
<script>
|
||||
$(document).ready(() => {
|
||||
$(".dataTable-selector").on("change", function() {
|
||||
const per_page = $('.dataTable-selector').val();
|
||||
{% if all_devices %}
|
||||
window.location.href = "{{ url_for('inventory.alldevicelist', page=1) }}&filter={{ filter }}&per_page="+per_page;
|
||||
{% elif lot %}
|
||||
window.location.href = "{{ url_for('inventory.lotdevicelist', lot_id=lot.id, page=1) }}&filter={{ filter }}&per_page="+per_page;
|
||||
{% else %}
|
||||
window.location.href = "{{ url_for('inventory.devicelist', page=1) }}&filter={{ filter }}&per_page="+per_page;
|
||||
{% endif %}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
let table = new simpleDatatables.DataTable("table", {
|
||||
perPageSelect: [5, 10, 15, 20, 25, 50, 100],
|
||||
perPage: 20
|
||||
footer: false,
|
||||
paging: false,
|
||||
|
||||
})
|
||||
</script>
|
||||
{% if config['DEBUG'] %}
|
||||
|
|
|
@ -18,9 +18,54 @@
|
|||
|
||||
<div class="card">
|
||||
<div class="card-body pt-3" style="min-height: 650px;">
|
||||
<div class="tab-content pt-1">
|
||||
<ul class="nav nav-tabs nav-tabs-bordered">
|
||||
|
||||
<li class="nav-item">
|
||||
<a href="{{ url_for('inventory.device_erasure_list') }}" class="nav-link{% if not orphans %} active{% endif %}">
|
||||
All hard drives
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a href="{{ url_for('inventory.device_erasure_list_orphans', orphans=1) }}" class="nav-link{% if orphans %} active{% endif %}">
|
||||
Hard drives without device
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
<div class="tab-content pt-2">
|
||||
<div id="devices-list" class="tab-pane fade devices-list active show">
|
||||
<label class="btn btn-primary " for="SelectAllBTN"><input type="checkbox" id="SelectAllBTN" autocomplete="off"></label>
|
||||
{% if orphans %}
|
||||
<div class="btn-group dropdown ml-1">
|
||||
<button id="btnLots" type="button" onclick="processSelectedDevices()" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-folder2"></i>
|
||||
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" id="dropDownLotsSelector">
|
||||
<div class="row w-100">
|
||||
<div class="input-group mb-3 mx-2">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" id="basic-addon1"><i class="bi bi-search"></i></span>
|
||||
</div>
|
||||
<input type="text" class="form-control" id="lots-search" placeholder="search" aria-label="search" aria-describedby="basic-addon1">
|
||||
</div>
|
||||
</div>
|
||||
<h6 class="dropdown-header">Select lots where to store the selected devices</h6>
|
||||
<ul class="mx-3" id="LotsSelector"></ul>
|
||||
<li><hr /></li>
|
||||
<li>
|
||||
<a href="#" class="dropdown-item" id="ApplyDeviceLots">
|
||||
<i class="bi bi-check"></i>
|
||||
Apply
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="btn-group dropdown m-1" uib-dropdown="">
|
||||
<button id="btnExport" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-reply"></i>
|
||||
|
@ -42,8 +87,65 @@
|
|||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% if orphans %}
|
||||
<div class="btn-group dropdown m-1" uib-dropdown="">
|
||||
<button id="btnTags" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-tag"></i>
|
||||
Labels
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="btnTags">
|
||||
<li>
|
||||
<form id="print_labels" method="post" action="{{ url_for('labels.print_labels') }}">
|
||||
{% for f in form_print_labels %}
|
||||
{{ f }}
|
||||
{% endfor %}
|
||||
<a href="javascript:$('#print_labels').submit()" class="dropdown-item">
|
||||
<i class="bi bi-printer"></i>
|
||||
Print labels
|
||||
</a>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div id="select-devices-info" class="alert alert-info mb-0 mt-3 d-none" role="alert">
|
||||
If this text is showing is because there are an error
|
||||
</div>
|
||||
|
||||
<div class="tab-content pt-2">
|
||||
<div class="dataTable-top" style="float: left;">
|
||||
<div class="dataTable-dropdown">
|
||||
<label>
|
||||
<select class="dataTable-selector">
|
||||
<option value="5"{% if erasure.per_page == 5 %} selected="selected"{% endif %}>
|
||||
5
|
||||
</option>
|
||||
<option value="10"{% if erasure.per_page == 10 %} selected="selected"{% endif %}>
|
||||
10
|
||||
</option>
|
||||
<option value="15"{% if erasure.per_page == 15 %} selected="selected"{% endif %}>
|
||||
15
|
||||
</option>
|
||||
<option value="20"{% if erasure.per_page == 20 %} selected="selected"{% endif %}>
|
||||
20
|
||||
</option>
|
||||
<option value="25"{% if erasure.per_page == 25 %} selected="selected"{% endif %}>
|
||||
25
|
||||
</option>
|
||||
<option value="50"{% if erasure.per_page == 50 %} selected="selected"{% endif %}>
|
||||
50
|
||||
</option>
|
||||
<option value="100"{% if erasure.per_page == 100 %} selected="selected"{% endif %}>
|
||||
100
|
||||
</option>
|
||||
</select> entries per page
|
||||
</label>
|
||||
</div>
|
||||
<div class="dataTable-search">
|
||||
</div>
|
||||
</div>
|
||||
<div class="dataTable-container">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -59,10 +161,10 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for ac in erasure %}
|
||||
{% for ac in erasure.items %}
|
||||
<tr>
|
||||
<td>
|
||||
<input type="checkbox" class="deviceSelect" data="{{ ac.device.id }}"
|
||||
<input type="checkbox" class="deviceSelect" data="{{ ac.device.my_partner.id }}"
|
||||
data-device-type="{{ ac.device.type }}" data-device-manufacturer="{{ ac.device.manufacturer }}"
|
||||
data-device-dhid="{{ ac.device.dhid }}" data-device-vname="{{ ac.device.verbose_name }}"
|
||||
data-action-erasure="{{ ac.id }}"
|
||||
|
@ -74,11 +176,24 @@
|
|||
<td>
|
||||
{% if ac.device.phid() %}
|
||||
<a href="{{ url_for('inventory.device_details', id=ac.device.dhid)}}">
|
||||
{% if ac.device.get_type_logo() %}
|
||||
<i class="{{ ac.device.get_type_logo() }}" title="{{ ac.device.type }}"></i>
|
||||
{% endif %}
|
||||
{{ ac.device.serial_number.upper() }}
|
||||
</a>
|
||||
{% else %}
|
||||
{% if ac.device.get_type_logo() %}
|
||||
<i class="{{ ac.device.get_type_logo() }}" title="{{ ac.device.type }}"></i>
|
||||
{% endif %}
|
||||
{{ ac.device.serial_number.upper() }}
|
||||
{% endif %}
|
||||
{% if ac.device.my_partner.lots | length > 0 %}
|
||||
<h6 class="d-inline">
|
||||
{% for lot in ac.device.my_partner.get_lots_for_template() %}
|
||||
<span class="badge rounded-pill bg-light text-dark">{{ lot }}</span>
|
||||
{% endfor %}
|
||||
</h6>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if ac.device.phid() %}
|
||||
|
@ -91,7 +206,7 @@
|
|||
</td>
|
||||
<td>
|
||||
<a href="{{ url_for('inventory.export', export_id='snapshot') }}?id={{ ac.snapshot.uuid }}">
|
||||
{{ ac.snapshot.uuid }}
|
||||
{{ ac.snapshot.uuid }}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -115,135 +230,54 @@
|
|||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div class="dataTable-bottom">
|
||||
<div class="dataTable-info">
|
||||
Showing {{ erasure.first }} to {{ erasure.last }} of {{ erasure.total }} entries
|
||||
</div>
|
||||
<nav class="dataTable-pagination">
|
||||
<ul class="dataTable-pagination-list">
|
||||
{% if erasure.has_prev %}
|
||||
<li class="pager">
|
||||
{% if orphans %}
|
||||
<a href="{{ url_for('inventory.device_erasure_list_orphans', orphans=1, page=erasure.prev_num, per_page=erasure.per_page) }}">‹</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('inventory.device_erasure_list', page=erasure.prev_num, per_page=erasure.per_page) }}">‹</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% for page in erasure.iter_pages() %}
|
||||
{% if page %}
|
||||
{% if page == erasure.page %}
|
||||
<li class="active"><a href="javascript:void()">{{ page }}</a></li>
|
||||
{% else %}
|
||||
<li class="">
|
||||
{% if orphans %}
|
||||
<a href="{{ url_for('inventory.device_erasure_list_orphans', orphans=1, page=page, per_page=erasure.per_page) }}">
|
||||
{{ page }}
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('inventory.device_erasure_list', page=page, per_page=erasure.per_page) }}">
|
||||
{{ page }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if erasure.has_next %}
|
||||
<li class="pager">
|
||||
{% if orphans %}
|
||||
<a href="{{ url_for('inventory.device_erasure_list_orphans', orphans=1, page=erasure.next_num, per_page=erasure.per_page) }}">›</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('inventory.device_erasure_list', page=erasure.next_num, per_page=erasure.per_page) }}">›</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</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 %}
|
||||
<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>
|
||||
<div id="edit-transfer" class="tab-pane fade edit-transfer">
|
||||
<h5 class="card-title">Transfer</h5>
|
||||
<form method="post" action="{{ url_for('inventory.edit_transfer', lot_id=lot.id) }}" class="row g-3 needs-validation" novalidate>
|
||||
{{ form_transfer.csrf_token }}
|
||||
|
||||
{% for field in form_transfer %}
|
||||
{% if field != form_transfer.csrf_token %}
|
||||
<div class="col-12">
|
||||
{% if field != form_transfer.type %}
|
||||
{{ field.label(class_="form-label") }}
|
||||
{% if field == form_transfer.code %}
|
||||
<span class="text-danger">*</span>
|
||||
{% endif %}
|
||||
{{ 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>
|
||||
<div id="edit-delivery-note" class="tab-pane fade edit-delivery-note">
|
||||
<h5 class="card-title">Delivery Note</h5>
|
||||
<form method="post" action="{{ url_for('inventory.delivery_note', lot_id=lot.id) }}" class="row g-3 needs-validation" novalidate>
|
||||
{{ form_delivery.csrf_token }}
|
||||
|
||||
{% for field in form_delivery %}
|
||||
{% if field != form_delivery.csrf_token %}
|
||||
<div class="col-12">
|
||||
{% if field != form_delivery.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 %}
|
||||
|
||||
{% if lot.transfer and form_receiver.is_editable() %}
|
||||
<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>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
<div id="edit-receiver-note" class="tab-pane fade edit-receiver-note">
|
||||
<h5 class="card-title">Receiver Note</h5>
|
||||
<form method="post" action="{{ url_for('inventory.receiver_note', lot_id=lot.id) }}" class="row g-3 needs-validation" novalidate>
|
||||
{{ form_receiver.csrf_token }}
|
||||
|
||||
{% for field in form_receiver %}
|
||||
{% if field != form_receiver.csrf_token %}
|
||||
<div class="col-12">
|
||||
{% if field != form_receiver.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 %}
|
||||
|
||||
{% if lot.transfer and form_receiver.is_editable() %}
|
||||
<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>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div><!-- End Bordered Tabs -->
|
||||
</div>
|
||||
|
@ -255,21 +289,32 @@
|
|||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% include "inventory/lot_delete_modal.html" %}
|
||||
{% include "inventory/actions.html" %}
|
||||
{% include "inventory/allocate.html" %}
|
||||
{% include "inventory/data_wipe.html" %}
|
||||
{% include "inventory/trade.html" %}
|
||||
{% include "inventory/alert_export_error.html" %}
|
||||
{% include "inventory/alert_lots_changes.html" %}
|
||||
|
||||
<!-- Custom Code -->
|
||||
<script>
|
||||
$(document).ready(() => {
|
||||
$(".dataTable-selector").on("change", function() {
|
||||
const per_page = $('.dataTable-selector').val();
|
||||
{% if orphans %}
|
||||
window.location.href = "{{ url_for('inventory.device_erasure_list_orphans', orphans=1, page=1) }}&per_page="+per_page;
|
||||
{% else %}
|
||||
window.location.href = "{{ url_for('inventory.device_erasure_list', page=1) }}&per_page="+per_page;
|
||||
{% endif %}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
let table = new simpleDatatables.DataTable("table", {
|
||||
perPageSelect: [5, 10, 15, 20, 25, 50, 100],
|
||||
perPage: 20
|
||||
//perPageSelect: [5, 10, 15, 20, 25, 50, 100],
|
||||
//perPage: 20,
|
||||
footer: false,
|
||||
paging: false,
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
{% if config['DEBUG'] %}
|
||||
<script src="{{ url_for('static', filename='js/main_inventory.js') }}"></script>
|
||||
{% else %}
|
||||
|
|
|
@ -22,6 +22,38 @@
|
|||
<div class="tab-content pt-5">
|
||||
<div id="devices-list" class="tab-pane fade devices-list active show">
|
||||
<div class="tab-content pt-2">
|
||||
<div class="dataTable-top" style="float: left;">
|
||||
<div class="dataTable-dropdown">
|
||||
<label>
|
||||
<select class="dataTable-selector">
|
||||
<option value="5"{% if placeholders_log.per_page == 5 %} selected="selected"{% endif %}>
|
||||
5
|
||||
</option>
|
||||
<option value="10"{% if placeholders_log.per_page == 10 %} selected="selected"{% endif %}>
|
||||
10
|
||||
</option>
|
||||
<option value="15"{% if placeholders_log.per_page == 15 %} selected="selected"{% endif %}>
|
||||
15
|
||||
</option>
|
||||
<option value="20"{% if placeholders_log.per_page == 20 %} selected="selected"{% endif %}>
|
||||
20
|
||||
</option>
|
||||
<option value="25"{% if placeholders_log.per_page == 25 %} selected="selected"{% endif %}>
|
||||
25
|
||||
</option>
|
||||
<option value="50"{% if placeholders_log.per_page == 50 %} selected="selected"{% endif %}>
|
||||
50
|
||||
</option>
|
||||
<option value="100"{% if placeholders_log.per_page == 100 %} selected="selected"{% endif %}>
|
||||
100
|
||||
</option>
|
||||
</select> entries per page
|
||||
</label>
|
||||
</div>
|
||||
<div class="dataTable-search">
|
||||
</div>
|
||||
</div>
|
||||
<div class="dataTable-container">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -34,7 +66,7 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for log in placeholders_log %}
|
||||
{% for log in placeholders_log.items %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ log.phid }}
|
||||
|
@ -58,6 +90,38 @@
|
|||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="dataTable-bottom">
|
||||
<div class="dataTable-info">
|
||||
Showing {{ placeholders_log.first }} to {{ placeholders_log.last }} of {{ placeholders_log.total }} entries
|
||||
</div>
|
||||
<nav class="dataTable-pagination">
|
||||
<ul class="dataTable-pagination-list">
|
||||
{% if placeholders_log.has_prev %}
|
||||
<li class="pager">
|
||||
<a href="{{ url_for('inventory.placeholder_logs', page=placeholders_log.prev_num, per_page=placeholders_log.per_page) }}">‹</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% for page in placeholders_log.iter_pages() %}
|
||||
{% if page %}
|
||||
{% if page == placeholders_log.page %}
|
||||
<li class="active"><a href="javascript:void()">{{ page }}</a></li>
|
||||
{% else %}
|
||||
<li class="">
|
||||
<a href="{{ url_for('inventory.placeholder_logs', page=page, per_page=placeholders_log.per_page) }}">
|
||||
{{ page }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if placeholders_log.has_next %}
|
||||
<li class="pager">
|
||||
<a href="{{ url_for('inventory.placeholder_logs', page=placeholders_log.next_num, per_page=placeholders_log.per_page) }}">›</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -75,6 +139,18 @@
|
|||
|
||||
<!-- Custom Code -->
|
||||
<script>
|
||||
const table = new simpleDatatables.DataTable("table")
|
||||
$(document).ready(() => {
|
||||
$(".dataTable-selector").on("change", function() {
|
||||
const per_page = $('.dataTable-selector').val();
|
||||
window.location.href = "{{ url_for('inventory.placeholder_logs', page=1) }}&per_page="+per_page;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
let table = new simpleDatatables.DataTable("table", {
|
||||
footer: false,
|
||||
paging: false,
|
||||
|
||||
})
|
||||
</script>
|
||||
{% endblock main %}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
<div class="col-xl-12">
|
||||
|
||||
<div class="card">
|
||||
<div class="card d-none">
|
||||
<div class="tab-content pt-2">
|
||||
<div class="flex mt-4 mb-4">
|
||||
<form method="get" class="ms-4">
|
||||
|
@ -307,8 +307,8 @@
|
|||
</a>
|
||||
{% if dev.lots | length > 0 %}
|
||||
<h6 class="d-inline">
|
||||
{% for lot in dev.lots %}
|
||||
<span class="badge rounded-pill bg-light text-dark">{{ lot.name }}</span>
|
||||
{% for lot in dev.get_lots_for_template() %}
|
||||
<span class="badge rounded-pill bg-light text-dark">{{ lot }}</span>
|
||||
{% endfor %}
|
||||
</h6>
|
||||
{% endif %}
|
||||
|
|
|
@ -22,6 +22,38 @@
|
|||
<div class="tab-content pt-5">
|
||||
<div id="devices-list" class="tab-pane fade devices-list active show">
|
||||
<div class="tab-content pt-2">
|
||||
<div class="dataTable-top" style="float: left;">
|
||||
<div class="dataTable-dropdown">
|
||||
<label>
|
||||
<select class="dataTable-selector">
|
||||
<option value="5"{% if snapshots_log.per_page == 5 %} selected="selected"{% endif %}>
|
||||
5
|
||||
</option>
|
||||
<option value="10"{% if snapshots_log.per_page == 10 %} selected="selected"{% endif %}>
|
||||
10
|
||||
</option>
|
||||
<option value="15"{% if snapshots_log.per_page == 15 %} selected="selected"{% endif %}>
|
||||
15
|
||||
</option>
|
||||
<option value="20"{% if snapshots_log.per_page == 20 %} selected="selected"{% endif %}>
|
||||
20
|
||||
</option>
|
||||
<option value="25"{% if snapshots_log.per_page == 25 %} selected="selected"{% endif %}>
|
||||
25
|
||||
</option>
|
||||
<option value="50"{% if snapshots_log.per_page == 50 %} selected="selected"{% endif %}>
|
||||
50
|
||||
</option>
|
||||
<option value="100"{% if snapshots_log.per_page == 100 %} selected="selected"{% endif %}>
|
||||
100
|
||||
</option>
|
||||
</select> entries per page
|
||||
</label>
|
||||
</div>
|
||||
<div class="dataTable-search">
|
||||
</div>
|
||||
</div>
|
||||
<div class="dataTable-container">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -29,7 +61,6 @@
|
|||
<th scope="col">Snapshot UUID</th>
|
||||
<th scope="col">Version</th>
|
||||
<th scope="col">DHID</th>
|
||||
<th scope="col">System UUID</th>
|
||||
<th scope="col">Status</th>
|
||||
<th scope="col">Type Upload</th>
|
||||
<th scope="col">Type Device</th>
|
||||
|
@ -39,7 +70,7 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for snap in snapshots_log %}
|
||||
{% for snap in snapshots_log.items %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if snap.sid and snap.snapshot_uuid %}
|
||||
|
@ -56,29 +87,26 @@
|
|||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ snap.version }}
|
||||
{{ snap.get_version() }}
|
||||
</td>
|
||||
<td>
|
||||
{% if snap.device %}
|
||||
{% if snap.get_device() %}
|
||||
<a href="{{ url_for('inventory.device_details', id=snap.device) }}">
|
||||
{{ snap.device }}
|
||||
{{ snap.get_device() }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ snap.system_uuid }}
|
||||
{{ snap.get_status() }}
|
||||
</td>
|
||||
<td>
|
||||
{{ snap.status }}
|
||||
{{ snap.get_new_device() }}
|
||||
</td>
|
||||
<td>
|
||||
{{ snap.new_device }}
|
||||
{{ snap.get_type_device() }}
|
||||
</td>
|
||||
<td>
|
||||
{{ snap.type_device }}
|
||||
</td>
|
||||
<td>
|
||||
{{ snap.original_dhid }}
|
||||
{{ snap.get_original_dhid() }}
|
||||
</td>
|
||||
<td>{{ snap.created.strftime('%Y-%m-%d %H:%M') }}</td>
|
||||
<td>
|
||||
|
@ -93,6 +121,38 @@
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="dataTable-bottom">
|
||||
<div class="dataTable-info">
|
||||
Showing {{ snapshots_log.first }} to {{ snapshots_log.last }} of {{ snapshots_log.total }} entries
|
||||
</div>
|
||||
<nav class="dataTable-pagination">
|
||||
<ul class="dataTable-pagination-list">
|
||||
{% if snapshots_log.has_prev %}
|
||||
<li class="pager">
|
||||
<a href="{{ url_for('inventory.snapshotslist', page=snapshots_log.prev_num, per_page=snapshots_log.per_page) }}">‹</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% for page in snapshots_log.iter_pages() %}
|
||||
{% if page %}
|
||||
{% if page == snapshots_log.page %}
|
||||
<li class="active"><a href="javascript:void()">{{ page }}</a></li>
|
||||
{% else %}
|
||||
<li class="">
|
||||
<a href="{{ url_for('inventory.snapshotslist', page=page, per_page=snapshots_log.per_page) }}">
|
||||
{{ page }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if snapshots_log.has_next %}
|
||||
<li class="pager">
|
||||
<a href="{{ url_for('inventory.snapshotslist', page=snapshots_log.next_num, per_page=snapshots_log.per_page) }}">›</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -109,6 +169,18 @@
|
|||
|
||||
<!-- Custom Code -->
|
||||
<script>
|
||||
const table = new simpleDatatables.DataTable("table")
|
||||
$(document).ready(() => {
|
||||
$(".dataTable-selector").on("change", function() {
|
||||
const per_page = $('.dataTable-selector').val();
|
||||
window.location.href = "{{ url_for('inventory.snapshotslist', page=1) }}&per_page="+per_page;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
let table = new simpleDatatables.DataTable("table", {
|
||||
footer: false,
|
||||
paging: false,
|
||||
|
||||
})
|
||||
</script>
|
||||
{% endblock main %}
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
</div>
|
||||
<div class="row">
|
||||
<div class="col qr">
|
||||
<div id="{{ dev.devicehub_id }}"></div>
|
||||
<div id="{{ dev.dhid }}"></div>
|
||||
</div>
|
||||
<div class="col dhid">
|
||||
<div style="padding-top: 55px">
|
||||
|
@ -41,7 +41,7 @@
|
|||
data-model="{{ dev.model or '' }}"
|
||||
data-tags="{{ dev.list_tags() }}"
|
||||
data-phid="{{ dev.phid() }}"
|
||||
data-sid="{{ dev.sid or '' }}">{{ dev.devicehub_id }}</b>
|
||||
data-sid="{{ dev.sid or '' }}">{{ dev.dhid }}</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -192,7 +192,7 @@
|
|||
<script src="{{ url_for('static', filename='js/print.pdf.js') }}"></script>
|
||||
<script type="text/javascript">
|
||||
{% for dev in devices %}
|
||||
qr_draw("{{ dev.public_link }}", "#{{ dev.devicehub_id }}")
|
||||
qr_draw("{{ dev.public_link }}", "#{{ dev.dhid }}")
|
||||
{% endfor %}
|
||||
</script>
|
||||
{% endblock main %}
|
||||
|
|
|
@ -14,8 +14,8 @@ WB_SMART_TEST = short
|
|||
WB_ERASE = EraseBasic
|
||||
WB_ERASE_STEPS = 1
|
||||
WB_ERASE_LEADING_ZEROS = False
|
||||
VERSION = "Basic Erasure (BE)"
|
||||
|
||||
WB_DEBUG = True
|
||||
{% elif baseline_erease %}
|
||||
DH_HOST = {{ api_host }}
|
||||
DH_DATABASE = {{ schema }}
|
||||
|
@ -28,6 +28,11 @@ WB_SMART_TEST = short
|
|||
WB_ERASE = EraseSectors
|
||||
WB_ERASE_STEPS = {{ erase_steps }}
|
||||
WB_ERASE_LEADING_ZEROS = True
|
||||
VERSION = {%if erase_steps < 3 %}"Baseline Secure Erasure (BSE)"{% else %}"Enhanced Secure Erasure (ESE)"{% endif %}
|
||||
|
||||
WB_DEBUG = True
|
||||
{% endif %}
|
||||
{% else %}
|
||||
SNAPSHOTS_PATH = /mnt
|
||||
LOGS_PATH = /mnt
|
||||
VERSION = "Basic Metadata (BM)"
|
||||
|
||||
{% endif %}
|
|
@ -56,6 +56,7 @@ def test_api_docs(client: Client):
|
|||
'/inventory/device/{id}/',
|
||||
'/inventory/device/{dhid}/binding/',
|
||||
'/inventory/device/erasure/',
|
||||
'/inventory/device/erasure/{orphans}/',
|
||||
'/inventory/all/device/',
|
||||
'/inventory/export/{export_id}/',
|
||||
'/inventory/lot/add/',
|
||||
|
@ -116,4 +117,4 @@ def test_api_docs(client: Client):
|
|||
'scheme': 'basic',
|
||||
'name': 'Authorization',
|
||||
}
|
||||
assert len(docs['definitions']) == 132
|
||||
assert len(docs['definitions']) == 134
|
||||
|
|
|
@ -2368,6 +2368,9 @@ def test_upload_snapshot_smartphone(user3: UserClientFlask):
|
|||
@pytest.mark.mvp
|
||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||
def test_list_erasures(user3: UserClientFlask):
|
||||
from flask import current_app as app
|
||||
|
||||
app.config['SCHEMA'] = 'test'
|
||||
uri = '/inventory/upload-snapshot/'
|
||||
file_name = 'erase-sectors-2-hdd.snapshot.yaml'
|
||||
body, status = user3.get(uri)
|
||||
|
@ -2393,6 +2396,19 @@ def test_list_erasures(user3: UserClientFlask):
|
|||
assert status == '200 OK'
|
||||
assert txt in body
|
||||
|
||||
uri = '/inventory/device/erasure/1/'
|
||||
body, status = user3.get(uri)
|
||||
assert status == '200 OK'
|
||||
assert txt not in body
|
||||
|
||||
dev = Device.query.first()
|
||||
dev.binding.kangaroo = True
|
||||
db.session.commit()
|
||||
|
||||
body, status = user3.get(uri)
|
||||
assert status == '200 OK'
|
||||
assert txt in body
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||
|
@ -2454,6 +2470,7 @@ def test_bug_3831_documents(user3: UserClientFlask):
|
|||
assert 'Delete Lot' in body
|
||||
assert 'Incoming Lot' in body
|
||||
|
||||
lot_id = Lot.query.all()[1].id
|
||||
uri = f'/inventory/lot/{lot_id}/trade-document/add/'
|
||||
body, status = user3.get(uri)
|
||||
|
||||
|
@ -2471,8 +2488,16 @@ def test_bug_3831_documents(user3: UserClientFlask):
|
|||
}
|
||||
|
||||
uri = f'/inventory/lot/{lot_id}/trade-document/add/'
|
||||
# body, status = user3.post(uri, data=data, content_type="multipart/form-data")
|
||||
# assert status == '200 OK'
|
||||
body, status = user3.post(uri, data=data, content_type="multipart/form-data")
|
||||
assert status == '200 OK'
|
||||
|
||||
# Second document
|
||||
uri = f'/inventory/lot/{lot_id}/trade-document/add/'
|
||||
file_upload = (BytesIO(b_file), file_name)
|
||||
data['file'] = file_upload
|
||||
data['csrf_token'] = generate_csrf()
|
||||
body, status = user3.post(uri, data=data, content_type="multipart/form-data")
|
||||
assert status == '200 OK'
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
|
|
Reference in a new issue