Merge branch 'testing' into changes/4025-new-hid

This commit is contained in:
Cayo Puigdefabregas 2022-12-01 15:59:52 +01:00
commit 39f19f676b
31 changed files with 1212 additions and 326 deletions

View File

@ -6,6 +6,35 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
ml). ml).
## testing ## 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 ## [2.4.2] - 2022-10-18
- [added] #373 Enhancement - UX Lots. - [added] #373 Enhancement - UX Lots.

View File

@ -30,7 +30,6 @@ from teal.enums import Country, Currency, Layouts, Subdivision
from teal.marshmallow import EnumField from teal.marshmallow import EnumField
from ereuse_devicehub.marshmallow import NestedOn from ereuse_devicehub.marshmallow import NestedOn
from ereuse_devicehub.resources.schemas import Thing
project = 'Devicehub' project = 'Devicehub'
copyright = '2020, eReuse.org team' copyright = '2020, eReuse.org team'
@ -56,7 +55,7 @@ extensions = [
'sphinx.ext.viewcode', 'sphinx.ext.viewcode',
'sphinxcontrib.plantuml', 'sphinxcontrib.plantuml',
'sphinx.ext.autosectionlabel', 'sphinx.ext.autosectionlabel',
'sphinx.ext.autodoc' 'sphinx.ext.autodoc',
] ]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
@ -126,15 +125,12 @@ latex_elements = {
# The paper size ('letterpaper' or 'a4paper'). # The paper size ('letterpaper' or 'a4paper').
# #
# 'papersize': 'letterpaper', # 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt'). # The font size ('10pt', '11pt' or '12pt').
# #
# 'pointsize': '10pt', # 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble. # Additional stuff for the LaTeX preamble.
# #
# 'preamble': '', # 'preamble': '',
# Latex figure (float) alignment # Latex figure (float) alignment
# #
# 'figure_align': 'htbp', # 'figure_align': 'htbp',
@ -144,18 +140,20 @@ latex_elements = {
# (source start file, target name, title, # (source start file, target name, title,
# author, documentclass [howto, manual, or own class]). # author, documentclass [howto, manual, or own class]).
latex_documents = [ 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 ------------------------------------------ # -- Options for manual page output ------------------------------------------
# One entry per manual page. List of tuples # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [ man_pages = [(master_doc, 'devicehub', 'Devicehub Documentation', [author], 1)]
(master_doc, 'devicehub', 'Devicehub Documentation',
[author], 1)
]
# -- Options for Texinfo output ---------------------------------------------- # -- Options for Texinfo output ----------------------------------------------
@ -163,9 +161,15 @@ man_pages = [
# (source start file, target name, title, author, # (source start file, target name, title, author,
# dir menu entry, description, category) # dir menu entry, description, category)
texinfo_documents = [ texinfo_documents = [
(master_doc, 'Devicehub', 'Devicehub Documentation', (
author, 'Devicehub', 'One line description of project.', master_doc,
'Miscellaneous'), 'Devicehub',
'Devicehub Documentation',
author,
'Devicehub',
'One line description of project.',
'Miscellaneous',
),
] ]
# -- Extension configuration ------------------------------------------------- # -- Extension configuration -------------------------------------------------
@ -199,6 +203,7 @@ class DhlistDirective(Directive):
This requires :py:class:`ereuse_devicehub.resources.schemas.SchemaMeta`. This requires :py:class:`ereuse_devicehub.resources.schemas.SchemaMeta`.
You will find in that module more information. You will find in that module more information.
""" """
has_content = False has_content = False
# Definition of passed-in options # Definition of passed-in options
@ -216,7 +221,7 @@ class DhlistDirective(Directive):
sections = [] sections = []
sections.append(self.links(things)) # Make index 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, # Generate a section for each class, with a title,
# fields description and a paragraph # fields description and a paragraph
section = n.section(ids=[self._id(thng)]) section = n.section(ids=[self._id(thng)])
@ -228,7 +233,9 @@ class DhlistDirective(Directive):
for key, f in thng._own: for key, f in thng._own:
name = n.field_name(text=f.data_key or key) name = n.field_name(text=f.data_key or key)
body = [ 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): if isinstance(f, EnumField):
body.append(self._parse_enum_field(f)) body.append(self._parse_enum_field(f))
@ -244,6 +251,7 @@ class DhlistDirective(Directive):
def _parse_enum_field(self, f): def _parse_enum_field(self, f):
from ereuse_devicehub.resources.device import states from ereuse_devicehub.resources.device import states
if issubclass(f.enum, (Subdivision, Currency, Country, Layouts, states.State)): if issubclass(f.enum, (Subdivision, Currency, Country, Layouts, states.State)):
return self.parse(f.enum.__doc__) return self.parse(f.enum.__doc__)
else: else:

View File

@ -1 +1 @@
__version__ = "2.4.2" __version__ = "2.5.0"

View File

@ -1,6 +1,5 @@
from distutils.version import StrictVersion from distutils.version import StrictVersion
from itertools import chain from itertools import chain
from typing import Set
from decouple import config from decouple import config
from teal.auth import TokenAuth from teal.auth import TokenAuth
@ -44,7 +43,7 @@ class DevicehubConfig(Config):
import_resource(metric_def), import_resource(metric_def),
), ),
) )
PASSWORD_SCHEMES = {'pbkdf2_sha256'} # type: Set[str] PASSWORD_SCHEMES = {'pbkdf2_sha256'}
SECRET_KEY = config('SECRET_KEY') SECRET_KEY = config('SECRET_KEY')
DB_USER = config('DB_USER', 'dhub') DB_USER = config('DB_USER', 'dhub')
DB_PASSWORD = config('DB_PASSWORD', 'ereuse') DB_PASSWORD = config('DB_PASSWORD', 'ereuse')

