Merge pull request #201 from eReuse/feature/server-side-render-actions-documents
Server side render Data Wipe Action
This commit is contained in:
commit
ea26dc2e2f
|
@ -47,9 +47,6 @@ jobs:
|
||||||
sudo apt-get update -qy
|
sudo apt-get update -qy
|
||||||
sudo apt-get -y install postgresql-client
|
sudo apt-get -y install postgresql-client
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install virtualenv
|
|
||||||
virtualenv env
|
|
||||||
source env/bin/activate
|
|
||||||
pip install flake8 pytest coverage
|
pip install flake8 pytest coverage
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
@ -65,6 +62,17 @@ jobs:
|
||||||
psql -h "localhost" -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "CREATE EXTENSION citext SCHEMA public;"
|
psql -h "localhost" -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "CREATE EXTENSION citext SCHEMA public;"
|
||||||
psql -h "localhost" -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "CREATE EXTENSION pg_trgm SCHEMA public;"
|
psql -h "localhost" -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "CREATE EXTENSION pg_trgm SCHEMA public;"
|
||||||
|
|
||||||
|
- name: Lint with flake8
|
||||||
|
run: |
|
||||||
|
# stop the build if:
|
||||||
|
# - E9,F63,F7,F82: Python syntax errors or undefined names
|
||||||
|
# - E501: line longer than 120 characters
|
||||||
|
# - C901: complexity greater than 10
|
||||||
|
# - F401: modules imported but unused
|
||||||
|
# See: https://flake8.pycqa.org/en/latest/user/error-codes.html
|
||||||
|
flake8 . --select=E9,F63,F7,F82,E501,C901,F401
|
||||||
|
flake8 . --exit-zero
|
||||||
|
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: |
|
run: |
|
||||||
export SECRET_KEY=`python3 -c 'import secrets; print(secrets.token_hex())'`
|
export SECRET_KEY=`python3 -c 'import secrets; print(secrets.token_hex())'`
|
||||||
|
|
|
@ -1,28 +1,29 @@
|
||||||
|
import copy
|
||||||
import json
|
import json
|
||||||
from json.decoder import JSONDecodeError
|
from json.decoder import JSONDecodeError
|
||||||
|
|
||||||
from flask import g, request
|
from boltons.urlutils import URL
|
||||||
from flask_wtf import FlaskForm
|
|
||||||
from sqlalchemy.util import OrderedSet
|
|
||||||
from wtforms import (DateField, FloatField, HiddenField, IntegerField,
|
|
||||||
MultipleFileField, SelectField, StringField,
|
|
||||||
TextAreaField, validators)
|
|
||||||
|
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
from ereuse_devicehub.resources.action.models import (Action, RateComputer,
|
from ereuse_devicehub.resources.action.models import RateComputer, Snapshot
|
||||||
Snapshot, VisualTest)
|
|
||||||
from ereuse_devicehub.resources.action.rate.v1_0 import CannotRate
|
from ereuse_devicehub.resources.action.rate.v1_0 import CannotRate
|
||||||
from ereuse_devicehub.resources.action.schemas import \
|
from ereuse_devicehub.resources.action.schemas import \
|
||||||
Snapshot as SnapshotSchema
|
Snapshot as SnapshotSchema
|
||||||
from ereuse_devicehub.resources.action.views.snapshot import (move_json,
|
from ereuse_devicehub.resources.action.views.snapshot import move_json, save_json
|
||||||
save_json)
|
|
||||||
from ereuse_devicehub.resources.device.models import (SAI, Cellphone, Computer,
|
from ereuse_devicehub.resources.device.models import (SAI, Cellphone, Computer,
|
||||||
Device, Keyboard,
|
Device, Keyboard, MemoryCardReader,
|
||||||
MemoryCardReader,
|
Monitor, Mouse, Smartphone, Tablet)
|
||||||
Monitor, Mouse,
|
from flask import g, request
|
||||||
Smartphone, Tablet)
|
from flask_wtf import FlaskForm
|
||||||
|
from sqlalchemy.util import OrderedSet
|
||||||
|
from wtforms import (BooleanField, DateField, FileField, FloatField, Form,
|
||||||
|
HiddenField, IntegerField, MultipleFileField, SelectField,
|
||||||
|
StringField, TextAreaField, URLField, validators)
|
||||||
|
from wtforms.fields import FormField
|
||||||
|
|
||||||
from ereuse_devicehub.resources.device.sync import Sync
|
from ereuse_devicehub.resources.device.sync import Sync
|
||||||
|
from ereuse_devicehub.resources.documents.models import DataWipeDocument
|
||||||
from ereuse_devicehub.resources.enums import Severity, SnapshotSoftware
|
from ereuse_devicehub.resources.enums import Severity, SnapshotSoftware
|
||||||
|
from ereuse_devicehub.resources.hash_reports import insert_hash
|
||||||
from ereuse_devicehub.resources.lot.models import Lot
|
from ereuse_devicehub.resources.lot.models import Lot
|
||||||
from ereuse_devicehub.resources.tag.model import Tag
|
from ereuse_devicehub.resources.tag.model import Tag
|
||||||
from ereuse_devicehub.resources.user.exceptions import InsufficientPermission
|
from ereuse_devicehub.resources.user.exceptions import InsufficientPermission
|
||||||
|
@ -472,10 +473,18 @@ class TagDeviceForm(FlaskForm):
|
||||||
|
|
||||||
|
|
||||||
class NewActionForm(FlaskForm):
|
class NewActionForm(FlaskForm):
|
||||||
name = StringField(u'Name', [validators.length(max=50)])
|
name = StringField(u'Name', [validators.length(max=50)],
|
||||||
|
description="A name or title of the event. Something to look for.")
|
||||||
devices = HiddenField()
|
devices = HiddenField()
|
||||||
date = DateField(u'Date', validators=(validators.Optional(),))
|
date = DateField(u'Date', [validators.Optional()],
|
||||||
severity = SelectField(u'Severity', choices=[(v.name, v.name) for v in Severity])
|
description="""When the action ends. For some actions like booking
|
||||||
|
the time when it expires, for others like renting the
|
||||||
|
time that the end rents. For specific actions, it is the
|
||||||
|
time in which they are carried out; differs from created
|
||||||
|
in that created is where the system receives the action.""")
|
||||||
|
severity = SelectField(u'Severity', choices=[(v.name, v.name) for v in Severity],
|
||||||
|
description="""An indicator that evaluates the execution of the event.
|
||||||
|
For example, failed events are set to Error""")
|
||||||
description = TextAreaField(u'Description')
|
description = TextAreaField(u'Description')
|
||||||
lot = HiddenField()
|
lot = HiddenField()
|
||||||
type = HiddenField()
|
type = HiddenField()
|
||||||
|
@ -537,3 +546,71 @@ class AllocateForm(NewActionForm):
|
||||||
is_valid = False
|
is_valid = False
|
||||||
|
|
||||||
return is_valid
|
return is_valid
|
||||||
|
|
||||||
|
|
||||||
|
class DataWipeDocumentForm(Form):
|
||||||
|
date = DateField(u'Date', [validators.Optional()],
|
||||||
|
description="Date when was data wipe")
|
||||||
|
url = URLField(u'Url', [validators.Optional()],
|
||||||
|
description="Url where the document resides")
|
||||||
|
success = BooleanField(u'Success', [validators.Optional()],
|
||||||
|
description="The erase was success or not?")
|
||||||
|
software = StringField(u'Software', [validators.Optional()],
|
||||||
|
description="Which software has you use for erase the disks")
|
||||||
|
id_document = StringField(u'Document Id', [validators.Optional()],
|
||||||
|
description="Identification number of document")
|
||||||
|
file_name = FileField(u'File', [validators.DataRequired()],
|
||||||
|
description="""This file is not stored on our servers, it is only used to
|
||||||
|
generate a digital signature and obtain the name of the file.""")
|
||||||
|
|
||||||
|
def validate(self, extra_validators=None):
|
||||||
|
is_valid = super().validate(extra_validators)
|
||||||
|
|
||||||
|
return is_valid
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
file_name = ''
|
||||||
|
file_hash = ''
|
||||||
|
if self.file_name.data:
|
||||||
|
file_name = self.file_name.data.filename
|
||||||
|
file_hash = insert_hash(self.file_name.data.read(), commit=False)
|
||||||
|
|
||||||
|
self.url.data = URL(self.url.data)
|
||||||
|
self._obj = DataWipeDocument(
|
||||||
|
document_type='DataWipeDocument',
|
||||||
|
)
|
||||||
|
self.populate_obj(self._obj)
|
||||||
|
self._obj.file_name = file_name
|
||||||
|
self._obj.file_hash = file_hash
|
||||||
|
db.session.add(self._obj)
|
||||||
|
if commit:
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return self._obj
|
||||||
|
|
||||||
|
|
||||||
|
class DataWipeForm(NewActionForm):
|
||||||
|
document = FormField(DataWipeDocumentForm)
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
self.document.form.save(commit=False)
|
||||||
|
|
||||||
|
Model = db.Model._decl_class_registry.data[self.type.data]()
|
||||||
|
self.instance = Model()
|
||||||
|
devices = self.devices.data
|
||||||
|
severity = self.severity.data
|
||||||
|
self.devices.data = self._devices
|
||||||
|
self.severity.data = Severity[self.severity.data]
|
||||||
|
|
||||||
|
document = copy.copy(self.document)
|
||||||
|
del self.document
|
||||||
|
self.populate_obj(self.instance)
|
||||||
|
self.instance.document = document.form._obj
|
||||||
|
db.session.add(self.instance)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
self.devices.data = devices
|
||||||
|
self.severity.data = severity
|
||||||
|
self.document = document
|
||||||
|
|
||||||
|
return self.instance
|
||||||
|
|
|
@ -8,7 +8,7 @@ from ereuse_devicehub.inventory.forms import (AllocateForm, LotDeviceForm,
|
||||||
LotForm, NewActionForm,
|
LotForm, NewActionForm,
|
||||||
NewDeviceForm, TagDeviceForm,
|
NewDeviceForm, TagDeviceForm,
|
||||||
TagForm, TagUnnamedForm,
|
TagForm, TagUnnamedForm,
|
||||||
UploadSnapshotForm)
|
UploadSnapshotForm, DataWipeForm)
|
||||||
from ereuse_devicehub.resources.device.models import Device
|
from ereuse_devicehub.resources.device.models import Device
|
||||||
from ereuse_devicehub.resources.lot.models import Lot
|
from ereuse_devicehub.resources.lot.models import Lot
|
||||||
from ereuse_devicehub.resources.tag.model import Tag
|
from ereuse_devicehub.resources.tag.model import Tag
|
||||||
|
@ -36,6 +36,7 @@ class DeviceListMix(View):
|
||||||
devices = sorted(devices, key=lambda x: x.updated, reverse=True)
|
devices = sorted(devices, key=lambda x: x.updated, reverse=True)
|
||||||
form_new_action = NewActionForm(lot=lot.id)
|
form_new_action = NewActionForm(lot=lot.id)
|
||||||
form_new_allocate = AllocateForm(lot=lot.id)
|
form_new_allocate = AllocateForm(lot=lot.id)
|
||||||
|
form_new_datawipe = DataWipeForm(lot=lot.id)
|
||||||
else:
|
else:
|
||||||
devices = Device.query.filter(
|
devices = Device.query.filter(
|
||||||
Device.owner_id == current_user.id).filter(
|
Device.owner_id == current_user.id).filter(
|
||||||
|
@ -43,6 +44,7 @@ class DeviceListMix(View):
|
||||||
Device.updated.desc())
|
Device.updated.desc())
|
||||||
form_new_action = NewActionForm()
|
form_new_action = NewActionForm()
|
||||||
form_new_allocate = AllocateForm()
|
form_new_allocate = AllocateForm()
|
||||||
|
form_new_datawipe = DataWipeForm()
|
||||||
|
|
||||||
action_devices = form_new_action.devices.data
|
action_devices = form_new_action.devices.data
|
||||||
list_devices = []
|
list_devices = []
|
||||||
|
@ -56,6 +58,7 @@ class DeviceListMix(View):
|
||||||
'form_tag_device': TagDeviceForm(),
|
'form_tag_device': TagDeviceForm(),
|
||||||
'form_new_action': form_new_action,
|
'form_new_action': form_new_action,
|
||||||
'form_new_allocate': form_new_allocate,
|
'form_new_allocate': form_new_allocate,
|
||||||
|
'form_new_datawipe': form_new_datawipe,
|
||||||
'lot': lot,
|
'lot': lot,
|
||||||
'tags': tags,
|
'tags': tags,
|
||||||
'list_devices': list_devices
|
'list_devices': list_devices
|
||||||
|
@ -331,8 +334,29 @@ class NewAllocateView(NewActionView, DeviceListMix):
|
||||||
return flask.render_template(self.template_name, **self.context)
|
return flask.render_template(self.template_name, **self.context)
|
||||||
|
|
||||||
|
|
||||||
|
class NewDataWipeView(NewActionView, DeviceListMix):
|
||||||
|
methods = ['POST']
|
||||||
|
form_class = DataWipeForm
|
||||||
|
|
||||||
|
def dispatch_request(self):
|
||||||
|
self.form = self.form_class()
|
||||||
|
|
||||||
|
if self.form.validate_on_submit():
|
||||||
|
instance = self.form.save()
|
||||||
|
messages.success('Action "{}" created successfully!'.format(instance.type))
|
||||||
|
|
||||||
|
next_url = self.get_next_url()
|
||||||
|
return flask.redirect(next_url)
|
||||||
|
|
||||||
|
lot_id = self.form.lot.data
|
||||||
|
self.get_context(lot_id)
|
||||||
|
self.context['form_new_datawipe'] = self.form
|
||||||
|
return flask.render_template(self.template_name, **self.context)
|
||||||
|
|
||||||
|
|
||||||
devices.add_url_rule('/action/add/', view_func=NewActionView.as_view('action_add'))
|
devices.add_url_rule('/action/add/', view_func=NewActionView.as_view('action_add'))
|
||||||
devices.add_url_rule('/action/allocate/add/', view_func=NewAllocateView.as_view('allocate_add'))
|
devices.add_url_rule('/action/allocate/add/', view_func=NewAllocateView.as_view('allocate_add'))
|
||||||
|
devices.add_url_rule('/action/datawipe/add/', view_func=NewDataWipeView.as_view('datawipe_add'))
|
||||||
devices.add_url_rule('/device/', view_func=DeviceListView.as_view('devicelist'))
|
devices.add_url_rule('/device/', view_func=DeviceListView.as_view('devicelist'))
|
||||||
devices.add_url_rule('/device/<string:id>/', view_func=DeviceDetailView.as_view('device_details'))
|
devices.add_url_rule('/device/<string:id>/', view_func=DeviceDetailView.as_view('device_details'))
|
||||||
devices.add_url_rule('/lot/<string:lot_id>/device/', view_func=DeviceListView.as_view('lotdevicelist'))
|
devices.add_url_rule('/lot/<string:lot_id>/device/', view_func=DeviceListView.as_view('lotdevicelist'))
|
||||||
|
|
|
@ -26,12 +26,15 @@ class ReportHash(db.Model):
|
||||||
hash3.comment = """The normalized name of the hash."""
|
hash3.comment = """The normalized name of the hash."""
|
||||||
|
|
||||||
|
|
||||||
def insert_hash(bfile):
|
def insert_hash(bfile, commit=True):
|
||||||
hash3 = hashlib.sha3_256(bfile).hexdigest()
|
hash3 = hashlib.sha3_256(bfile).hexdigest()
|
||||||
db_hash = ReportHash(hash3=hash3)
|
db_hash = ReportHash(hash3=hash3)
|
||||||
db.session.add(db_hash)
|
db.session.add(db_hash)
|
||||||
db.session.commit()
|
if commit:
|
||||||
db.session.flush()
|
db.session.commit()
|
||||||
|
db.session.flush()
|
||||||
|
|
||||||
|
return hash3
|
||||||
|
|
||||||
|
|
||||||
def verify_hash(bfile):
|
def verify_hash(bfile):
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
var show_action_form = $("#allocateModal").data('show-action-form');
|
var show_allocate_form = $("#allocateModal").data('show-action-form');
|
||||||
if (show_action_form != "None") {
|
var show_datawipe_form = $("#datawipeModal").data('show-action-form');
|
||||||
|
if (show_allocate_form != "None") {
|
||||||
$("#allocateModal .btn-primary").show();
|
$("#allocateModal .btn-primary").show();
|
||||||
newAllocate(show_action_form);
|
newAllocate(show_allocate_form);
|
||||||
|
} else if (show_datawipe_form != "None") {
|
||||||
|
$("#datawipeModal .btn-primary").show();
|
||||||
|
newDataWipe(show_datawipe_form);
|
||||||
} else {
|
} else {
|
||||||
$(".deviceSelect").on("change", deviceSelect);
|
$(".deviceSelect").on("change", deviceSelect);
|
||||||
}
|
}
|
||||||
|
@ -26,6 +30,9 @@ function deviceSelect() {
|
||||||
|
|
||||||
$("#allocateModal .pol").show();
|
$("#allocateModal .pol").show();
|
||||||
$("#allocateModal .btn-primary").hide();
|
$("#allocateModal .btn-primary").hide();
|
||||||
|
|
||||||
|
$("#datawipeModal .pol").show();
|
||||||
|
$("#datawipeModal .btn-primary").hide();
|
||||||
} else {
|
} else {
|
||||||
$("#addingLotModal .pol").hide();
|
$("#addingLotModal .pol").hide();
|
||||||
$("#addingLotModal .btn-primary").show();
|
$("#addingLotModal .btn-primary").show();
|
||||||
|
@ -39,6 +46,9 @@ function deviceSelect() {
|
||||||
$("#allocateModal .pol").hide();
|
$("#allocateModal .pol").hide();
|
||||||
$("#allocateModal .btn-primary").show();
|
$("#allocateModal .btn-primary").show();
|
||||||
|
|
||||||
|
$("#datawipeModal .pol").hide();
|
||||||
|
$("#datawipeModal .btn-primary").show();
|
||||||
|
|
||||||
$("#addingTagModal .pol").hide();
|
$("#addingTagModal .pol").hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,11 +79,20 @@ function newAllocate(action) {
|
||||||
$("#activeAllocateModal").click();
|
$("#activeAllocateModal").click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function newDataWipe(action) {
|
||||||
|
$("#datawipeModal #type").val(action);
|
||||||
|
$("#datawipeModal #title-action").html(action);
|
||||||
|
get_device_list();
|
||||||
|
deviceSelect();
|
||||||
|
$("#activeDatawipeModal").click();
|
||||||
|
}
|
||||||
|
|
||||||
function get_device_list() {
|
function get_device_list() {
|
||||||
var devices = $(".deviceSelect").filter(':checked');
|
var devices = $(".deviceSelect").filter(':checked');
|
||||||
|
|
||||||
/* Insert the correct count of devices in actions form */
|
/* Insert the correct count of devices in actions form */
|
||||||
var devices_count = devices.length;
|
var devices_count = devices.length;
|
||||||
|
$("#datawipeModal .devices-count").html(devices_count);
|
||||||
$("#allocateModal .devices-count").html(devices_count);
|
$("#allocateModal .devices-count").html(devices_count);
|
||||||
$("#actionModal .devices-count").html(devices_count);
|
$("#actionModal .devices-count").html(devices_count);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
<div class="modal fade" id="datawipeModal" tabindex="-1" style="display: none;" aria-hidden="true"
|
||||||
|
data-show-action-form="{{ form_new_datawipe.type.data }}">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">New Action <span id="title-action"></span></h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="{{ url_for('inventory.devices.datawipe_add') }}" method="post" enctype="multipart/form-data">
|
||||||
|
{{ form_new_datawipe.csrf_token }}
|
||||||
|
<div class="modal-body">
|
||||||
|
{% for field in form_new_datawipe %}
|
||||||
|
{% if field != form_new_datawipe.csrf_token %}
|
||||||
|
{% if field == form_new_datawipe.devices %}
|
||||||
|
<div class="col-12">
|
||||||
|
{{ field.label(class_="form-label") }}: <span class="devices-count"></span>
|
||||||
|
{{ field(class_="devicesList") }}
|
||||||
|
<p class="text-danger pol" style="display: none;">
|
||||||
|
You need select first some device before to do one action
|
||||||
|
</p>
|
||||||
|
<p class="enumeration-devices"></p>
|
||||||
|
</div>
|
||||||
|
{% elif field == form_new_datawipe.lot %}
|
||||||
|
{{ field }}
|
||||||
|
{% elif field == form_new_datawipe.type %}
|
||||||
|
{{ field }}
|
||||||
|
{% elif field == form_new_datawipe.document %}
|
||||||
|
{% for _field in field %}
|
||||||
|
<div class="col-12">
|
||||||
|
{{ _field.label(class_="form-label") }}
|
||||||
|
{% if _field == field.success %}
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
{{ _field(class_="form-check-input") }}
|
||||||
|
<small class="text-muted">{{ _field.description }}</small>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{{ _field(class_="form-control") }}
|
||||||
|
<small class="text-muted">{{ _field.description }}</small>
|
||||||
|
{% endif %}
|
||||||
|
{% if _field.errors %}
|
||||||
|
<p class="text-danger">
|
||||||
|
{% for error in _field.errors %}
|
||||||
|
{{ error }}<br/>
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<div class="col-12">
|
||||||
|
{{ field.label(class_="form-label") }}
|
||||||
|
{% if field == form_new_datawipe.success %}
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
{{ field(class_="form-check-input") }}
|
||||||
|
<small class="text-muted">{{ field.description }}</small>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{{ field(class_="form-control") }}
|
||||||
|
<small class="text-muted">{{ field.description }}</small>
|
||||||
|
{% endif %}
|
||||||
|
{% if field.errors %}
|
||||||
|
<p class="text-danger">
|
||||||
|
{% for error in field.errors %}
|
||||||
|
{{ error }}<br/>
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
<input type="submit" class="btn btn-primary" style="display: none;" value="Create" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -80,6 +80,7 @@
|
||||||
</button>
|
</button>
|
||||||
<span class="d-none" id="activeActionModal" data-bs-toggle="modal" data-bs-target="#actionModal"></span>
|
<span class="d-none" id="activeActionModal" data-bs-toggle="modal" data-bs-target="#actionModal"></span>
|
||||||
<span class="d-none" id="activeAllocateModal" data-bs-toggle="modal" data-bs-target="#allocateModal"></span>
|
<span class="d-none" id="activeAllocateModal" data-bs-toggle="modal" data-bs-target="#allocateModal"></span>
|
||||||
|
<span class="d-none" id="activeDatawipeModal" data-bs-toggle="modal" data-bs-target="#datawipeModal"></span>
|
||||||
<ul class="dropdown-menu" aria-labelledby="btnActions">
|
<ul class="dropdown-menu" aria-labelledby="btnActions">
|
||||||
<li>
|
<li>
|
||||||
Status actions
|
Status actions
|
||||||
|
@ -139,7 +140,7 @@
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="javascript:void()" class="dropdown-item">
|
<a href="javascript:newDataWipe('DataWipe')" class="dropdown-item">
|
||||||
<i class="bi bi-eraser-fill"></i>
|
<i class="bi bi-eraser-fill"></i>
|
||||||
DataWipe
|
DataWipe
|
||||||
</a>
|
</a>
|
||||||
|
@ -280,6 +281,7 @@
|
||||||
{% include "inventory/removelot.html" %}
|
{% include "inventory/removelot.html" %}
|
||||||
{% include "inventory/actions.html" %}
|
{% include "inventory/actions.html" %}
|
||||||
{% include "inventory/allocate.html" %}
|
{% include "inventory/allocate.html" %}
|
||||||
|
{% include "inventory/data_wipe.html" %}
|
||||||
|
|
||||||
<!-- CDN -->
|
<!-- CDN -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/simple-datatables@latest"></script>
|
<script src="https://cdn.jsdelivr.net/npm/simple-datatables@latest"></script>
|
||||||
|
|
Reference in New Issue