diff --git a/CHANGELOG.md b/CHANGELOG.md index 57c76542..bf4a03f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,9 @@ ml). ## master ## testing -- [added] #219 add functionality to searchbar (Lots and devices) +- [added] #219 Add functionality to searchbar (Lots and devices). - [changed] #211 Print DHID-QR label for selected devices. +- [changed] #218 Add reactivity to device lots. - [fixed] #214 Login workflow ## [2.0.0] - 2022-03-15 diff --git a/ereuse_devicehub/inventory/forms.py b/ereuse_devicehub/inventory/forms.py index 48484a1e..72f033f8 100644 --- a/ereuse_devicehub/inventory/forms.py +++ b/ereuse_devicehub/inventory/forms.py @@ -97,62 +97,6 @@ class FilterForm(FlaskForm): return ['Desktop', 'Laptop', 'Server'] -class LotDeviceForm(FlaskForm): - lot = StringField('Lot', [validators.UUID()]) - devices = StringField('Devices', [validators.length(min=1)]) - - def validate(self, extra_validators=None): - is_valid = super().validate(extra_validators) - - if not is_valid: - return False - - self._lot = ( - Lot.query.outerjoin(Trade) - .filter(Lot.id == self.lot.data) - .filter( - or_( - Trade.user_from == g.user, - Trade.user_to == g.user, - Lot.owner_id == g.user.id, - ) - ) - .one() - ) - - devices = set(self.devices.data.split(",")) - self._devices = ( - Device.query.filter(Device.id.in_(devices)) - .filter(Device.owner_id == g.user.id) - .distinct() - .all() - ) - - return bool(self._devices) - - def save(self, commit=True): - trade = self._lot.trade - if trade: - for dev in self._devices: - if trade not in dev.actions: - trade.devices.add(dev) - - if self._devices: - self._lot.devices.update(self._devices) - db.session.add(self._lot) - - if commit: - db.session.commit() - - def remove(self, commit=True): - if self._devices: - self._lot.devices.difference_update(self._devices) - db.session.add(self._lot) - - if commit: - db.session.commit() - - class LotForm(FlaskForm): name = StringField('Name', [validators.length(min=1)]) diff --git a/ereuse_devicehub/inventory/views.py b/ereuse_devicehub/inventory/views.py index 66b28563..38ff4bc6 100644 --- a/ereuse_devicehub/inventory/views.py +++ b/ereuse_devicehub/inventory/views.py @@ -16,7 +16,6 @@ from ereuse_devicehub.inventory.forms import ( AllocateForm, DataWipeForm, FilterForm, - LotDeviceForm, LotForm, NewActionForm, NewDeviceForm, @@ -109,7 +108,6 @@ class DeviceListMix(GenericMixView): self.context = { 'devices': devices, 'lots': lots, - 'form_lot_device': LotDeviceForm(), 'form_tag_device': TagDeviceForm(), 'form_new_action': form_new_action, 'form_new_allocate': form_new_allocate, @@ -153,46 +151,6 @@ class DeviceDetailView(GenericMixView): return flask.render_template(self.template_name, **context) -class LotDeviceAddView(View): - methods = ['POST'] - decorators = [login_required] - template_name = 'inventory/device_list.html' - - def dispatch_request(self): - form = LotDeviceForm() - if form.validate_on_submit(): - form.save(commit=False) - messages.success( - 'Add devices to lot "{}" successfully!'.format(form._lot.name) - ) - db.session.commit() - else: - messages.error('Error adding devices to lot!') - - next_url = request.referrer or url_for('inventory.devicelist') - return flask.redirect(next_url) - - -class LotDeviceDeleteView(View): - methods = ['POST'] - decorators = [login_required] - template_name = 'inventory/device_list.html' - - def dispatch_request(self): - form = LotDeviceForm() - if form.validate_on_submit(): - form.remove(commit=False) - messages.success( - 'Remove devices from lot "{}" successfully!'.format(form._lot.name) - ) - db.session.commit() - else: - messages.error('Error removing devices from lot!') - - next_url = request.referrer or url_for('inventory.devicelist') - return flask.redirect(next_url) - - class LotCreateView(GenericMixView): methods = ['GET', 'POST'] decorators = [login_required] @@ -607,12 +565,6 @@ devices.add_url_rule( devices.add_url_rule( '/lot//device/', view_func=DeviceListView.as_view('lotdevicelist') ) -devices.add_url_rule( - '/lot/devices/add/', view_func=LotDeviceAddView.as_view('lot_devices_add') -) -devices.add_url_rule( - '/lot/devices/del/', view_func=LotDeviceDeleteView.as_view('lot_devices_del') -) devices.add_url_rule('/lot/add/', view_func=LotCreateView.as_view('lot_add')) devices.add_url_rule( '/lot//del/', view_func=LotDeleteView.as_view('lot_del') diff --git a/ereuse_devicehub/static/js/api.js b/ereuse_devicehub/static/js/api.js new file mode 100644 index 00000000..ee98a08f --- /dev/null +++ b/ereuse_devicehub/static/js/api.js @@ -0,0 +1,76 @@ +const Api = { + /** + * get lots id + * @returns get lots + */ + async get_lots() { + var request = await this.doRequest(API_URLS.lots, "GET", null); + if (request != undefined) return request.items; + throw request; + }, + + /** + * Get filtered devices info + * @param {number[]} ids devices ids + * @returns full detailed device list + */ + async get_devices(ids) { + var request = await this.doRequest(API_URLS.devices + '?filter={"id": [' + ids.toString() + ']}', "GET", null); + if (request != undefined) return request.items; + throw request; + }, + + /** + * Get filtered devices info + * @param {number[]} ids devices ids + * @returns full detailed device list + */ + async search_device(id) { + var request = await this.doRequest(API_URLS.devices + '?filter={"devicehub_id": ["' + id + '"]}', "GET", null) + if (request != undefined) return request.items + throw request + }, + + /** + * Add devices to lot + * @param {number} lotID lot id + * @param {number[]} listDevices list devices id + */ + async devices_add(lotID, listDevices) { + var queryURL = API_URLS.devices_modify.replace("UUID", lotID) + "?" + listDevices.map(deviceID => "id=" + deviceID).join("&"); + return await Api.doRequest(queryURL, "POST", null); + }, + + /** + * Remove devices from a lot + * @param {number} lotID lot id + * @param {number[]} listDevices list devices id + */ + async devices_remove(lotID, listDevices) { + var queryURL = API_URLS.devices_modify.replace("UUID", lotID) + "?" + listDevices.map(deviceID => "id=" + deviceID).join("&"); + return await Api.doRequest(queryURL, "DELETE", null); + }, + + /** + * + * @param {string} url URL to be requested + * @param {String} type Action type + * @param {String | Object} body body content + * @returns + */ + async doRequest(url, type, body) { + var result; + try { + result = await $.ajax({ + url: url, + type: type, + headers: { "Authorization": API_URLS.Auth_Token }, + body: body + }); + return result; + } catch (error) { + console.error(error); + throw error; + } + } +} \ No newline at end of file diff --git a/ereuse_devicehub/static/js/main.js b/ereuse_devicehub/static/js/main.js index 695378db..5eaec3ea 100644 --- a/ereuse_devicehub/static/js/main.js +++ b/ereuse_devicehub/static/js/main.js @@ -4,7 +4,7 @@ * Author: BootstrapMade.com * License: https://bootstrapmade.com/license/ */ -(function() { +(function () { "use strict"; /** @@ -41,7 +41,7 @@ * Sidebar toggle */ if (select('.toggle-sidebar-btn')) { - on('click', '.toggle-sidebar-btn', function(e) { + on('click', '.toggle-sidebar-btn', function (e) { select('body').classList.toggle('toggle-sidebar') }) } @@ -50,7 +50,7 @@ * Search bar toggle */ if (select('.search-bar-toggle')) { - on('click', '.search-bar-toggle', function(e) { + on('click', '.search-bar-toggle', function (e) { select('.search-bar').classList.toggle('search-bar-show') }) } @@ -111,7 +111,7 @@ * Initiate tooltips */ var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')) - var tooltipList = tooltipTriggerList.map(function(tooltipTriggerEl) { + var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { return new bootstrap.Tooltip(tooltipTriggerEl) }) @@ -141,31 +141,31 @@ }], ["bold", "italic", "underline", "strike"], [{ - color: [] - }, - { - background: [] - } + color: [] + }, + { + background: [] + } ], [{ - script: "super" - }, - { - script: "sub" - } + script: "super" + }, + { + script: "sub" + } ], [{ - list: "ordered" - }, - { - list: "bullet" - }, - { - indent: "-1" - }, - { - indent: "+1" - } + list: "ordered" + }, + { + list: "bullet" + }, + { + indent: "-1" + }, + { + indent: "+1" + } ], ["direction", { align: [] @@ -184,8 +184,8 @@ var needsValidation = document.querySelectorAll('.needs-validation') Array.prototype.slice.call(needsValidation) - .forEach(function(form) { - form.addEventListener('submit', function(event) { + .forEach(function (form) { + form.addEventListener('submit', function (event) { if (!form.checkValidity()) { event.preventDefault() event.stopPropagation() @@ -209,7 +209,7 @@ const mainContainer = select('#main'); if (mainContainer) { setTimeout(() => { - new ResizeObserver(function() { + new ResizeObserver(function () { select('.echart', true).forEach(getEchart => { echarts.getInstanceByDom(getEchart).resize(); }) @@ -217,4 +217,167 @@ }, 200); } + /** + * Select all functionality + */ + var btnSelectAll = document.getElementById("SelectAllBTN"); + var tableListCheckboxes = document.querySelectorAll(".deviceSelect"); + + function itemListCheckChanged(event) { + let isAllChecked = Array.from(tableListCheckboxes).map(itm => itm.checked); + if (isAllChecked.every(bool => bool == true)) { + btnSelectAll.checked = true; + btnSelectAll.indeterminate = false; + } else if (isAllChecked.every(bool => bool == false)) { + btnSelectAll.checked = false; + btnSelectAll.indeterminate = false; + } else { + btnSelectAll.indeterminate = true; + } + } + + tableListCheckboxes.forEach(item => { + item.addEventListener("click", itemListCheckChanged); + }) + + btnSelectAll.addEventListener("click", event => { + let checkedState = event.target.checked; + tableListCheckboxes.forEach(ckeckbox => ckeckbox.checked = checkedState); + }) + + /** + * Avoid hide dropdown when user clicked inside + */ + document.getElementById("dropDownLotsSelector").addEventListener("click", event => { + event.stopPropagation(); + }) + + /** + * Search form functionality + */ + window.addEventListener("DOMContentLoaded", () => { + var searchForm = document.getElementById("SearchForm") + var inputSearch = document.querySelector("#SearchForm > input") + var doSearch = true + + searchForm.addEventListener("submit", (event) => { + event.preventDefault(); + }) + + let timeoutHandler = setTimeout(() => { }, 1) + let dropdownList = document.getElementById("dropdown-search-list") + let defaultEmptySearch = document.getElementById("dropdown-search-list").innerHTML + + + inputSearch.addEventListener("input", (e) => { + clearTimeout(timeoutHandler) + let searchText = e.target.value + if (searchText == '') { + document.getElementById("dropdown-search-list").innerHTML = defaultEmptySearch; + return + } + + let resultCount = 0; + function searchCompleted() { + resultCount++; + setTimeout(() => { + if (resultCount == 2 && document.getElementById("dropdown-search-list").children.length == 2) { + document.getElementById("dropdown-search-list").innerHTML = ` + ` + } + }, 100) + } + + timeoutHandler = setTimeout(async () => { + dropdownList.innerHTML = ` + + `; + + + try { + Api.search_device(searchText.toUpperCase()).then(devices => { + dropdownList.querySelector("#deviceSearchLoader").style = "display: none" + + for (let i = 0; i < devices.length; i++) { + const device = devices[i]; + + // See: ereuse_devicehub/resources/device/models.py + var verboseName = `${device.type} ${device.manufacturer} ${device.model}` + + const templateString = ` +
  • + + + ${verboseName} + ${device.devicehubID} + +
  • `; + dropdownList.innerHTML += templateString + if (i == 4) { // Limit to 4 resullts + break; + } + } + + searchCompleted(); + }) + } catch (error) { + dropdownList.innerHTML += ` + `; + console.log(error); + } + + try { + Api.get_lots().then(lots => { + dropdownList.querySelector("#lotSearchLoader").style = "display: none" + for (let i = 0; i < lots.length; i++) { + const lot = lots[i]; + if (lot.name.toUpperCase().includes(searchText.toUpperCase())) { + const templateString = ` +
  • + + + ${lot.name} + +
  • `; + dropdownList.innerHTML += templateString + if (i == 4) { // Limit to 4 resullts + break; + } + } + } + searchCompleted(); + }) + + } catch (error) { + dropdownList.innerHTML += ` + `; + console.log(error); + } + }, 1000) + }) + }) + })(); diff --git a/ereuse_devicehub/static/js/main_inventory.js b/ereuse_devicehub/static/js/main_inventory.js index 8bf254a4..bf2fdf43 100644 --- a/ereuse_devicehub/static/js/main_inventory.js +++ b/ereuse_devicehub/static/js/main_inventory.js @@ -1,359 +1,384 @@ -$(document).ready(function () { - var show_allocate_form = $("#allocateModal").data('show-action-form'); - var show_datawipe_form = $("#datawipeModal").data('show-action-form'); - var show_trade_form = $("#tradeLotModal").data('show-action-form'); - if (show_allocate_form != "None") { - $("#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(); -}) - -function deviceSelect() { - var devices_count = $(".deviceSelect").filter(':checked').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() { - var devices = $(".deviceSelect"); - if (devices.length > 0) { - $("#btnRemoveLots .text-danger").show(); - } else { - $("#btnRemoveLots .text-danger").hide(); - } - $("#activeRemoveLotModal").click(); -} - -function removeTag() { - var devices = $(".deviceSelect").filter(':checked'); - var devices_id = $.map(devices, function (x) { return $(x).attr('data') }); - if (devices_id.length == 1) { - var url = "/inventory/tag/devices/" + devices_id[0] + "/del/"; - window.location.href = url; - } else { - $("#unlinkTagAlertModal").click(); - } -} - -function addTag() { - var devices = $(".deviceSelect").filter(':checked'); - var devices_id = $.map(devices, function (x) { return $(x).attr('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) { - var title = "Trade " - var user_to = $("#user_to").data("email"); - var user_from = $("#user_from").data("email"); - if (action == 'user_from') { - title = 'Trade Incoming'; - $("#user_to").attr('readonly', 'readonly'); - $("#user_from").prop('readonly', false); - $("#user_from").val(''); - $("#user_to").val(user_to); - } else if (action == 'user_to') { - title = 'Trade Outgoing'; - $("#user_from").attr('readonly', 'readonly'); - $("#user_to").prop('readonly', false); - $("#user_to").val(''); - $("#user_from").val(user_from); - } - $("#tradeLotModal #title-action").html(title); - $("#activeTradeModal").click(); -} - -function newAction(action) { - $("#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() { - var devices = $(".deviceSelect").filter(':checked'); - - /* Insert the correct count of devices in actions form */ - var 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 */ - var devices_id = $.map(devices, function (x) { return $(x).attr('data') }).join(","); - $.map($(".devicesList"), function (x) { - $(x).val(devices_id); - }); - - /* Create a list of devices for human representation */ - var computer = { - "Desktop": "", - "Laptop": "", - }; - list_devices = devices.map(function (x) { - var typ = $(devices[x]).data("device-type"); - var manuf = $(devices[x]).data("device-manufacturer"); - var dhid = $(devices[x]).data("device-dhid"); - if (computer[typ]) { - typ = computer[typ]; - }; - return typ + " " + manuf + " " + dhid; - }); - - description = $.map(list_devices, function (x) { return x }).join(", "); - $(".enumeration-devices").html(description); -} - -function export_file(type_file) { - var devices = $(".deviceSelect").filter(':checked'); - var devices_id = $.map(devices, function (x) { return $(x).attr('data-device-dhid') }).join(","); - if (devices_id) { - var url = "/inventory/export/" + type_file + "/?ids=" + devices_id; - window.location.href = url; - } else { - $("#exportAlertModal").click(); - } -} - -window.addEventListener("DOMContentLoaded", () => { - var searchForm = document.getElementById("SearchForm") - var inputSearch = document.querySelector("#SearchForm > input") - var doSearch = true - - const Api = { - /** - * get lots id - * @returns get lots - */ - async get_lots() { - var request = await this.doRequest(API_URLS.lots, "GET", null) - if (request != undefined) return request.items - throw request - }, - - /** - * Get filtered devices info - * @param {number[]} ids devices ids - * @returns full detailed device list - */ - async get_devices(id) { - var request = await this.doRequest(API_URLS.devices + '?filter={"devicehub_id": ["' + id + '"]}', "GET", null) - if (request != undefined) return request.items - throw request - }, - - /** - * - * @param {string} url URL to be requested - * @param {String} type Action type - * @param {String | Object} body body content - * @returns {Object[]} - */ - async doRequest(url, type, body) { - var result; - try { - result = await $.ajax({ - url: url, - type: type, - headers: { - "Authorization": API_URLS.Auth_Token - }, - body: body - }); - return result - } catch (error) { - console.error(error) - throw error - } - } - } - - - - searchForm.addEventListener("submit", (event) => { - event.preventDefault(); - }) - - let timeoutHandler = setTimeout(() => { }, 1) - let dropdownList = document.getElementById("dropdown-search-list") - let defaultEmptySearch = document.getElementById("dropdown-search-list").innerHTML - - - inputSearch.addEventListener("input", (e) => { - clearTimeout(timeoutHandler) - let searchText = e.target.value - if (searchText == '') { - document.getElementById("dropdown-search-list").innerHTML = defaultEmptySearch; - return - } - - let resultCount = 0; - function searchCompleted() { - resultCount++; - if (resultCount < 2 && document.getElementById("dropdown-search-list").children.length > 0) { - setTimeout(() => { - document.getElementById("dropdown-search-list").innerHTML = ` - ` - }, 100) - } - } - - timeoutHandler = setTimeout(async () => { - dropdownList.innerHTML = ` - - `; - - - try { - Api.get_devices(searchText.toUpperCase()).then(devices => { - dropdownList.querySelector("#deviceSearchLoader").style = "display: none" - - for (let i = 0; i < devices.length; i++) { - const device = devices[i]; - - // See: ereuse_devicehub/resources/device/models.py - var verboseName = `${device.type} ${device.manufacturer} ${device.model}` - - const templateString = ` -
  • - - - ${verboseName} - ${device.devicehubID} - -
  • `; - dropdownList.innerHTML += templateString - if (i == 4) { // Limit to 4 resullts - break; - } - } - - searchCompleted(); - }) - } catch (error) { - dropdownList.innerHTML += ` - `; - console.log(error); - } - - try { - Api.get_lots().then(lots => { - dropdownList.querySelector("#lotSearchLoader").style = "display: none" - for (let i = 0; i < lots.length; i++) { - const lot = lots[i]; - if (lot.name.toUpperCase().includes(searchText.toUpperCase())) { - const templateString = ` -
  • - - - ${lot.name} - -
  • `; - dropdownList.innerHTML += templateString - if (i == 4) { // Limit to 4 resullts - break; - } - } - } - searchCompleted(); - }) - - } catch (error) { - dropdownList.innerHTML += ` - `; - console.log(error); - } - }, 1000) - }) - - -}) \ No newline at end of file +$(document).ready(function() { + var show_allocate_form = $("#allocateModal").data('show-action-form'); + var show_datawipe_form = $("#datawipeModal").data('show-action-form'); + var show_trade_form = $("#tradeLotModal").data('show-action-form'); + if (show_allocate_form != "None") { + $("#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(); +}) + +function deviceSelect() { + var devices_count = $(".deviceSelect").filter(':checked').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() { + var devices = $(".deviceSelect"); + if (devices.length > 0) { + $("#btnRemoveLots .text-danger").show(); + } else { + $("#btnRemoveLots .text-danger").hide(); + } + $("#activeRemoveLotModal").click(); +} + +function removeTag() { + var devices = $(".deviceSelect").filter(':checked'); + var devices_id = $.map(devices, function(x) { return $(x).attr('data')}); + if (devices_id.length == 1) { + var url = "/inventory/tag/devices/"+devices_id[0]+"/del/"; + window.location.href = url; + } else { + $("#unlinkTagAlertModal").click(); + } +} + +function addTag() { + var devices = $(".deviceSelect").filter(':checked'); + var devices_id = $.map(devices, function(x) { return $(x).attr('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) { + var title = "Trade " + var user_to = $("#user_to").data("email"); + var user_from = $("#user_from").data("email"); + if (action == 'user_from') { + title = 'Trade Incoming'; + $("#user_to").attr('readonly', 'readonly'); + $("#user_from").prop('readonly', false); + $("#user_from").val(''); + $("#user_to").val(user_to); + } else if (action == 'user_to') { + title = 'Trade Outgoing'; + $("#user_from").attr('readonly', 'readonly'); + $("#user_to").prop('readonly', false); + $("#user_to").val(''); + $("#user_from").val(user_from); + } + $("#tradeLotModal #title-action").html(title); + $("#activeTradeModal").click(); +} + +function newAction(action) { + $("#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() { + var devices = $(".deviceSelect").filter(':checked'); + + /* Insert the correct count of devices in actions form */ + var 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 */ + var devices_id = $.map(devices, function(x) { return $(x).attr('data')}).join(","); + $.map($(".devicesList"), function(x) { + $(x).val(devices_id); + }); + + /* Create a list of devices for human representation */ + var computer = { + "Desktop": "", + "Laptop": "", + }; + list_devices = devices.map(function (x) { + var typ = $(devices[x]).data("device-type"); + var manuf = $(devices[x]).data("device-manufacturer"); + var dhid = $(devices[x]).data("device-dhid"); + if (computer[typ]) { + typ = computer[typ]; + }; + return typ + " " + manuf + " " + dhid; + }); + + description = $.map(list_devices, function(x) { return x }).join(", "); + $(".enumeration-devices").html(description); +} + +function export_file(type_file) { + var devices = $(".deviceSelect").filter(':checked'); + var devices_id = $.map(devices, function(x) { return $(x).attr('data-device-dhid')}).join(","); + if (devices_id){ + var url = "/inventory/export/"+type_file+"/?ids="+devices_id; + window.location.href = url; + } else { + $("#exportAlertModal").click(); + } +} + + +/** + * 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 {*} ev event (Should be a checkbox type) + * @param {string} lotID lot id + * @param {number} deviceID device id + */ + manage(event, lotID, deviceListID) { + event.preventDefault(); + const indeterminate = event.srcElement.indeterminate; + const checked = !event.srcElement.checked; + + var found = this.list.filter(list => list.lotID == lotID)[0]; + var foundIndex = found != undefined ? this.list.findLastIndex(x => x.lotID == found.lotID) : -1; + + if (checked) { + if (found != undefined && found.type == "Remove") { + if (found.isFromIndeterminate == true) { + found.type = "Add"; + this.list[foundIndex] = found; + } else { + this.list = this.list.filter(list => list.lotID != lotID); + } + } else { + this.list.push({ type: "Add", lotID: lotID, devices: deviceListID, isFromIndeterminate: indeterminate }); + } + } else { + if (found != undefined && found.type == "Add") { + if (found.isFromIndeterminate == true) { + found.type = "Remove"; + this.list[foundIndex] = found; + } else { + this.list = this.list.filter(list => list.lotID != lotID); + } + } else { + this.list.push({ type: "Remove", lotID: lotID, devices: deviceListID, isFromIndeterminate: 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) { + let toast = document.createElement("div"); + toast.classList = "alert alert-dismissible fade show " + (isError ? "alert-danger" : "alert-success"); + toast.attributes["data-autohide"] = !isError; + toast.attributes["role"] = "alert"; + toast.style = "margin-left: auto; width: fit-content;"; + toast.innerHTML = `${title}`; + if (toastText && toastText.length > 0) { + toast.innerHTML += `
    ${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() { + var 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 { + await Api.devices_add(action.lotID, action.devices); + this.notifyUser("Devices sucefully aded 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 { + await Api.devices_remove(action.lotID, action.devices); + this.notifyUser("Devices sucefully removed from selected lot/s", "", false); + } catch (error) { + this.notifyUser("Fail to remove devices from selected lot/s", error.responseJSON.message, true); + } + } + requestCount += 1 + if (requestCount == this.list.length) { + this.reRenderTable(); + this.list = []; + } + }) + document.getElementById("dropDownLotsSelector").classList.remove("show"); + } + + /** + * Re-render list in table + */ + async reRenderTable() { + var newRequest = await Api.doRequest(window.location) + + var tmpDiv = document.createElement("div") + tmpDiv.innerHTML = newRequest + + var oldTable = Array.from(document.querySelectorAll("table.table > tbody > tr .deviceSelect")).map(x => x.attributes["data-device-dhid"].value) + var newTable = Array.from(tmpDiv.querySelectorAll("table.table > tbody > tr .deviceSelect")).map(x => x.attributes["data-device-dhid"].value) + + for (let i = 0; i < oldTable.length; i++) { + if (!newTable.includes(oldTable[i])) { + // variable from device_list.html --> See: ereuse_devicehub\templates\inventory\device_list.html (Ln: 411) + table.rows().remove(i) + } + } + } + } + + var eventClickActions; + + /** + * Generates a list item with a correspondient checkbox state + * @param {String} lotID + * @param {String} lotName + * @param {Array} selectedDevicesIDs + * @param {HTMLElement} target + */ + function templateLot(lotID, lot, selectedDevicesIDs, elementTarget, actions) { + elementTarget.innerHTML = "" + + var htmlTemplate = ` + `; + + var existLotList = selectedDevicesIDs.map(selected => lot.devices.includes(selected)); + + var doc = document.createElement('li'); + doc.innerHTML = htmlTemplate; + + if (selectedDevicesIDs.length <= 0) { + doc.children[0].disabled = true; + } else if (existLotList.every(value => value == true)) { + doc.children[0].checked = true; + } else if (existLotList.every(value => value == false)) { + doc.children[0].checked = false; + } else { + doc.children[0].indeterminate = true; + } + + doc.children[0].addEventListener('mouseup', (ev) => actions.manage(ev, lotID, selectedDevicesIDs)); + elementTarget.append(doc); + } + + var listHTML = $("#LotsSelector") + + // Get selected devices + var selectedDevicesIDs = $.map($(".deviceSelect").filter(':checked'), function (x) { return parseInt($(x).attr('data')) }); + if (selectedDevicesIDs.length <= 0) { + listHTML.html('
  • No devices selected
  • '); + return; + } + + // Initialize Actions list, and set checkbox triggers + var actions = new Actions(); + if (eventClickActions) { + document.getElementById("ApplyDeviceLots").removeEventListener(eventClickActions); + } + eventClickActions = document.getElementById("ApplyDeviceLots").addEventListener("click", () => actions.doActions()); + document.getElementById("ApplyDeviceLots").classList.add("disabled"); + + try { + listHTML.html('
  • ') + var devices = await Api.get_devices(selectedDevicesIDs); + var lots = await Api.get_lots(); + + lots = lots.map(lot => { + lot.devices = devices + .filter(device => device.lots.filter(devicelot => devicelot.id == lot.id).length > 0) + .map(device => parseInt(device.id)); + return lot; + }) + + listHTML.html(''); + lots.forEach(lot => templateLot(lot.id, lot, selectedDevicesIDs, listHTML, actions)); + } catch (error) { + console.log(error); + listHTML.html('
  • Error feching devices and lots
    (see console for more details)
  • '); + } +} diff --git a/ereuse_devicehub/templates/ereuse_devicehub/base.html b/ereuse_devicehub/templates/ereuse_devicehub/base.html index 7b56a1ec..8e147828 100644 --- a/ereuse_devicehub/templates/ereuse_devicehub/base.html +++ b/ereuse_devicehub/templates/ereuse_devicehub/base.html @@ -50,6 +50,20 @@ + + + + diff --git a/ereuse_devicehub/templates/ereuse_devicehub/base_site.html b/ereuse_devicehub/templates/ereuse_devicehub/base_site.html index 8e03f429..4e721730 100644 --- a/ereuse_devicehub/templates/ereuse_devicehub/base_site.html +++ b/ereuse_devicehub/templates/ereuse_devicehub/base_site.html @@ -1,245 +1,241 @@ -{% extends "ereuse_devicehub/base.html" %} -{% block page_title %}{{ page_title }}{% endblock %} -{% block body %} - - - - - - -
    - {% block messages %} - {% for level, message in get_flashed_messages(with_categories=true) %} - - {% endfor %} - {% endblock %} - {% block main %} - - {% endblock main %} -
    - - -
    - -
    - - - - - Designed by BootstrapMade // DeviceHub {{ version }} -
    -
    - - - - -{% endblock body %} +{% extends "ereuse_devicehub/base.html" %} +{% block page_title %}{{ page_title }}{% endblock %} +{% block body %} + + + + + + +
    + {% block messages %} + {% for level, message in get_flashed_messages(with_categories=true) %} + + {% endfor %} + {% endblock %} + {% block main %} + + {% endblock main %} +
    + + +
    + +
    + + + + + Designed by BootstrapMade // DeviceHub {{ version }} +
    +
    + +{% endblock body %} \ No newline at end of file diff --git a/ereuse_devicehub/templates/inventory/addDeviceslot.html b/ereuse_devicehub/templates/inventory/addDeviceslot.html deleted file mode 100644 index bc051d42..00000000 --- a/ereuse_devicehub/templates/inventory/addDeviceslot.html +++ /dev/null @@ -1,33 +0,0 @@ - diff --git a/ereuse_devicehub/templates/inventory/device_list.html b/ereuse_devicehub/templates/inventory/device_list.html index b1c45382..c7e8c133 100644 --- a/ereuse_devicehub/templates/inventory/device_list.html +++ b/ereuse_devicehub/templates/inventory/device_list.html @@ -71,25 +71,21 @@ {% endif %}
    - +
    +
    +
    -{% include "inventory/addDeviceslot.html" %} {% include "inventory/addDevicestag.html" %} -{% include "inventory/removeDeviceslot.html" %} {% include "inventory/lot_delete_modal.html" %} {% include "inventory/actions.html" %} {% include "inventory/allocate.html" %} diff --git a/ereuse_devicehub/templates/inventory/removeDeviceslot.html b/ereuse_devicehub/templates/inventory/removeDeviceslot.html deleted file mode 100644 index 186bcaf6..00000000 --- a/ereuse_devicehub/templates/inventory/removeDeviceslot.html +++ /dev/null @@ -1,32 +0,0 @@ -