View File

@ -1,7 +1,6 @@
import itertools import itertools
import json import json
from pathlib import Path from pathlib import Path
from typing import Set
import click import click
import click_spinner import click_spinner
@ -109,7 +108,7 @@ class Dummy:
files = tuple(Path(__file__).parent.joinpath('files').iterdir()) files = tuple(Path(__file__).parent.joinpath('files').iterdir())
print('done.') print('done.')
sample_pc = None # We treat this one as a special sample for demonstrations 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: with click.progressbar(files, label='Creating devices...'.ljust(28)) as bar:
for path in bar: for path in bar:
with path.open() as f: with path.open() as f:

View File

@ -50,11 +50,15 @@ from ereuse_devicehub.resources.device.models import (
Keyboard, Keyboard,
Laptop, Laptop,
MemoryCardReader, MemoryCardReader,
Monitor,
Mouse, Mouse,
Other,
Placeholder, Placeholder,
Projector,
Server, Server,
Smartphone, Smartphone,
Tablet, Tablet,
TelevisionSet,
) )
from ereuse_devicehub.resources.documents.models import DataWipeDocument from ereuse_devicehub.resources.documents.models import DataWipeDocument
from ereuse_devicehub.resources.enums import Severity from ereuse_devicehub.resources.enums import Severity
@ -81,16 +85,23 @@ DEVICES = {
], ],
"Mobile, tablet & smartphone": [ "Mobile, tablet & smartphone": [
"All Mobile", "All Mobile",
"Mobile",
"Tablet", "Tablet",
"Smartphone", "Smartphone",
"Cellphone", "Cellphone",
], ],
"Drives & Storage": [ "Drives & Storage": [
"All DataStorage", "All DataStorage",
"HardDrives", "HardDrive",
"SolidStageDrive", "SolidStageDrive",
], ],
"Accessories": [
"All Accessories",
"Mouse",
"MemoryCardReader",
"SAI",
"Keyboard",
],
"Other Devices": ["Other"],
} }
COMPUTERS = ['Desktop', 'Laptop', 'Server', 'Computer'] COMPUTERS = ['Desktop', 'Laptop', 'Server', 'Computer']
@ -98,6 +109,8 @@ COMPUTERS = ['Desktop', 'Laptop', 'Server', 'Computer']
MONITORS = ["ComputerMonitor", "Monitor", "TelevisionSet", "Projector"] MONITORS = ["ComputerMonitor", "Monitor", "TelevisionSet", "Projector"]
MOBILE = ["Mobile", "Tablet", "Smartphone", "Cellphone"] MOBILE = ["Mobile", "Tablet", "Smartphone", "Cellphone"]
STORAGE = ["HardDrive", "SolidStateDrive"] STORAGE = ["HardDrive", "SolidStateDrive"]
ACCESSORIES = ["Mouse", "MemoryCardReader", "SAI", "Keyboard"]
OTHERS = ["Other"]
class AdvancedSearchForm(FlaskForm): class AdvancedSearchForm(FlaskForm):
@ -170,7 +183,7 @@ class FilterForm(FlaskForm):
# Generic Filters # Generic Filters
if "All Devices" == self.device_type: if "All Devices" == self.device_type:
filter_type = COMPUTERS + MONITORS + MOBILE filter_type = COMPUTERS + MONITORS + MOBILE + OTHERS
elif "All Computers" == self.device_type: elif "All Computers" == self.device_type:
filter_type = COMPUTERS filter_type = COMPUTERS
@ -184,6 +197,9 @@ class FilterForm(FlaskForm):
elif "All DataStorage" == self.device_type: elif "All DataStorage" == self.device_type:
filter_type = STORAGE filter_type = STORAGE
elif "All Accessories" == self.device_type:
filter_type = ACCESSORIES
if filter_type: if filter_type:
self.devices = self.devices.filter(Device.type.in_(filter_type)) self.devices = self.devices.filter(Device.type.in_(filter_type))
@ -374,10 +390,14 @@ class NewDeviceForm(FlaskForm):
"Tablet": Tablet, "Tablet": Tablet,
"Cellphone": Cellphone, "Cellphone": Cellphone,
"ComputerMonitor": ComputerMonitor, "ComputerMonitor": ComputerMonitor,
"Monitor": Monitor,
"TelevisionSet": TelevisionSet,
"Projector": Projector,
"Mouse": Mouse, "Mouse": Mouse,
"Keyboard": Keyboard, "Keyboard": Keyboard,
"SAI": SAI, "SAI": SAI,
"MemoryCardReader": MemoryCardReader, "MemoryCardReader": MemoryCardReader,
"Other": Other,
} }
def reset_from_obj(self): def reset_from_obj(self):

View File

