diff --git a/.babelrc.json b/.babelrc.json
new file mode 100644
index 00000000..15e24a3a
--- /dev/null
+++ b/.babelrc.json
@@ -0,0 +1,15 @@
+{
+ "presets": [
+ [
+ "@babel/preset-env",
+ {
+ "targets": {
+ "edge": "17",
+ "firefox": "60",
+ "chrome": "67",
+ "safari": "11.1"
+ }
+ }
+ ]
+ ]
+}
\ No newline at end of file
diff --git a/.eslintignore b/.eslintignore
index 416c47b1..8d09e126 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,4 +1,5 @@
ereuse_devicehub/static/vendor
ereuse_devicehub/static/js/print.pdf.js
ereuse_devicehub/static/js/qrcode.js
+*.build.js
*.min.js
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 35d175a6..c6c51b46 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,13 @@ ml).
## master
## testing
+
+## [2.1.1] - 2022-05-11
+Hot fix release.
+- [fixed] #256 JS support to old browsers using babel.
+- [fixed] #266 Fix error when trade.document.url is None on device_list.html
+
+## [2.1.0] - 2022-05-11
- [added] #219 Add functionality to searchbar (Lots and devices).
- [added] #222 Allow user to update its password.
- [added] #233 Filter in out trades from lots selector.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c34d93c5..142fd954 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,6 +2,15 @@
## Writing code
+### Javascript and compatibility with "old" browsers
+**Warning:** This project is using babel compiler... You need run an additional build step to make build js file
+```bash
+npm install
+npm run babel
+```
+NOTE: If you prefer you can use yarn instead, it's compatible
+NOTE2: This only affect to file `ereuse_devicehub/static/js/main_inventory.js`.
+
### Coding style
#### Python style
diff --git a/ereuse_devicehub/__init__.py b/ereuse_devicehub/__init__.py
index 4bf535c4..d5fe4817 100644
--- a/ereuse_devicehub/__init__.py
+++ b/ereuse_devicehub/__init__.py
@@ -1 +1 @@
-__version__ = "2.1.0.dev"
+__version__ = "2.1.2.alpha0"
diff --git a/ereuse_devicehub/inventory/forms.py b/ereuse_devicehub/inventory/forms.py
index 990608c6..bbdd61a7 100644
--- a/ereuse_devicehub/inventory/forms.py
+++ b/ereuse_devicehub/inventory/forms.py
@@ -58,7 +58,7 @@ from ereuse_devicehub.resources.tradedocument.models import TradeDocument
from ereuse_devicehub.resources.user.models import User
DEVICES = {
- "All": ["All Devices", "All Components"],
+ "All": ["All Devices"],
"Computer": [
"All Computers",
"Desktop",
@@ -79,55 +79,12 @@ DEVICES = {
"Smartphone",
"Cellphone",
],
- "DataStorage": ["All DataStorage", "HardDrive", "SolidStateDrive"],
- "Accessories & Peripherals": [
- "All Peripherals",
- "GraphicCard",
- "Motherboard",
- "NetworkAdapter",
- "Processor",
- "RamModule",
- "SoundCard",
- "Battery",
- "Keyboard",
- "Mouse",
- "MemoryCardReader",
- ],
}
COMPUTERS = ['Desktop', 'Laptop', 'Server', 'Computer']
-COMPONENTS = [
- 'GraphicCard',
- 'DataStorage',
- 'HardDrive',
- 'DataStorage',
- 'SolidStateDrive',
- 'Motherboard',
- 'NetworkAdapter',
- 'Processor',
- 'RamModule',
- 'SoundCard',
- 'Display',
- 'Battery',
- 'Camera',
-]
-
MONITORS = ["ComputerMonitor", "Monitor", "TelevisionSet", "Projector"]
MOBILE = ["Mobile", "Tablet", "Smartphone", "Cellphone"]
-DATASTORAGE = ["HardDrive", "SolidStateDrive"]
-PERIPHERALS = [
- "GraphicCard",
- "Motherboard",
- "NetworkAdapter",
- "Processor",
- "RamModule",
- "SoundCard",
- "Battery",
- "Keyboard",
- "Mouse",
- "MemoryCardReader",
-]
class FilterForm(FlaskForm):
@@ -139,6 +96,7 @@ class FilterForm(FlaskForm):
super().__init__(*args, **kwargs)
self.lots = lots
self.lot_id = lot_id
+ self.only_unassigned = kwargs.pop('only_unassigned', True)
self._get_types()
def _get_types(self):
@@ -154,9 +112,9 @@ class FilterForm(FlaskForm):
device_ids = (d.id for d in self.lot.devices)
self.devices = Device.query.filter(Device.id.in_(device_ids))
else:
- self.devices = Device.query.filter(Device.owner_id == g.user.id).filter_by(
- lots=None
- )
+ self.devices = Device.query.filter(Device.owner_id == g.user.id)
+ if self.only_unassigned:
+ self.devices = self.devices.filter_by(lots=None)
def search(self):
self.filter_from_lots()
@@ -171,9 +129,6 @@ class FilterForm(FlaskForm):
if "All Devices" == self.device_type:
filter_type = COMPUTERS + ["Monitor"] + MOBILE
- elif "All Components" == self.device_type:
- filter_type = COMPONENTS
-
elif "All Computers" == self.device_type:
filter_type = COMPUTERS
@@ -183,12 +138,6 @@ class FilterForm(FlaskForm):
elif "All Mobile" == self.device_type:
filter_type = MOBILE
- elif "All DataStorage" == self.device_type:
- filter_type = DATASTORAGE
-
- elif "All Peripherals" == self.device_type:
- filter_type = PERIPHERALS
-
if filter_type:
self.devices = self.devices.filter(Device.type.in_(filter_type))
@@ -655,6 +604,7 @@ class NewActionForm(ActionFormMixin):
class AllocateForm(ActionFormMixin):
+ date = HiddenField('')
start_time = DateField('Start time')
end_time = DateField('End time', [validators.Optional()])
final_user_code = StringField(
@@ -663,7 +613,7 @@ class AllocateForm(ActionFormMixin):
transaction = StringField(
'Transaction', [validators.Optional(), validators.length(max=50)]
)
- end_users = IntegerField('End users', [validators.Optional()])
+ end_users = IntegerField('Number of end users', [validators.Optional()])
def validate(self, extra_validators=None):
if not super().validate(extra_validators):
diff --git a/ereuse_devicehub/inventory/views.py b/ereuse_devicehub/inventory/views.py
index 0c725448..c60fe1d5 100644
--- a/ereuse_devicehub/inventory/views.py
+++ b/ereuse_devicehub/inventory/views.py
@@ -1,5 +1,6 @@
import csv
import logging
+from distutils.util import strtobool
from io import StringIO
import flask
@@ -40,60 +41,64 @@ logger = logging.getLogger(__name__)
class DeviceListMixin(GenericMixin):
template_name = 'inventory/device_list.html'
- def get_context(self, lot_id):
+ def get_context(self, lot_id, only_unassigned=True):
super().get_context()
lots = self.context['lots']
- form_filter = FilterForm(lots, lot_id)
+ form_filter = FilterForm(lots, lot_id, only_unassigned=only_unassigned)
devices = form_filter.search()
lot = None
- tags = (
- Tag.query.filter(Tag.owner_id == current_user.id)
- .filter(Tag.device_id.is_(None))
- .order_by(Tag.id.asc())
- )
if lot_id:
lot = lots.filter(Lot.id == lot_id).one()
- form_new_action = NewActionForm(lot=lot.id)
- form_new_allocate = AllocateForm(lot=lot.id)
- form_new_datawipe = DataWipeForm(lot=lot.id)
form_new_trade = TradeForm(
lot=lot.id,
user_to=g.user.email,
user_from=g.user.email,
)
else:
- form_new_action = NewActionForm()
- form_new_allocate = AllocateForm()
- form_new_datawipe = DataWipeForm()
form_new_trade = ''
- action_devices = form_new_action.devices.data
- list_devices = []
- if action_devices:
- list_devices.extend([int(x) for x in action_devices.split(",")])
+ form_new_action = NewActionForm(lot=lot_id)
self.context.update(
{
'devices': devices,
'form_tag_device': TagDeviceForm(),
'form_new_action': form_new_action,
- 'form_new_allocate': form_new_allocate,
- 'form_new_datawipe': form_new_datawipe,
+ 'form_new_allocate': AllocateForm(lot=lot_id),
+ 'form_new_datawipe': DataWipeForm(lot=lot_id),
'form_new_trade': form_new_trade,
'form_filter': form_filter,
'form_print_labels': PrintLabelsForm(),
'lot': lot,
- 'tags': tags,
- 'list_devices': list_devices,
+ 'tags': self.get_user_tags(),
+ 'list_devices': self.get_selected_devices(form_new_action),
+ 'unassigned_devices': only_unassigned,
}
)
return self.context
+ def get_user_tags(self):
+ return (
+ Tag.query.filter(Tag.owner_id == current_user.id)
+ .filter(Tag.device_id.is_(None))
+ .order_by(Tag.id.asc())
+ )
+
+ def get_selected_devices(self, action_form):
+ """Retrieve selected devices (when action form is submited)"""
+ action_devices = action_form.devices.data
+ if action_devices:
+ return [int(x) for x in action_devices.split(",")]
+ return []
+
class DeviceListView(DeviceListMixin):
def dispatch_request(self, lot_id=None):
- self.get_context(lot_id)
+ only_unassigned = request.args.get(
+ 'only_unassigned', default=True, type=strtobool
+ )
+ self.get_context(lot_id, only_unassigned)
return flask.render_template(self.template_name, **self.context)
@@ -327,7 +332,8 @@ class NewAllocateView(DeviceListMixin, NewActionView):
messages.error('Action {} error!'.format(self.form.type.data))
for k, v in self.form.errors.items():
value = ';'.join(v)
- messages.error('Action Error {key}: {value}!'.format(key=k, value=value))
+ key = self.form[k].label.text
+ messages.error('Action Error {key}: {value}!'.format(key=key, value=value))
next_url = self.get_next_url()
return flask.redirect(next_url)
diff --git a/ereuse_devicehub/labels/views.py b/ereuse_devicehub/labels/views.py
index e7fc3b0d..421effcb 100644
--- a/ereuse_devicehub/labels/views.py
+++ b/ereuse_devicehub/labels/views.py
@@ -23,7 +23,9 @@ class TagListView(View):
def dispatch_request(self):
lots = Lot.query.filter(Lot.owner_id == current_user.id)
- tags = Tag.query.filter(Tag.owner_id == current_user.id).order_by(Tag.id)
+ tags = Tag.query.filter(Tag.owner_id == current_user.id).order_by(
+ Tag.created.desc()
+ )
context = {
'lots': lots,
'tags': tags,
diff --git a/ereuse_devicehub/resources/device/models.py b/ereuse_devicehub/resources/device/models.py
index 1bd33cf6..771ee725 100644
--- a/ereuse_devicehub/resources/device/models.py
+++ b/ereuse_devicehub/resources/device/models.py
@@ -332,6 +332,35 @@ class Device(Thing):
with suppress(LookupError, ValueError):
return self.last_action_of(*states.Trading.actions())
+ @property
+ def allocated_status(self):
+ """Show the actual status of device.
+ The status depend of one of this 3 actions:
+ - Allocate
+ - Deallocate
+ - InUse (Live register)
+ """
+ from ereuse_devicehub.resources.device import states
+
+ with suppress(LookupError, ValueError):
+ return self.last_action_of(*states.Usage.actions())
+
+ @property
+ def physical_status(self):
+ """Show the actual status of device for this owner.
+ The status depend of one of this 4 actions:
+ - ToPrepare
+ - Prepare
+ - DataWipe
+ - ToRepair
+ - Ready
+ """
+ from ereuse_devicehub.resources.device import states
+
+ with suppress(LookupError, ValueError):
+ # import pdb; pdb.set_trace()
+ return self.last_action_of(*states.Physical.actions())
+
@property
def status(self):
"""Show the actual status of device for this owner.
@@ -560,6 +589,20 @@ class Device(Thing):
args[POLYMORPHIC_ON] = cls.type
return args
+ def is_status(self, action):
+ from ereuse_devicehub.resources.device import states
+
+ if action.type in states.Usage.__members__:
+ return "Allocate State: "
+
+ if action.type in states.Status.__members__:
+ return "Lifecycle State: "
+
+ if action.type in states.Physical.__members__:
+ return "Physical State: "
+
+ return ""
+
def set_hid(self):
with suppress(TypeError):
self.hid = Naming.hid(
diff --git a/ereuse_devicehub/resources/device/states.py b/ereuse_devicehub/resources/device/states.py
index 3cfefe4b..30578704 100644
--- a/ereuse_devicehub/resources/device/states.py
+++ b/ereuse_devicehub/resources/device/states.py
@@ -33,6 +33,7 @@ class Trading(State):
:cvar ProductDisposed: The device has been removed
from the facility. It does not mean end-of-life.
"""
+
Reserved = e.Reserve
Trade = e.Trade
Confirm = e.Confirm
@@ -50,16 +51,17 @@ class Trading(State):
class Physical(State):
"""Physical states.
- :cvar ToBeRepaired: The device has been selected for reparation.
- :cvar Repaired: The device has been repaired.
- :cvar Preparing: The device is going to be or being prepared.
- :cvar Prepared: The device has been prepared.
+ :cvar Repair: The device has been repaired.
+ :cvar ToPrepare: The device is going to be or being prepared.
+ :cvar Prepare: The device has been prepared.
:cvar Ready: The device is in working conditions.
+ :cvar DataWipe: Do DataWipe over the device.
"""
- ToBeRepaired = e.ToRepair
- Repaired = e.Repair
- Preparing = e.ToPrepare
- Prepared = e.Prepare
+
+ ToPrepare = e.ToPrepare
+ Prepare = e.Prepare
+ DataWipe = e.DataWipe
+ ToRepair = e.ToRepair
Ready = e.Ready
@@ -68,22 +70,24 @@ class Traking(State):
:cvar Receive: The device changes hands
"""
+
# Receive = e.Receive
pass
-
+
class Usage(State):
"""Usage states.
:cvar Allocate: The device is allocate in other Agent (organization, person ...)
:cvar Deallocate: The device is deallocate and return to the owner
- :cvar InUse: The device is being reported to be in active use.
+ :cvar InUse: The device is being reported to be in active use.
"""
+
Allocate = e.Allocate
Deallocate = e.Deallocate
InUse = e.Live
-
+
class Status(State):
"""Define status of device for one user.
:cvar Use: The device is in use for one final user.
@@ -91,6 +95,7 @@ class Status(State):
:cvar Recycling: The device is sended to recycling.
:cvar Management: The device is owned by one Manager.
"""
+
Use = e.Use
Refurbish = e.Refurbish
Recycling = e.Recycling
diff --git a/ereuse_devicehub/resources/device/templates/devices/layout.html b/ereuse_devicehub/resources/device/templates/devices/layout.html
index de46ecf3..e4474426 100644
--- a/ereuse_devicehub/resources/device/templates/devices/layout.html
+++ b/ereuse_devicehub/resources/device/templates/devices/layout.html
@@ -154,6 +154,37 @@
+
Actual Status
+
+ -
+
+ Lifecycle Status
+
+ —
+ {% if device.status %}
+ {{ device.status.type }}
+ {% endif %}
+
+ -
+
+ Allocate Status
+
+ —
+ {% if device.allocated_status %}
+ {{ device.allocated_status.type }}
+ {% endif %}
+
+ -
+
+ Physical Status
+
+ —
+ {% if device.physical_status %}
+ {{ device.physical_status.type }}
+ {% endif %}
+
+
+
Public traceability log of the device
Latest one.
@@ -162,10 +193,17 @@
{% for action in device.public_actions %}
- {{ action.type }}
+ {{ device.is_status(action) }}
+ {% if not device.is_status(action) %}
+ {{ action.type }}
+ {% endif %}
—
- {{ action }}
+ {% if device.is_status(action) %}
+ {{ action }} {{ action.type }}
+ {% else %}
+ {{ action }}
+ {% endif %}
diff --git a/ereuse_devicehub/resources/documents/device_row.py b/ereuse_devicehub/resources/documents/device_row.py
index f4b8ba93..5f48eaa3 100644
--- a/ereuse_devicehub/resources/documents/device_row.py
+++ b/ereuse_devicehub/resources/documents/device_row.py
@@ -70,16 +70,19 @@ class DeviceRow(OrderedDict):
self['Updated in (software)'] = device.updated
self['Updated in (web)'] = ''
+ self['Physical state'] = ''
+ if device.physical_status:
+ self['Physical state'] = device.physical_status.type
+
+ self['Allocate state'] = ''
+ if device.allocated_status:
+ self['Allocate state'] = device.allocated_status.type
+
try:
- self['Physical state'] = device.last_action_of(
- *states.Physical.actions()).t
- except LookupError:
- self['Physical state'] = ''
- try:
- self['Trading state'] = device.last_action_of(
+ self['Lifecycle state'] = device.last_action_of(
*states.Trading.actions()).t
except LookupError:
- self['Trading state'] = ''
+ self['Lifecycle state'] = ''
if isinstance(device, d.Computer):
self['Processor'] = none2str(device.processor_model)
self['RAM (MB)'] = none2str(device.ram_size)
@@ -367,15 +370,18 @@ class StockRow(OrderedDict):
self['Model'] = none2str(device.model)
self['Manufacturer'] = none2str(device.manufacturer)
self['Registered in'] = format(device.created, '%c')
+ self['Physical state'] = ''
+ if device.physical_status:
+ self['Physical state'] = device.physical_status.type
+
+ self['Allocate state'] = ''
+ if device.allocated_status:
+ self['Allocate state'] = device.allocated_status.type
+
try:
- self['Physical state'] = device.last_action_of(
- *states.Physical.actions()).t
+ self['Lifecycle state'] = device.last_action_of(*states.Trading.actions()).t
except LookupError:
- self['Physical state'] = ''
- try:
- self['Trading state'] = device.last_action_of(*states.Trading.actions()).t
- except LookupError:
- self['Trading state'] = ''
+ self['Lifecycle state'] = ''
self['Price'] = none2str(device.price)
self['Processor'] = none2str(device.processor_model)
self['RAM (MB)'] = none2str(device.ram_size)
diff --git a/ereuse_devicehub/static/js/main_inventory.build.js b/ereuse_devicehub/static/js/main_inventory.build.js
new file mode 100644
index 00000000..48ab00a7
--- /dev/null
+++ b/ereuse_devicehub/static/js/main_inventory.build.js
@@ -0,0 +1,621 @@
+"use strict";
+
+function _classStaticPrivateFieldSpecGet(receiver, classConstructor, descriptor) { _classCheckPrivateStaticAccess(receiver, classConstructor); _classCheckPrivateStaticFieldDescriptor(descriptor, "get"); return _classApplyDescriptorGet(receiver, descriptor); }
+
+function _classCheckPrivateStaticFieldDescriptor(descriptor, action) { if (descriptor === undefined) { throw new TypeError("attempted to " + action + " private static field before its declaration"); } }
+
+function _classCheckPrivateStaticAccess(receiver, classConstructor) { if (receiver !== classConstructor) { throw new TypeError("Private static access of wrong provenance"); } }
+
+function _classApplyDescriptorGet(receiver, descriptor) { if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; }
+
+$(document).ready(() => {
+ const show_allocate_form = $("#allocateModal").data("show-action-form");
+ const show_datawipe_form = $("#datawipeModal").data("show-action-form");
+ const show_trade_form = $("#tradeLotModal").data("show-action-form");
+
+ if (show_allocate_form != "None") {
+ $("#allocateModal .btn-primary").show();
+ newAllocate(show_allocate_form);
+ } else if (show_datawipe_form != "None") {
+ $("#datawipeModal .btn-primary").show();
+ newDataWipe(show_datawipe_form);
+ } else if (show_trade_form != "None") {
+ $("#tradeLotModal .btn-primary").show();
+ newTrade(show_trade_form);
+ } else {
+ $(".deviceSelect").on("change", deviceSelect);
+ } // $('#selectLot').selectpicker();
+
+});
+
+class TableController {
+ /**
+ * @returns Selected inputs from device list
+ */
+ static getSelectedDevices() {
+ if (_classStaticPrivateFieldSpecGet(this, TableController, _tableRows).call(this) == undefined) return [];
+ return _classStaticPrivateFieldSpecGet(this, TableController, _tableRows).call(this).filter(element => element.querySelector("input").checked).map(element => element.querySelector("input"));
+ }
+ /**
+ * @returns Selected inputs in current page from device list
+ */
+
+
+ static getAllSelectedDevicesInCurrentPage() {
+ if (_classStaticPrivateFieldSpecGet(this, TableController, _tableRowsPage).call(this) == undefined) return [];
+ return _classStaticPrivateFieldSpecGet(this, TableController, _tableRowsPage).call(this).filter(element => element.querySelector("input").checked).map(element => element.querySelector("input"));
+ }
+ /**
+ * @returns All inputs from device list
+ */
+
+
+ static getAllDevices() {
+ if (_classStaticPrivateFieldSpecGet(this, TableController, _tableRows).call(this) == undefined) return [];
+ return _classStaticPrivateFieldSpecGet(this, TableController, _tableRows).call(this).map(element => element.querySelector("input"));
+ }
+ /**
+ * @returns All inputs from current page in device list
+ */
+
+
+ static getAllDevicesInCurrentPage() {
+ if (_classStaticPrivateFieldSpecGet(this, TableController, _tableRowsPage).call(this) == undefined) return [];
+ return _classStaticPrivateFieldSpecGet(this, TableController, _tableRowsPage).call(this).map(element => element.querySelector("input"));
+ }
+ /**
+ *
+ * @param {HTMLElement} DOMElements
+ * @returns Procesed input atributes to an Object class
+ */
+
+
+ static ProcessTR(DOMElements) {
+ return DOMElements.map(element => {
+ const info = {};
+ info.checked = element.checked;
+ Object.values(element.attributes).forEach(attrib => {
+ info[attrib.nodeName.replace(/-/g, "_")] = attrib.nodeValue;
+ });
+ return info;
+ });
+ }
+
+}
+/**
+ * Select all functionality
+ */
+
+
+var _tableRows = {
+ writable: true,
+ value: () => table.activeRows.length > 0 ? table.activeRows : []
+};
+var _tableRowsPage = {
+ writable: true,
+ value: () => table.pages[table.rows().dt.currentPage - 1]
+};
+
+const selectorController = action => {
+ const btnSelectAll = document.getElementById("SelectAllBTN");
+ const alertInfoDevices = document.getElementById("select-devices-info");
+
+ function softInit() {
+ TableController.getAllDevices().forEach(item => {
+ item.addEventListener("click", itemListCheckChanged);
+ }); // https://github.com/fiduswriter/Simple-DataTables/wiki/Events
+
+ table.on("datatable.page", () => itemListCheckChanged());
+ table.on("datatable.perpage", () => itemListCheckChanged());
+ table.on("datatable.update", () => itemListCheckChanged());
+ }
+
+ if (action == "softInit") {
+ softInit();
+ itemListCheckChanged();
+ return;
+ }
+
+ function itemListCheckChanged() {
+ alertInfoDevices.innerHTML = "Selected devices: ".concat(TableController.getSelectedDevices().length, "\n ").concat(TableController.getAllDevices().length != TableController.getSelectedDevices().length ? "Select all devices (".concat(TableController.getAllDevices().length, ")") : "Cancel selection");
+
+ if (TableController.getSelectedDevices().length <= 0) {
+ alertInfoDevices.classList.add("d-none");
+ } else {
+ alertInfoDevices.classList.remove("d-none");
+ }
+
+ if (TableController.getAllDevices().length == TableController.getSelectedDevices().length) {
+ btnSelectAll.checked = true;
+ btnSelectAll.indeterminate = false;
+ } else if (TableController.getAllSelectedDevicesInCurrentPage().length > 0) {
+ btnSelectAll.indeterminate = true;
+ } else {
+ btnSelectAll.checked = false;
+ btnSelectAll.indeterminate = false;
+ }
+
+ if (TableController.getAllDevices().length == 0) {
+ btnSelectAll.checked = false;
+ btnSelectAll.disabled = true;
+ } else {
+ btnSelectAll.disabled = false;
+ }
+
+ get_device_list();
+ }
+
+ btnSelectAll.addEventListener("click", event => {
+ const checkedState = event.target.checked;
+ TableController.getAllDevicesInCurrentPage().forEach(ckeckbox => {
+ ckeckbox.checked = checkedState;
+ });
+ itemListCheckChanged();
+ });
+ alertInfoDevices.addEventListener("click", () => {
+ const checkState = TableController.getAllDevices().length == TableController.getSelectedDevices().length;
+ TableController.getAllDevices().forEach(ckeckbox => {
+ ckeckbox.checked = !checkState;
+ });
+ itemListCheckChanged();
+ });
+ softInit();
+ itemListCheckChanged();
+};
+
+window.addEventListener("DOMContentLoaded", () => selectorController());
+
+function deviceSelect() {
+ const devices_count = TableController.getSelectedDevices().length;
+ get_device_list();
+
+ if (devices_count == 0) {
+ $("#addingLotModal .pol").show();
+ $("#addingLotModal .btn-primary").hide();
+ $("#removeLotModal .pol").show();
+ $("#removeLotModal .btn-primary").hide();
+ $("#addingTagModal .pol").show();
+ $("#addingTagModal .btn-primary").hide();
+ $("#actionModal .pol").show();
+ $("#actionModal .btn-primary").hide();
+ $("#allocateModal .pol").show();
+ $("#allocateModal .btn-primary").hide();
+ $("#datawipeModal .pol").show();
+ $("#datawipeModal .btn-primary").hide();
+ } else {
+ $("#addingLotModal .pol").hide();
+ $("#addingLotModal .btn-primary").show();
+ $("#removeLotModal .pol").hide();
+ $("#removeLotModal .btn-primary").show();
+ $("#actionModal .pol").hide();
+ $("#actionModal .btn-primary").show();
+ $("#allocateModal .pol").hide();
+ $("#allocateModal .btn-primary").show();
+ $("#datawipeModal .pol").hide();
+ $("#datawipeModal .btn-primary").show();
+ $("#addingTagModal .pol").hide();
+ $("#addingTagModal .btn-primary").show();
+ }
+}
+
+function removeLot() {
+ const devices = TableController.getAllDevices();
+
+ if (devices.length > 0) {
+ $("#btnRemoveLots .text-danger").show();
+ } else {
+ $("#btnRemoveLots .text-danger").hide();
+ }
+
+ $("#activeRemoveLotModal").click();
+}
+
+function removeTag() {
+ const devices = TableController.getSelectedDevices();
+ const devices_id = devices.map(dev => dev.data);
+
+ if (devices_id.length == 1) {
+ const url = "/inventory/tag/devices/".concat(devices_id[0], "/del/");
+ window.location.href = url;
+ } else {
+ $("#unlinkTagAlertModal").click();
+ }
+}
+
+function addTag() {
+ const devices = TableController.getSelectedDevices();
+ const devices_id = devices.map(dev => dev.data);
+
+ if (devices_id.length == 1) {
+ $("#addingTagModal .pol").hide();
+ $("#addingTagModal .btn-primary").show();
+ } else {
+ $("#addingTagModal .pol").show();
+ $("#addingTagModal .btn-primary").hide();
+ }
+
+ $("#addTagAlertModal").click();
+}
+
+function newTrade(action) {
+ let title = "Trade ";
+ const user_to = $("#user_to").data("email");
+ const user_from = $("#user_from").data("email");
+
+ if (action == "user_from") {
+ title = "Trade Incoming";
+ $("#user_to").attr("readonly", "readonly");
+ $("#user_from").prop("readonly", false);
+ $("#user_from").val("");
+ $("#user_to").val(user_to);
+ } else if (action == "user_to") {
+ title = "Trade Outgoing";
+ $("#user_from").attr("readonly", "readonly");
+ $("#user_to").prop("readonly", false);
+ $("#user_to").val("");
+ $("#user_from").val(user_from);
+ }
+
+ $("#tradeLotModal #title-action").html(title);
+ $("#activeTradeModal").click();
+}
+
+function newAction(action) {
+ $("#actionModal #type").val(action);
+ $("#actionModal #title-action").html(action);
+ deviceSelect();
+ $("#activeActionModal").click();
+}
+
+function newAllocate(action) {
+ $("#allocateModal #type").val(action);
+ $("#allocateModal #title-action").html(action);
+ deviceSelect();
+ $("#activeAllocateModal").click();
+}
+
+function newDataWipe(action) {
+ $("#datawipeModal #type").val(action);
+ $("#datawipeModal #title-action").html(action);
+ deviceSelect();
+ $("#activeDatawipeModal").click();
+}
+
+function get_device_list() {
+ const devices = TableController.getSelectedDevices();
+ /* Insert the correct count of devices in actions form */
+
+ const devices_count = devices.length;
+ $("#datawipeModal .devices-count").html(devices_count);
+ $("#allocateModal .devices-count").html(devices_count);
+ $("#actionModal .devices-count").html(devices_count);
+ /* Insert the correct value in the input devicesList */
+
+ const devices_id = $.map(devices, x => $(x).attr("data")).join(",");
+ $.map($(".devicesList"), x => {
+ $(x).val(devices_id);
+ });
+ /* Create a list of devices for human representation */
+
+ const computer = {
+ "Desktop": "",
+ "Laptop": ""
+ };
+ const list_devices = devices.map(x => {
+ let typ = $(x).data("device-type");
+ const manuf = $(x).data("device-manufacturer");
+ const dhid = $(x).data("device-dhid");
+
+ if (computer[typ]) {
+ typ = computer[typ];
+ }
+
+ ;
+ return "".concat(typ, " ").concat(manuf, " ").concat(dhid);
+ });
+ const description = $.map(list_devices, x => x).join(", ");
+ $(".enumeration-devices").html(description);
+}
+
+function export_file(type_file) {
+ const devices = TableController.getSelectedDevices();
+ const devices_id = $.map(devices, x => $(x).attr("data-device-dhid")).join(",");
+
+ if (devices_id) {
+ const url = "/inventory/export/".concat(type_file, "/?ids=").concat(devices_id);
+ window.location.href = url;
+ } else {
+ $("#exportAlertModal").click();
+ }
+}
+/**
+ * Reactive lots button
+ */
+
+
+async function processSelectedDevices() {
+ class Actions {
+ constructor() {
+ this.list = []; // list of petitions of requests @item --> {type: ["Remove" | "Add"], "LotID": string, "devices": number[]}
+ }
+ /**
+ * Manage the actions that will be performed when applying the changes
+ * @param {EventSource} ev event (Should be a checkbox type)
+ * @param {Lot} lot lot id
+ * @param {Device[]} selectedDevices device id
+ */
+
+
+ manage(event, lot, selectedDevices) {
+ event.preventDefault();
+ const lotID = lot.id;
+ const srcElement = event.srcElement.parentElement.children[0];
+ const checked = !srcElement.checked;
+ const {
+ indeterminate
+ } = srcElement;
+ const found = this.list.filter(list => list.lot.id == lotID)[0];
+
+ if (checked) {
+ if (found && found.type == "Remove") {
+ const affectedDevices = found.devices.filter(dev => found.lot.devices.includes(dev.id));
+
+ if (affectedDevices.length > 0 && found.indeterminate == false) {
+ // Remove action from list
+ actions.list = actions.list.filter(x => x.lot.id != found.lot.id);
+ } else {
+ found.type = "Add";
+ }
+ } else {
+ this.list.push({
+ type: "Add",
+ lot,
+ devices: selectedDevices,
+ indeterminate
+ });
+ }
+ } else if (found && found.type == "Add") {
+ const affectedDevices = found.devices.filter(dev => !found.lot.devices.includes(dev.id));
+
+ if (affectedDevices.length > 0 && found.indeterminate == false) {
+ // Remove action from list
+ actions.list = actions.list.filter(x => x.lot.id != found.lot.id);
+ } else {
+ found.type = "Remove";
+ }
+ } else {
+ this.list.push({
+ type: "Remove",
+ lot,
+ devices: selectedDevices,
+ indeterminate
+ });
+ }
+
+ if (this.list.length > 0) {
+ document.getElementById("ApplyDeviceLots").classList.remove("disabled");
+ } else {
+ document.getElementById("ApplyDeviceLots").classList.add("disabled");
+ }
+ }
+ /**
+ * Creates notification to give feedback to user
+ * @param {string} title notification title
+ * @param {string | null} toastText notification text
+ * @param {boolean} isError defines if a toast is a error
+ */
+
+
+ notifyUser(title, toastText, isError) {
+ const toast = document.createElement("div");
+ toast.classList = "alert alert-dismissible fade show ".concat(isError ? "alert-danger" : "alert-success");
+ toast.attributes["data-autohide"] = !isError;
+ toast.attributes.role = "alert";
+ toast.style = "margin-left: auto; width: fit-content;";
+ toast.innerHTML = "".concat(title, "");
+
+ if (toastText && toastText.length > 0) {
+ toast.innerHTML += "
".concat(toastText);
+ }
+
+ document.getElementById("NotificationsContainer").appendChild(toast);
+
+ if (!isError) {
+ setTimeout(() => toast.classList.remove("show"), 3000);
+ }
+
+ setTimeout(() => document.getElementById("NotificationsContainer").innerHTML == "", 3500);
+ }
+ /**
+ * Get actions and execute call request to add or remove devices from lots
+ */
+
+
+ doActions() {
+ let requestCount = 0; // This is for count all requested api count, to perform reRender of table device list
+
+ this.list.forEach(async action => {
+ if (action.type == "Add") {
+ try {
+ const devicesIDs = action.devices.filter(dev => !action.lot.devices.includes(dev.id)).map(dev => dev.id);
+ await Api.devices_add(action.lot.id, devicesIDs);
+ this.notifyUser("Devices sucefully added to selected lot/s", "", false);
+ } catch (error) {
+ this.notifyUser("Failed to add devices to selected lot/s", error.responseJSON.message, true);
+ }
+ } else if (action.type == "Remove") {
+ try {
+ const devicesIDs = action.devices.filter(dev => action.lot.devices.includes(dev.id)).map(dev => dev.id);
+ await Api.devices_remove(action.lot.id, devicesIDs);
+ this.notifyUser("Devices sucefully removed from selected lot/s", "", false);
+ } catch (error) {
+ this.notifyUser("Failed to remove devices from selected lot/s", error.responseJSON.message, true);
+ }
+ }
+
+ requestCount += 1;
+
+ if (requestCount == this.list.length) {
+ this.reRenderTable();
+ this.list = [];
+ }
+ });
+ $("#confirmLotsModal").modal("hide"); // Hide dialog when click "Save changes"
+
+ document.getElementById("dropDownLotsSelector").classList.remove("show");
+ }
+ /**
+ * Re-render list in table
+ */
+
+
+ async reRenderTable() {
+ const newRequest = await Api.doRequest(window.location);
+ const tmpDiv = document.createElement("div");
+ tmpDiv.innerHTML = newRequest;
+ const newTable = document.createElement("table");
+ newTable.innerHTML = tmpDiv.querySelector("table").innerHTML;
+ newTable.classList = "table";
+ const oldTable = document.querySelector(".dataTable-wrapper");
+ oldTable.parentElement.replaceChild(newTable, oldTable);
+ table = new simpleDatatables.DataTable(newTable, {
+ perPage: 20
+ }); // // Restore state of checkbox
+
+ const selectAllBTN = document.getElementById("SelectAllBTN");
+ selectAllBTN.checked = false;
+ selectAllBTN.indeterminate = false; // Re-init SelectorController
+
+ selectorController("softInit");
+ }
+
+ }
+
+ let eventClickActions;
+ /**
+ * Generates a list item with a correspondient checkbox state
+ * @param {Object} lot Lot model server
+ * @param {Device[]} selectedDevices list selected devices
+ * @param {HTMLElement} elementTarget
+ * @param {Action[]} actions
+ */
+
+ function templateLot(lot, selectedDevices, elementTarget, actions) {
+ elementTarget.innerHTML = "";
+ const {
+ id,
+ name,
+ state
+ } = lot;
+ const htmlTemplate = "\n ");
+ const doc = document.createElement("li");
+ doc.innerHTML = htmlTemplate;
+
+ switch (state) {
+ case "true":
+ doc.children[0].checked = true;
+ break;
+
+ case "false":
+ doc.children[0].checked = false;
+ break;
+
+ case "indetermined":
+ doc.children[0].indeterminate = true;
+ break;
+
+ default:
+ console.warn("This shouldn't be happend: Lot without state: ", lot);
+ break;
+ }
+
+ doc.children[0].addEventListener("mouseup", ev => actions.manage(ev, lot, selectedDevices));
+ doc.children[1].addEventListener("mouseup", ev => actions.manage(ev, lot, selectedDevices));
+ elementTarget.append(doc);
+ }
+
+ const listHTML = $("#LotsSelector"); // Get selected devices
+
+ const selectedDevicesID = TableController.ProcessTR(TableController.getSelectedDevices()).map(item => item.data);
+
+ if (selectedDevicesID.length <= 0) {
+ listHTML.html("No devices selected");
+ return;
+ } // Initialize Actions list, and set checkbox triggers
+
+
+ const actions = new Actions();
+
+ if (eventClickActions) {
+ document.getElementById("ApplyDeviceLots").removeEventListener(eventClickActions);
+ }
+
+ eventClickActions = document.getElementById("ApplyDeviceLots").addEventListener("click", () => {
+ const modal = $("#confirmLotsModal");
+ modal.modal({
+ keyboard: false
+ });
+ let list_changes_html = ""; // {type: ["Remove" | "Add"], "LotID": string, "devices": number[]}
+
+ actions.list.forEach(action => {
+ let type;
+ let devices;
+
+ if (action.type == "Add") {
+ type = "success";
+ devices = action.devices.filter(dev => !action.lot.devices.includes(dev.id)); // Only show affected devices
+ } else {
+ type = "danger";
+ devices = action.devices.filter(dev => action.lot.devices.includes(dev.id)); // Only show affected devices
+ }
+
+ list_changes_html += "\n \n \n
\n
\n ").concat(devices.map(item => {
+ const name = "".concat(item.type, " ").concat(item.manufacturer, " ").concat(item.model);
+ return "").concat(item.devicehubID, "");
+ }).join(" "), "\n
\n
\n
");
+ });
+ modal.find(".modal-body").html(list_changes_html);
+ const el = document.getElementById("SaveAllActions");
+ const elClone = el.cloneNode(true);
+ el.parentNode.replaceChild(elClone, el);
+ elClone.addEventListener("click", () => actions.doActions());
+ modal.modal("show"); // actions.doActions();
+ });
+ document.getElementById("ApplyDeviceLots").classList.add("disabled");
+
+ try {
+ listHTML.html("");
+ const selectedDevices = await Api.get_devices(selectedDevicesID);
+ let lots = await Api.get_lots();
+ lots = lots.map(lot => {
+ lot.devices = selectedDevices.filter(device => device.lots.filter(devicelot => devicelot.id == lot.id).length > 0).map(device => parseInt(device.id));
+
+ switch (lot.devices.length) {
+ case 0:
+ lot.state = "false";
+ break;
+
+ case selectedDevicesID.length:
+ lot.state = "true";
+ break;
+
+ default:
+ lot.state = "indetermined";
+ break;
+ }
+
+ return lot;
+ });
+ let lotsList = [];
+ lotsList.push(lots.filter(lot => lot.state == "true").sort((a, b) => a.name.localeCompare(b.name)));
+ lotsList.push(lots.filter(lot => lot.state == "indetermined").sort((a, b) => a.name.localeCompare(b.name)));
+ lotsList.push(lots.filter(lot => lot.state == "false").sort((a, b) => a.name.localeCompare(b.name)));
+ lotsList = lotsList.flat(); // flat array
+
+ listHTML.html("");
+ lotsList.forEach(lot => templateLot(lot, selectedDevices, listHTML, actions));
+ } catch (error) {
+ console.log(error);
+ listHTML.html("Error feching devices and lots
(see console for more details)");
+ }
+}
diff --git a/ereuse_devicehub/static/js/main_inventory.js b/ereuse_devicehub/static/js/main_inventory.js
index 848c72e4..7a2ecc93 100644
--- a/ereuse_devicehub/static/js/main_inventory.js
+++ b/ereuse_devicehub/static/js/main_inventory.js
@@ -78,38 +78,61 @@ class TableController {
/**
* Select all functionality
*/
-window.addEventListener("DOMContentLoaded", () => {
+
+const selectorController = (action) => {
const btnSelectAll = document.getElementById("SelectAllBTN");
const alertInfoDevices = document.getElementById("select-devices-info");
- function itemListCheckChanged() {
- const listDevices = TableController.getAllDevicesInCurrentPage()
- const isAllChecked = listDevices.map(itm => itm.checked);
+ function softInit() {
+ TableController.getAllDevices().forEach(item => {
+ item.addEventListener("click", itemListCheckChanged);
+ })
- if (isAllChecked.every(bool => bool == true)) {
- btnSelectAll.checked = true;
- btnSelectAll.indeterminate = false;
- alertInfoDevices.innerHTML = `Selected devices: ${TableController.getSelectedDevices().length}
- ${
- TableController.getAllDevices().length != TableController.getSelectedDevices().length
- ? `Select all devices (${TableController.getAllDevices().length})`
- : "Cancel selection"
- }`;
- alertInfoDevices.classList.remove("d-none");
- } else if (isAllChecked.every(bool => bool == false)) {
- btnSelectAll.checked = false;
- btnSelectAll.indeterminate = false;
- alertInfoDevices.classList.add("d-none")
- } else {
- btnSelectAll.indeterminate = true;
- alertInfoDevices.classList.add("d-none")
- }
+ // https://github.com/fiduswriter/Simple-DataTables/wiki/Events
+ table.on("datatable.page", () => itemListCheckChanged());
+ table.on("datatable.perpage", () => itemListCheckChanged());
+ table.on("datatable.update", () => itemListCheckChanged());
+ }
+
+ if (action == "softInit") {
+ softInit();
+ itemListCheckChanged();
+ return;
}
- TableController.getAllDevices().forEach(item => {
- item.addEventListener("click", itemListCheckChanged);
- })
+ function itemListCheckChanged() {
+ alertInfoDevices.innerHTML = `Selected devices: ${TableController.getSelectedDevices().length}
+ ${TableController.getAllDevices().length != TableController.getSelectedDevices().length
+ ? `Select all devices (${TableController.getAllDevices().length})`
+ : "Cancel selection"
+ }`;
+ if (TableController.getSelectedDevices().length <= 0) {
+ alertInfoDevices.classList.add("d-none")
+ } else {
+ alertInfoDevices.classList.remove("d-none");
+ }
+
+ if (TableController.getAllDevices().length == TableController.getSelectedDevices().length) {
+ btnSelectAll.checked = true;
+ btnSelectAll.indeterminate = false;
+ } else if(TableController.getAllSelectedDevicesInCurrentPage().length > 0) {
+ btnSelectAll.indeterminate = true;
+ } else {
+ btnSelectAll.checked = false;
+ btnSelectAll.indeterminate = false;
+ }
+
+ if (TableController.getAllDevices().length == 0) {
+ btnSelectAll.checked = false;
+ btnSelectAll.disabled = true;
+ } else {
+ btnSelectAll.disabled = false;
+ }
+
+ get_device_list();
+ }
+
btnSelectAll.addEventListener("click", event => {
const checkedState = event.target.checked;
TableController.getAllDevicesInCurrentPage().forEach(ckeckbox => { ckeckbox.checked = checkedState });
@@ -122,11 +145,12 @@ window.addEventListener("DOMContentLoaded", () => {
itemListCheckChanged()
})
- // https://github.com/fiduswriter/Simple-DataTables/wiki/Events
- table.on("datatable.page", () => itemListCheckChanged());
- table.on("datatable.perpage", () => itemListCheckChanged());
- table.on("datatable.update", () => itemListCheckChanged());
-})
+ softInit();
+
+ itemListCheckChanged();
+}
+
+window.addEventListener("DOMContentLoaded", () => selectorController());
function deviceSelect() {
const devices_count = TableController.getSelectedDevices().length;
@@ -268,7 +292,7 @@ function get_device_list() {
"Laptop": "",
};
- list_devices = devices.map((x) => {
+ const list_devices = devices.map((x) => {
let typ = $(x).data("device-type");
const manuf = $(x).data("device-manufacturer");
const dhid = $(x).data("device-dhid");
@@ -278,7 +302,7 @@ function get_device_list() {
return `${typ} ${manuf} ${dhid}`;
});
- description = $.map(list_devices, (x) => x).join(", ");
+ const description = $.map(list_devices, (x) => x).join(", ");
$(".enumeration-devices").html(description);
}
@@ -315,19 +339,30 @@ async function processSelectedDevices() {
const lotID = lot.id;
const srcElement = event.srcElement.parentElement.children[0]
const checked = !srcElement.checked;
+ const { indeterminate } = srcElement
const found = this.list.filter(list => list.lot.id == lotID)[0];
if (checked) {
if (found && found.type == "Remove") {
- found.type = "Add";
+ const affectedDevices = found.devices.filter(dev => found.lot.devices.includes(dev.id))
+ if (affectedDevices.length > 0 && found.indeterminate == false) { // Remove action from list
+ actions.list = actions.list.filter(x => x.lot.id != found.lot.id)
+ } else {
+ found.type = "Add";
+ }
} else {
- this.list.push({ type: "Add", lot, devices: selectedDevices });
+ this.list.push({ type: "Add", lot, devices: selectedDevices, indeterminate });
}
} else if (found && found.type == "Add") {
- found.type = "Remove";
+ const affectedDevices = found.devices.filter(dev => !found.lot.devices.includes(dev.id))
+ if (affectedDevices.length > 0 && found.indeterminate == false) { // Remove action from list
+ actions.list = actions.list.filter(x => x.lot.id != found.lot.id)
+ } else {
+ found.type = "Remove";
+ }
} else {
- this.list.push({ type: "Remove", lot, devices: selectedDevices });
+ this.list.push({ type: "Remove", lot, devices: selectedDevices, indeterminate });
}
if (this.list.length > 0) {
@@ -403,22 +438,23 @@ async function processSelectedDevices() {
const tmpDiv = document.createElement("div")
tmpDiv.innerHTML = newRequest
- const newTable = Array.from(tmpDiv.querySelectorAll("table.table > tbody > tr .deviceSelect")).map(x => x.attributes["data-device-dhid"].value)
+
+ const newTable = document.createElement("table")
+ newTable.innerHTML = tmpDiv.querySelector("table").innerHTML
+ newTable.classList = "table"
- // https://github.com/fiduswriter/Simple-DataTables/wiki/rows()#removeselect-arraynumber
- const rowsToRemove = []
- for (let i = 0; i < table.activeRows.length; i++) {
- const row = table.activeRows[i];
- if (!newTable.includes(row.querySelector("input").attributes["data-device-dhid"].value)) {
- rowsToRemove.push(i)
- }
- }
- table.rows().remove(rowsToRemove);
+ const oldTable = document.querySelector(".dataTable-wrapper")
+ oldTable.parentElement.replaceChild(newTable, oldTable)
- // Restore state of checkbox
+ table = new simpleDatatables.DataTable(newTable, {perPage: 20})
+
+ // // Restore state of checkbox
const selectAllBTN = document.getElementById("SelectAllBTN");
selectAllBTN.checked = false;
selectAllBTN.indeterminate = false;
+
+ // Re-init SelectorController
+ selectorController("softInit");
}
}
diff --git a/ereuse_devicehub/templates/ereuse_devicehub/base_site.html b/ereuse_devicehub/templates/ereuse_devicehub/base_site.html
index fe014107..acf6efa2 100644
--- a/ereuse_devicehub/templates/ereuse_devicehub/base_site.html
+++ b/ereuse_devicehub/templates/ereuse_devicehub/base_site.html
@@ -64,6 +64,16 @@
+
+
+
+ Workbench Settings
+
+
+
+
+
+
@@ -101,6 +111,15 @@
+ Devices
+
+
+
+
+ All devices
+
+
+
diff --git a/ereuse_devicehub/templates/ereuse_devicehub/user_login.html b/ereuse_devicehub/templates/ereuse_devicehub/user_login.html
index f010e3d7..f6423759 100644
--- a/ereuse_devicehub/templates/ereuse_devicehub/user_login.html
+++ b/ereuse_devicehub/templates/ereuse_devicehub/user_login.html
@@ -60,7 +60,7 @@
@@ -83,4 +83,18 @@
+
+
+
{% endblock body %}
diff --git a/ereuse_devicehub/templates/inventory/allocate.html b/ereuse_devicehub/templates/inventory/allocate.html
index f611a763..2f036795 100644
--- a/ereuse_devicehub/templates/inventory/allocate.html
+++ b/ereuse_devicehub/templates/inventory/allocate.html
@@ -29,6 +29,9 @@
{% else %}
{{ field.label(class_="form-label") }}
+ {% if field == form_new_allocate.start_time %}
+
*
+ {% endif %}
{{ field(class_="form-control") }}
{% if field.errors %}
diff --git a/ereuse_devicehub/templates/inventory/device_detail.html b/ereuse_devicehub/templates/inventory/device_detail.html
index f18c1e0e..66344ddb 100644
--- a/ereuse_devicehub/templates/inventory/device_detail.html
+++ b/ereuse_devicehub/templates/inventory/device_detail.html
@@ -124,16 +124,28 @@
Status Details
-
Physical States
-
{{ device.physical or '' }}
+
Physical State
+
+ {% if device.physical_status %}
+ {{ device.physical_status.type }}
+ {% endif %}
+
-
Trading States
-
{{ device.last_action_trading or ''}}
+
Lifecycle State
+
+ {% if device.status %}
+ {{ device.status.type }}
+ {% endif %}
+
-
Usage States
-
{{ device.usage or '' }}
+
Allocated State
+
+ {% if device.allocated_status %}
+ {{ device.allocated_status.type }}
+ {% endif %}
+
diff --git a/ereuse_devicehub/templates/inventory/device_list.html b/ereuse_devicehub/templates/inventory/device_list.html
index ef8f7aef..59050e5a 100644
--- a/ereuse_devicehub/templates/inventory/device_list.html
+++ b/ereuse_devicehub/templates/inventory/device_list.html
@@ -7,7 +7,11 @@
- Inventory
{% if not lot %}
- - Unassigned
+ {% if unassigned_devices %}
+ - Unassigned
+ {% else %}
+ - All devices
+ {% endif %}
{% elif lot.is_temporary %}
- Temporary Lot
- {{ lot.name }}
@@ -84,7 +88,7 @@