@ -55,15 +55,26 @@ devices = Blueprint('inventory', __name__, url_prefix='/inventory')
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
PER_PAGE = 20
class DeviceListMixin(GenericMixin): class DeviceListMixin(GenericMixin):
template_name = 'inventory/device_list.html' template_name = 'inventory/device_list.html'
def get_context(self, lot_id=None, all_devices=False): def get_context(self, lot_id=None, all_devices=False):
super().get_context() 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'] lots = self.context['lots']
form_filter = FilterForm(lots, lot_id, all_devices=all_devices) 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 lot = None
form_transfer = '' form_transfer = ''
form_delivery = '' form_delivery = ''
@ -92,6 +103,7 @@ class DeviceListMixin(GenericMixin):
'tags': self.get_user_tags(), 'tags': self.get_user_tags(),
'list_devices': self.get_selected_devices(form_new_action), 'list_devices': self.get_selected_devices(form_new_action),
'all_devices': all_devices, 'all_devices': all_devices,
'filter': filter,
} }
) )
@ -115,15 +127,43 @@ class DeviceListMixin(GenericMixin):
class ErasureListView(DeviceListMixin): class ErasureListView(DeviceListMixin):
template_name = 'inventory/erasure_list.html' template_name = 'inventory/erasure_list.html'
def dispatch_request(self): def dispatch_request(self, orphans=0):
self.get_context() self.get_context()
self.get_devices() self.get_devices(orphans)
return flask.render_template(self.template_name, **self.context) 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( erasure = EraseBasic.query.filter_by(author=g.user).order_by(
EraseBasic.created.desc() 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 self.context['erasure'] = erasure
@ -1174,43 +1214,17 @@ class SnapshotListView(GenericMixin):
return flask.render_template(self.template_name, **self.context) return flask.render_template(self.template_name, **self.context)
def get_snapshots_log(self): 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( snapshots_log = SnapshotsLog.query.filter(
SnapshotsLog.owner == g.user SnapshotsLog.owner == g.user
).order_by(SnapshotsLog.created.desc()) ).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: snapshots_log = snapshots_log.paginate(page=page, per_page=per_page)
logs[snap.snapshot_uuid] = { snapshots_log.first = per_page * snapshots_log.page - per_page + 1
'sid': snap.sid, snapshots_log.last = len(snapshots_log.items) + snapshots_log.first - 1
'snapshot_uuid': snap.snapshot_uuid, return snapshots_log
'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
class SnapshotDetailView(GenericMixin): class SnapshotDetailView(GenericMixin):
@ -1340,10 +1354,17 @@ class PlaceholderLogListView(GenericMixin):
return flask.render_template(self.template_name, **self.context) return flask.render_template(self.template_name, **self.context)
def get_placeholders_log(self): 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( placeholder_log = PlaceholdersLog.query.filter(
PlaceholdersLog.owner == g.user PlaceholdersLog.owner == g.user
).order_by(PlaceholdersLog.created.desc()) ).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 return placeholder_log
@ -1452,3 +1473,7 @@ devices.add_url_rule(
devices.add_url_rule( devices.add_url_rule(
'/device/erasure/', view_func=ErasureListView.as_view('device_erasure_list') '/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'),
)

View File

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

View File

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

View File

@ -4,7 +4,7 @@ from contextlib import suppress
from datetime import datetime from datetime import datetime
from fractions import Fraction from fractions import Fraction
from math import hypot from math import hypot
from typing import Iterator, List, Optional, Type, TypeVar from typing import Iterator, List, Optional, TypeVar
import dateutil.parser import dateutil.parser
from ereuse_utils import getter, text from ereuse_utils import getter, text
@ -404,7 +404,7 @@ class Computer(Device):
chassis value. chassis value.
""" """
COMPONENTS = list(Component.__subclasses__()) # type: List[Type[Component]] COMPONENTS = list(Component.__subclasses__())
COMPONENTS.remove(Motherboard) COMPONENTS.remove(Motherboard)
def __init__(self, node: dict) -> None: def __init__(self, node: dict) -> None:

View File

@ -73,11 +73,25 @@ class SnapshotsLog(Thing):
snapshots = [] snapshots = []
for s in self.snapshot.device.actions: for s in self.snapshot.device.actions:
if s == self.snapshot: if s == self.snapshot:
continue break
if s.type == self.snapshot.type: if s.type == self.snapshot.type:
snapshots.append(s) snapshots.append(s)
return snapshots and 'Update' or 'New Device' 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): class PlaceholdersLog(Thing):
"""A Placeholder log.""" """A Placeholder log."""

View File

@ -1,7 +1,6 @@
from datetime import datetime, timezone 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 from ereuse_workbench.utils import Dumpeable
@ -24,8 +23,8 @@ class Snapshot(Dumpeable):
self.endTime = datetime.now(timezone.utc) self.endTime = datetime.now(timezone.utc)
self.closed = False self.closed = False
self.elapsed = None self.elapsed = None
self.device = None # type: Computer self.device = None
self.components = None # type: List[Component] self.components = None
self._storages = None self._storages = None
def computer(self): def computer(self):

View File

@ -676,6 +676,7 @@ class Snapshot(JoinedWithOneDeviceMixin, ActionWithOneDevice):
of time it took to complete. of time it took to complete.
""" """
sid = Column(CIText(), nullable=True) sid = Column(CIText(), nullable=True)
settings_version = Column(CIText(), nullable=True)
is_server_erase = Column(Boolean(), nullable=True) is_server_erase = Column(Boolean(), nullable=True)
def get_last_lifetimes(self): def get_last_lifetimes(self):

View File

@ -453,6 +453,7 @@ class Snapshot(ActionWithOneDevice):
'Order is preserved, so the component num 0 when' 'Order is preserved, so the component num 0 when'
'submitting is the component num 0 when returning it back.', 'submitting is the component num 0 when returning it back.',
) )
settings_version = String(required=False)
@validates_schema @validates_schema
def validate_workbench_version(self, data: dict): def validate_workbench_version(self, data: dict):

View File

@ -4,7 +4,11 @@ from teal.resource import Converters, Resource
from ereuse_devicehub.resources.device import schemas from ereuse_devicehub.resources.device import schemas
from ereuse_devicehub.resources.device.models import Manufacturer 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): class DeviceDef(Resource):
@ -13,7 +17,9 @@ class DeviceDef(Resource):
ID_CONVERTER = Converters.string ID_CONVERTER = Converters.string
AUTH = False # We manage this at each view AUTH = False # We manage this at each view
def __init__(self, app, def __init__(
self,
app,
import_name=__name__, import_name=__name__,
static_folder='static', static_folder='static',
static_url_path=None, static_url_path=None,
@ -22,16 +28,31 @@ class DeviceDef(Resource):
subdomain=None, subdomain=None,
url_defaults=None, url_defaults=None,
root_path=None, root_path=None,
cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): 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) 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: if self.AUTH:
device_merge = app.auth.requires_auth(device_merge) 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'}) # self.add_url_rule(path, view_func=device_merge, methods={'POST'})
@ -40,11 +61,31 @@ class ComputerDef(DeviceDef):
VIEW = None VIEW = None
SCHEMA = schemas.Computer SCHEMA = schemas.Computer
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, def __init__(
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, self,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): app,
super().__init__(app, import_name, static_folder, static_url_path, template_folder, import_name=__name__,
url_prefix, subdomain, url_defaults, root_path, cli_commands) 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): class DesktopDef(ComputerDef):
@ -66,11 +107,31 @@ class MonitorDef(DeviceDef):
VIEW = None VIEW = None
SCHEMA = schemas.Monitor SCHEMA = schemas.Monitor
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, def __init__(
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, self,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): app,
super().__init__(app, import_name, static_folder, static_url_path, template_folder, import_name=__name__,
url_prefix, subdomain, url_defaults, root_path, cli_commands) 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): class ComputerMonitorDef(MonitorDef):
@ -83,15 +144,40 @@ class TelevisionSetDef(MonitorDef):
SCHEMA = schemas.TelevisionSet SCHEMA = schemas.TelevisionSet
class ProjectorDef(MonitorDef):
VIEW = None
SCHEMA = schemas.Projector
class MobileDef(DeviceDef): class MobileDef(DeviceDef):
VIEW = None VIEW = None
SCHEMA = schemas.Mobile SCHEMA = schemas.Mobile
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, def __init__(
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, self,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): app,
super().__init__(app, import_name, static_folder, static_url_path, template_folder, import_name=__name__,
url_prefix, subdomain, url_defaults, root_path, cli_commands) 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): class SmartphoneDef(MobileDef):
@ -113,11 +199,31 @@ class ComponentDef(DeviceDef):
VIEW = None VIEW = None
SCHEMA = schemas.Component SCHEMA = schemas.Component
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, def __init__(
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, self,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): app,
super().__init__(app, import_name, static_folder, static_url_path, template_folder, import_name=__name__,
url_prefix, subdomain, url_defaults, root_path, cli_commands) 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): class GraphicCardDef(ComponentDef):
@ -184,11 +290,31 @@ class ComputerAccessoryDef(DeviceDef):
VIEW = None VIEW = None
SCHEMA = schemas.ComputerAccessory SCHEMA = schemas.ComputerAccessory
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, def __init__(
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, self,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): app,
super().__init__(app, import_name, static_folder, static_url_path, template_folder, import_name=__name__,
url_prefix, subdomain, url_defaults, root_path, cli_commands) 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): class MouseDef(ComputerAccessoryDef):
@ -215,11 +341,31 @@ class NetworkingDef(DeviceDef):
VIEW = None VIEW = None
SCHEMA = schemas.Networking SCHEMA = schemas.Networking
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, def __init__(
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, self,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): app,
super().__init__(app, import_name, static_folder, static_url_path, template_folder, import_name=__name__,
url_prefix, subdomain, url_defaults, root_path, cli_commands) 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): class RouterDef(NetworkingDef):
@ -246,11 +392,31 @@ class PrinterDef(DeviceDef):
VIEW = None VIEW = None
SCHEMA = schemas.Printer SCHEMA = schemas.Printer
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, def __init__(
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, self,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): app,
super().__init__(app, import_name, static_folder, static_url_path, template_folder, import_name=__name__,
url_prefix, subdomain, url_defaults, root_path, cli_commands) 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): class LabelPrinterDef(PrinterDef):
@ -262,11 +428,31 @@ class SoundDef(DeviceDef):
VIEW = None VIEW = None
SCHEMA = schemas.Sound SCHEMA = schemas.Sound
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, def __init__(
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, self,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): app,
super().__init__(app, import_name, static_folder, static_url_path, template_folder, import_name=__name__,
url_prefix, subdomain, url_defaults, root_path, cli_commands) 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): class MicrophoneDef(SoundDef):
@ -278,11 +464,31 @@ class VideoDef(DeviceDef):
VIEW = None VIEW = None
SCHEMA = schemas.Video SCHEMA = schemas.Video
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, def __init__(
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, self,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): app,
super().__init__(app, import_name, static_folder, static_url_path, template_folder, import_name=__name__,
url_prefix, subdomain, url_defaults, root_path, cli_commands) 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): class VideoScalerDef(VideoDef):
@ -299,11 +505,31 @@ class CookingDef(DeviceDef):
VIEW = None VIEW = None
SCHEMA = schemas.Cooking SCHEMA = schemas.Cooking
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, def __init__(
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, self,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): app,
super().__init__(app, import_name, static_folder, static_url_path, template_folder, import_name=__name__,
url_prefix, subdomain, url_defaults, root_path, cli_commands) 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): class Mixer(CookingDef):
@ -315,11 +541,31 @@ class DIYAndGardeningDef(DeviceDef):
VIEW = None VIEW = None
SCHEMA = schemas.DIYAndGardening SCHEMA = schemas.DIYAndGardening
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, def __init__(
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, self,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): app,
super().__init__(app, import_name, static_folder, static_url_path, template_folder, import_name=__name__,
url_prefix, subdomain, url_defaults, root_path, cli_commands) 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): class DrillDef(DIYAndGardeningDef):
@ -331,22 +577,62 @@ class PackOfScrewdriversDef(DIYAndGardeningDef):
VIEW = None VIEW = None
SCHEMA = schemas.PackOfScrewdrivers SCHEMA = schemas.PackOfScrewdrivers
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, def __init__(
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, self,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): app,
super().__init__(app, import_name, static_folder, static_url_path, template_folder, import_name=__name__,
url_prefix, subdomain, url_defaults, root_path, cli_commands) 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): class HomeDef(DeviceDef):
VIEW = None VIEW = None
SCHEMA = schemas.Home SCHEMA = schemas.Home
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, def __init__(
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, self,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): app,
super().__init__(app, import_name, static_folder, static_url_path, template_folder, import_name=__name__,
url_prefix, subdomain, url_defaults, root_path, cli_commands) 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): class DehumidifierDef(HomeDef):
@ -363,11 +649,31 @@ class RecreationDef(DeviceDef):
VIEW = None VIEW = None
SCHEMA = schemas.Recreation SCHEMA = schemas.Recreation
def __init__(self, app, import_name=__name__, static_folder=None, static_url_path=None, def __init__(
template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, self,
root_path=None, cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()): app,
super().__init__(app, import_name, static_folder, static_url_path, template_folder, import_name=__name__,
url_prefix, subdomain, url_defaults, root_path, cli_commands) 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): class BikeDef(RecreationDef):
@ -389,3 +695,35 @@ class ManufacturerDef(Resource):
"""Loads the manufacturers to the database.""" """Loads the manufacturers to the database."""
if exclude_schema != 'common': if exclude_schema != 'common':
Manufacturer.add_all_to_session(db.session) 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,
)

View File

@ -635,6 +635,14 @@ class Device(Thing):
return self.binding.device.devicehub_id return self.binding.device.devicehub_id
return self.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 @property
def get_updated(self): def get_updated(self):
if self.placeholder and self.placeholder.binding: if self.placeholder and self.placeholder.binding:
@ -1394,6 +1402,19 @@ class DataStorage(JoinedComponentTableMixin, Component):
except LookupError: except LookupError:
return None 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): class HardDrive(DataStorage):
pass pass
@ -1684,3 +1705,11 @@ def create_code_tag(mapper, connection, device):
# from flask_sqlalchemy import event # from flask_sqlalchemy import event
# event.listen(Device, 'after_insert', create_code_tag, propagate=True) # 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)

View File

@ -270,6 +270,10 @@ class TelevisionSet(Monitor):
__doc__ = m.TelevisionSet.__doc__ __doc__ = m.TelevisionSet.__doc__
class Projector(Monitor):
__doc__ = m.Projector.__doc__
class Mobile(Device): class Mobile(Device):
__doc__ = m.Mobile.__doc__ __doc__ = m.Mobile.__doc__
@ -578,3 +582,7 @@ class Bike(Recreation):
class Racket(Recreation): class Racket(Recreation):
pass pass
class Other(Device):
pass

View File

@ -306,7 +306,7 @@ class LotDeviceView(LotBaseChildrenView):
dev_qry = Device.query.filter(Device.id.in_(ids)).filter(Device.owner == g.user) dev_qry = Device.query.filter(Device.id.in_(ids)).filter(Device.owner == g.user)
for dev in dev_qry: for dev in dev_qry:
if isinstance(dev, DataStorage) and dev.parent: if isinstance(dev, DataStorage) and not dev.orphan:
continue continue
devices.add(dev) devices.add(dev)

View File

@ -172,7 +172,9 @@ class TradeDocument(Thing):
return sorted(ev for ev in actions if ev.severity >= Severity.Warning) return sorted(ev for ev in actions if ev.severity >= Severity.Warning)
def __lt__(self, other): def __lt__(self, other):
if self.id and other.id:
return self.id < other.id return self.id < other.id
return False
def __str__(self) -> str: def __str__(self) -> str:
return '{0.file_name}'.format(self) return '{0.file_name}'.format(self)

View File

@ -48,12 +48,12 @@
/** /**
* Search bar toggle * Search bar toggle
*/
if (select(".search-bar-toggle")) { if (select(".search-bar-toggle")) {
on("click", ".search-bar-toggle", (e) => { on("click", ".search-bar-toggle", (e) => {
select(".search-bar").classList.toggle("search-bar-show") select(".search-bar").classList.toggle("search-bar-show")
}) })
} }
*/
/** /**
* Navbar links active state on scroll * Navbar links active state on scroll

View File

@ -8,6 +8,12 @@
</div><!-- End Logo --> </div><!-- End Logo -->
<div class="search-bar"> <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="#"> <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" <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"> autocomplete="off" id="dropdownSearch" data-bs-toggle="dropdown" aria-expanded="false">

View File

@ -46,7 +46,13 @@
</optgroup> </optgroup>
<optgroup label="Monitor"> <optgroup label="Monitor">
<option value="ComputerMonitor" <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>
<optgroup label="Mobile"> <optgroup label="Mobile">
<option value="Smartphone" <option value="Smartphone"
@ -66,6 +72,10 @@
<option value="Keyboard" <option value="Keyboard"
{% if form.type.data == 'Keyboard' %} selected="selected"{% endif %}>Keyboard</option> {% if form.type.data == 'Keyboard' %} selected="selected"{% endif %}>Keyboard</option>
</optgroup> </optgroup>
<optgroup label="Other Type of Device">
<option value="Other"
{% if form.type.data == 'Other' %} selected="selected"{% endif %}>Other</option>
</optgroup>
</select> </select>
<small class="text-muted form-text">Type of devices</small> <small class="text-muted form-text">Type of devices</small>
{% if form.type.errors %} {% if form.type.errors %}

View File

@ -335,6 +335,8 @@
{% for f in form_filter %} {% for f in form_filter %}
{{ f }} {{ f }}
{% endfor %} {% 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" /> <input type="submit" class="ms-2 btn btn-primary" value="Filter" />
</div> </div>
</form> </form>
@ -344,6 +346,38 @@
<em>{{ form_filter.filter.data or "Computer" }}</em> <em>{{ form_filter.filter.data or "Computer" }}</em>
</p> </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"> <table class="table">
<thead> <thead>
<tr> <tr>
@ -362,7 +396,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for dev in devices %} {% for dev in devices.items %}
{% if dev.placeholder and (not dev.parent_id or dev.parent.placeholder.kangaroo) %} {% if dev.placeholder and (not dev.parent_id or dev.parent.placeholder.kangaroo) %}
<tr> <tr>
<td> <td>
@ -421,6 +455,57 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </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>
</div> </div>
@ -592,10 +677,25 @@
{% include "inventory/alert_lots_changes.html" %} {% include "inventory/alert_lots_changes.html" %}
<!-- Custom Code --> <!-- 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> <script>
let table = new simpleDatatables.DataTable("table", { let table = new simpleDatatables.DataTable("table", {
perPageSelect: [5, 10, 15, 20, 25, 50, 100], footer: false,
perPage: 20 paging: false,
}) })
</script> </script>
{% if config['DEBUG'] %} {% if config['DEBUG'] %}

View File

@ -18,9 +18,54 @@
<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;">
<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"> <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> <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=""> <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"> <button id="btnExport" 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-reply"></i>
@ -42,8 +87,65 @@
</li> </li>
</ul> </ul>
</div> </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="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"> <table class="table">
<thead> <thead>
<tr> <tr>
@ -59,10 +161,10 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for ac in erasure %} {% for ac in erasure.items %}
<tr> <tr>
<td> <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-type="{{ ac.device.type }}" data-device-manufacturer="{{ ac.device.manufacturer }}"
data-device-dhid="{{ ac.device.dhid }}" data-device-vname="{{ ac.device.verbose_name }}" data-device-dhid="{{ ac.device.dhid }}" data-device-vname="{{ ac.device.verbose_name }}"
data-action-erasure="{{ ac.id }}" data-action-erasure="{{ ac.id }}"
@ -74,11 +176,24 @@
<td> <td>
{% if ac.device.phid() %} {% if ac.device.phid() %}
<a href="{{ url_for('inventory.device_details', id=ac.device.dhid)}}"> <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() }} {{ ac.device.serial_number.upper() }}
</a> </a>
{% else %} {% 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() }} {{ ac.device.serial_number.upper() }}
{% endif %} {% 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>
<td> <td>
{% if ac.device.phid() %} {% if ac.device.phid() %}
@ -115,135 +230,54 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="dataTable-bottom">
<div class="dataTable-info">
Showing {{ erasure.first }} to {{ erasure.last }} of {{ erasure.total }} entries
</div> </div>
{% if lot and not lot.is_temporary %} <nav class="dataTable-pagination">
<div id="trade-documents-list" class="tab-pane fade trade-documents-list"> <ul class="dataTable-pagination-list">
<h5 class="card-title">Documents</h5> {% if erasure.has_prev %}
<table class="table"> <li class="pager">
<thead> {% if orphans %}
<tr> <a href="{{ url_for('inventory.device_erasure_list_orphans', orphans=1, page=erasure.prev_num, per_page=erasure.per_page) }}"></a>
<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 %} {% else %}
{{ doc.file_name}} <a href="{{ url_for('inventory.device_erasure_list', page=erasure.prev_num, per_page=erasure.per_page) }}"></a>
{% endif %} {% endif %}
</td> </li>
<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 %} {% endif %}
{{ field }} {% for page in erasure.iter_pages() %}
<small class="text-muted">{{ field.description }}</small> {% if page %}
{% if field.errors %} {% if page == erasure.page %}
<p class="text-danger"> <li class="active"><a href="javascript:void()">{{ page }}</a></li>
{% for error in field.errors %} {% else %}
{{ error }}<br/> <li class="">
{% endfor %} {% if orphans %}
</p> <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 %} {% endif %}
</li>
{% endif %} {% endif %}
</div>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if erasure.has_next %}
<div> <li class="pager">
<a href="{{ url_for('inventory.lotdevicelist', lot_id=lot.id) }}" class="btn btn-danger">Cancel</a> {% if orphans %}
<button class="btn btn-primary" type="submit">Save</button> <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>
</form>
</div> </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><!-- End Bordered Tabs -->
</div> </div>
@ -255,21 +289,32 @@
</div> </div>
</div> </div>
</section> </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_export_error.html" %}
{% include "inventory/alert_lots_changes.html" %} {% include "inventory/alert_lots_changes.html" %}
<!-- Custom Code --> <!-- 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> <script>
let table = new simpleDatatables.DataTable("table", { let table = new simpleDatatables.DataTable("table", {
perPageSelect: [5, 10, 15, 20, 25, 50, 100], //perPageSelect: [5, 10, 15, 20, 25, 50, 100],
perPage: 20 //perPage: 20,
footer: false,
paging: false,
}) })
</script> </script>
{% if config['DEBUG'] %} {% if config['DEBUG'] %}
<script src="{{ url_for('static', filename='js/main_inventory.js') }}"></script> <script src="{{ url_for('static', filename='js/main_inventory.js') }}"></script>
{% else %} {% else %}

View File

@ -22,6 +22,38 @@
<div class="tab-content pt-5"> <div class="tab-content pt-5">
<div id="devices-list" class="tab-pane fade devices-list active show"> <div id="devices-list" class="tab-pane fade devices-list active show">
<div class="tab-content pt-2"> <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"> <table class="table">
<thead> <thead>
<tr> <tr>
@ -34,7 +66,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for log in placeholders_log %} {% for log in placeholders_log.items %}
<tr> <tr>
<td> <td>
{{ log.phid }} {{ log.phid }}
@ -58,6 +90,38 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </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>
</div> </div>
@ -75,6 +139,18 @@
<!-- Custom Code --> <!-- Custom Code -->
<script> <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> </script>
{% endblock main %} {% endblock main %}

View File

@ -16,7 +16,7 @@
<div class="col-xl-12"> <div class="col-xl-12">
<div class="card"> <div class="card d-none">
<div class="tab-content pt-2"> <div class="tab-content pt-2">
<div class="flex mt-4 mb-4"> <div class="flex mt-4 mb-4">
<form method="get" class="ms-4"> <form method="get" class="ms-4">
@ -307,8 +307,8 @@
</a> </a>
{% if dev.lots | length > 0 %} {% if dev.lots | length > 0 %}
<h6 class="d-inline"> <h6 class="d-inline">
{% for lot in dev.lots %} {% for lot in dev.get_lots_for_template() %}
<span class="badge rounded-pill bg-light text-dark">{{ lot.name }}</span> <span class="badge rounded-pill bg-light text-dark">{{ lot }}</span>
{% endfor %} {% endfor %}
</h6> </h6>
{% endif %} {% endif %}

View File

@ -22,6 +22,38 @@
<div class="tab-content pt-5"> <div class="tab-content pt-5">
<div id="devices-list" class="tab-pane fade devices-list active show"> <div id="devices-list" class="tab-pane fade devices-list active show">
<div class="tab-content pt-2"> <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"> <table class="table">
<thead> <thead>
<tr> <tr>
@ -29,7 +61,6 @@
<th scope="col">Snapshot UUID</th> <th scope="col">Snapshot UUID</th>
<th scope="col">Version</th> <th scope="col">Version</th>
<th scope="col">DHID</th> <th scope="col">DHID</th>
<th scope="col">System UUID</th>
<th scope="col">Status</th> <th scope="col">Status</th>
<th scope="col">Type Upload</th> <th scope="col">Type Upload</th>
<th scope="col">Type Device</th> <th scope="col">Type Device</th>
@ -39,7 +70,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for snap in snapshots_log %} {% for snap in snapshots_log.items %}
<tr> <tr>
<td> <td>
{% if snap.sid and snap.snapshot_uuid %} {% if snap.sid and snap.snapshot_uuid %}
@ -56,29 +87,26 @@
{% endif %} {% endif %}
</td> </td>
<td> <td>
{{ snap.version }} {{ snap.get_version() }}
</td> </td>
<td> <td>
{% if snap.device %} {% if snap.get_device() %}
<a href="{{ url_for('inventory.device_details', id=snap.device) }}"> <a href="{{ url_for('inventory.device_details', id=snap.device) }}">
{{ snap.device }} {{ snap.get_device() }}
</a> </a>
{% endif %} {% endif %}
</td> </td>
<td> <td>
{{ snap.system_uuid }} {{ snap.get_status() }}
</td> </td>
<td> <td>
{{ snap.status }} {{ snap.get_new_device() }}
</td> </td>
<td> <td>
{{ snap.new_device }} {{ snap.get_type_device() }}
</td> </td>
<td> <td>
{{ snap.type_device }} {{ snap.get_original_dhid() }}
</td>
<td>
{{ snap.original_dhid }}
</td> </td>
<td>{{ snap.created.strftime('%Y-%m-%d %H:%M') }}</td> <td>{{ snap.created.strftime('%Y-%m-%d %H:%M') }}</td>
<td> <td>
@ -93,6 +121,38 @@
</tbody> </tbody>
</table> </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>
</div> </div>
@ -109,6 +169,18 @@
<!-- Custom Code --> <!-- Custom Code -->
<script> <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> </script>
{% endblock main %} {% endblock main %}

View File

@ -32,7 +32,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col qr"> <div class="col qr">
<div id="{{ dev.devicehub_id }}"></div> <div id="{{ dev.dhid }}"></div>
</div> </div>
<div class="col dhid"> <div class="col dhid">
<div style="padding-top: 55px"> <div style="padding-top: 55px">
@ -41,7 +41,7 @@
data-model="{{ dev.model or '' }}" data-model="{{ dev.model or '' }}"
data-tags="{{ dev.list_tags() }}" data-tags="{{ dev.list_tags() }}"
data-phid="{{ dev.phid() }}" data-phid="{{ dev.phid() }}"
data-sid="{{ dev.sid or '' }}">{{ dev.devicehub_id }}</b> data-sid="{{ dev.sid or '' }}">{{ dev.dhid }}</b>
</div> </div>
</div> </div>
</div> </div>
@ -192,7 +192,7 @@
<script src="{{ url_for('static', filename='js/print.pdf.js') }}"></script> <script src="{{ url_for('static', filename='js/print.pdf.js') }}"></script>
<script type="text/javascript"> <script type="text/javascript">
{% for dev in devices %} {% for dev in devices %}
qr_draw("{{ dev.public_link }}", "#{{ dev.devicehub_id }}") qr_draw("{{ dev.public_link }}", "#{{ dev.dhid }}")
{% endfor %} {% endfor %}
</script> </script>
{% endblock main %} {% endblock main %}

View File

@ -14,8 +14,8 @@ WB_SMART_TEST = short
WB_ERASE = EraseBasic WB_ERASE = EraseBasic
WB_ERASE_STEPS = 1 WB_ERASE_STEPS = 1
WB_ERASE_LEADING_ZEROS = False WB_ERASE_LEADING_ZEROS = False
VERSION = "Basic Erasure (BE)"
WB_DEBUG = True
{% elif baseline_erease %} {% elif baseline_erease %}
DH_HOST = {{ api_host }} DH_HOST = {{ api_host }}
DH_DATABASE = {{ schema }} DH_DATABASE = {{ schema }}
@ -28,6 +28,11 @@ WB_SMART_TEST = short
WB_ERASE = EraseSectors WB_ERASE = EraseSectors
WB_ERASE_STEPS = {{ erase_steps }} WB_ERASE_STEPS = {{ erase_steps }}
WB_ERASE_LEADING_ZEROS = True WB_ERASE_LEADING_ZEROS = True
VERSION = {%if erase_steps < 3 %}"Baseline Secure Erasure (BSE)"{% else %}"Enhanced Secure Erasure (ESE)"{% endif %}
{% else %}
SNAPSHOTS_PATH = /mnt
LOGS_PATH = /mnt
VERSION = "Basic Metadata (BM)"
WB_DEBUG = True
{% endif %} {% endif %}

View File

@ -56,6 +56,7 @@ def test_api_docs(client: Client):
'/inventory/device/{id}/', '/inventory/device/{id}/',
'/inventory/device/{dhid}/binding/', '/inventory/device/{dhid}/binding/',
'/inventory/device/erasure/', '/inventory/device/erasure/',
'/inventory/device/erasure/{orphans}/',
'/inventory/all/device/', '/inventory/all/device/',
'/inventory/export/{export_id}/', '/inventory/export/{export_id}/',
'/inventory/lot/add/', '/inventory/lot/add/',
@ -116,4 +117,4 @@ def test_api_docs(client: Client):
'scheme': 'basic', 'scheme': 'basic',
'name': 'Authorization', 'name': 'Authorization',
} }
assert len(docs['definitions']) == 132 assert len(docs['definitions']) == 134

View File

@ -2368,6 +2368,9 @@ def test_upload_snapshot_smartphone(user3: UserClientFlask):
@pytest.mark.mvp @pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
def test_list_erasures(user3: UserClientFlask): def test_list_erasures(user3: UserClientFlask):
from flask import current_app as app
app.config['SCHEMA'] = 'test'
uri = '/inventory/upload-snapshot/' uri = '/inventory/upload-snapshot/'
file_name = 'erase-sectors-2-hdd.snapshot.yaml' file_name = 'erase-sectors-2-hdd.snapshot.yaml'
body, status = user3.get(uri) body, status = user3.get(uri)
@ -2393,6 +2396,19 @@ def test_list_erasures(user3: UserClientFlask):
assert status == '200 OK' assert status == '200 OK'
assert txt in body 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.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__) @pytest.mark.usefixtures(conftest.app_context.__name__)
@ -2454,6 +2470,7 @@ def test_bug_3831_documents(user3: UserClientFlask):
assert 'Delete Lot' in body assert 'Delete Lot' in body
assert 'Incoming Lot' in body assert 'Incoming Lot' in body
lot_id = Lot.query.all()[1].id
uri = f'/inventory/lot/{lot_id}/trade-document/add/' uri = f'/inventory/lot/{lot_id}/trade-document/add/'
body, status = user3.get(uri) 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/' uri = f'/inventory/lot/{lot_id}/trade-document/add/'
# body, status = user3.post(uri, data=data, content_type="multipart/form-data") body, status = user3.post(uri, data=data, content_type="multipart/form-data")
# assert status == '200 OK' 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 @pytest.mark.mvp