refactor
This commit is contained in:
parent
33fc69013f
commit
0fb4fa5ba6
|
@ -42,6 +42,7 @@ from ereuse_devicehub.parser.models import PlaceholdersLog, SnapshotsLog
|
||||||
from ereuse_devicehub.parser.parser import ParseSnapshotLsHw
|
from ereuse_devicehub.parser.parser import ParseSnapshotLsHw
|
||||||
from ereuse_devicehub.parser.schemas import Snapshot_lite
|
from ereuse_devicehub.parser.schemas import Snapshot_lite
|
||||||
from ereuse_devicehub.resources.action.models import Snapshot, Trade
|
from ereuse_devicehub.resources.action.models import Snapshot, Trade
|
||||||
|
from ereuse_devicehub.resources.action.schemas import EWaste as EWasteSchema
|
||||||
from ereuse_devicehub.resources.action.schemas import Snapshot as SnapshotSchema
|
from ereuse_devicehub.resources.action.schemas import Snapshot as SnapshotSchema
|
||||||
from ereuse_devicehub.resources.action.views.snapshot import (
|
from ereuse_devicehub.resources.action.views.snapshot import (
|
||||||
SnapshotMixin,
|
SnapshotMixin,
|
||||||
|
@ -856,6 +857,12 @@ class ActionFormMixin(FlaskForm):
|
||||||
|
|
||||||
self.populate_obj(self.instance)
|
self.populate_obj(self.instance)
|
||||||
db.session.add(self.instance)
|
db.session.add(self.instance)
|
||||||
|
|
||||||
|
if self.instance.type == 'EWaste':
|
||||||
|
ewaste = EWasteSchema().dump(self.instance)
|
||||||
|
doc = "{}".format(ewaste)
|
||||||
|
self.instance.register_proof(doc)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
self.devices.data = devices
|
self.devices.data = devices
|
||||||
|
@ -1212,7 +1219,6 @@ class TradeForm(ActionFormMixin):
|
||||||
or email_to == email_from
|
or email_to == email_from
|
||||||
or g.user.email not in [email_from, email_to]
|
or g.user.email not in [email_from, email_to]
|
||||||
):
|
):
|
||||||
|
|
||||||
errors = ["If you want confirm, you need a correct email"]
|
errors = ["If you want confirm, you need a correct email"]
|
||||||
self.user_to.errors = errors
|
self.user_to.errors = errors
|
||||||
self.user_from.errors = errors
|
self.user_from.errors = errors
|
||||||
|
@ -1918,7 +1924,6 @@ class UploadPlaceholderForm(FlaskForm):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
|
|
||||||
for device, placeholder_log in self.placeholders:
|
for device, placeholder_log in self.placeholders:
|
||||||
db.session.add(device)
|
db.session.add(device)
|
||||||
db.session.add(placeholder_log)
|
db.session.add(placeholder_log)
|
||||||
|
@ -1947,7 +1952,6 @@ class EditPlaceholderForm(FlaskForm):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
|
|
||||||
for device in self.placeholders:
|
for device in self.placeholders:
|
||||||
db.session.add(device)
|
db.session.add(device)
|
||||||
|
|
||||||
|
|
|
@ -45,13 +45,14 @@ def upgrade():
|
||||||
sa.Column('type', sa.Unicode(), nullable=False),
|
sa.Column('type', sa.Unicode(), nullable=False),
|
||||||
sa.Column('documentId', citext.CIText(), nullable=True),
|
sa.Column('documentId', citext.CIText(), nullable=True),
|
||||||
sa.Column('documentSignature', citext.CIText(), nullable=True),
|
sa.Column('documentSignature', citext.CIText(), nullable=True),
|
||||||
|
sa.Column('normalizeDoc', citext.CIText(), nullable=True),
|
||||||
sa.Column('timestamp', sa.BigInteger(), nullable=False),
|
sa.Column('timestamp', sa.BigInteger(), nullable=False),
|
||||||
sa.Column('device_id', sa.BigInteger(), nullable=False),
|
sa.Column('device_id', sa.BigInteger(), nullable=False),
|
||||||
sa.Column('snapshot_id', postgresql.UUID(as_uuid=True), nullable=False),
|
sa.Column('action_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||||
sa.Column('issuer_id', postgresql.UUID(as_uuid=True), nullable=False),
|
sa.Column('issuer_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||||
sa.ForeignKeyConstraint(
|
sa.ForeignKeyConstraint(
|
||||||
['snapshot_id'],
|
['action_id'],
|
||||||
[f'{get_inv()}.snapshot.id'],
|
[f'{get_inv()}.action.id'],
|
||||||
),
|
),
|
||||||
sa.ForeignKeyConstraint(
|
sa.ForeignKeyConstraint(
|
||||||
['device_id'],
|
['device_id'],
|
||||||
|
|
|
@ -6,7 +6,7 @@ from sqlalchemy.dialects.postgresql import UUID
|
||||||
from sqlalchemy.orm import backref, relationship
|
from sqlalchemy.orm import backref, relationship
|
||||||
from sqlalchemy.util import OrderedSet
|
from sqlalchemy.util import OrderedSet
|
||||||
|
|
||||||
from ereuse_devicehub.resources.action.models import Snapshot
|
from ereuse_devicehub.resources.action.models import Action, Snapshot
|
||||||
from ereuse_devicehub.resources.device.models import Device
|
from ereuse_devicehub.resources.device.models import Device
|
||||||
from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing
|
from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing
|
||||||
from ereuse_devicehub.resources.user.models import User
|
from ereuse_devicehub.resources.user.models import User
|
||||||
|
@ -17,6 +17,7 @@ PROOF_ENUM = {
|
||||||
'IssueDPP': 'IssueDPP',
|
'IssueDPP': 'IssueDPP',
|
||||||
'proof_of_recycling': 'proof_of_recycling',
|
'proof_of_recycling': 'proof_of_recycling',
|
||||||
'Erase': 'Erase',
|
'Erase': 'Erase',
|
||||||
|
'EWaste': 'EWaste',
|
||||||
}
|
}
|
||||||
|
|
||||||
_sorted_proofs = {'order_by': lambda: Proof.created, 'collection_class': SortedSet}
|
_sorted_proofs = {'order_by': lambda: Proof.created, 'collection_class': SortedSet}
|
||||||
|
@ -30,6 +31,7 @@ class Proof(Thing):
|
||||||
documentId.comment = "is the uuid of snapshot"
|
documentId.comment = "is the uuid of snapshot"
|
||||||
documentSignature = Column(CIText(), nullable=True)
|
documentSignature = Column(CIText(), nullable=True)
|
||||||
documentSignature.comment = "is the snapshot.json_hw with the signature of the user"
|
documentSignature.comment = "is the snapshot.json_hw with the signature of the user"
|
||||||
|
normalizeDoc = Column(CIText(), nullable=True)
|
||||||
timestamp = Column(BigInteger, nullable=False)
|
timestamp = Column(BigInteger, nullable=False)
|
||||||
type = Column(Unicode(STR_SM_SIZE), nullable=False)
|
type = Column(Unicode(STR_SM_SIZE), nullable=False)
|
||||||
|
|
||||||
|
@ -52,12 +54,12 @@ class Proof(Thing):
|
||||||
primaryjoin=Device.id == device_id,
|
primaryjoin=Device.id == device_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
snapshot_id = Column(UUID(as_uuid=True), ForeignKey(Snapshot.id), nullable=True)
|
action_id = Column(UUID(as_uuid=True), ForeignKey(Action.id), nullable=True)
|
||||||
snapshot = relationship(
|
action = relationship(
|
||||||
Snapshot,
|
Action,
|
||||||
backref=backref('proofs', lazy=True),
|
backref=backref('proofs', lazy=True),
|
||||||
collection_class=OrderedSet,
|
collection_class=OrderedSet,
|
||||||
primaryjoin=Snapshot.id == snapshot_id,
|
primaryjoin=Action.id == action_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -216,6 +216,11 @@ class ReadyDef(ActionDef):
|
||||||
SCHEMA = schemas.Ready
|
SCHEMA = schemas.Ready
|
||||||
|
|
||||||
|
|
||||||
|
class EWasteDef(ActionDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.EWaste
|
||||||
|
|
||||||
|
|
||||||
class RecyclingDef(ActionDef):
|
class RecyclingDef(ActionDef):
|
||||||
VIEW = None
|
VIEW = None
|
||||||
SCHEMA = schemas.Recycling
|
SCHEMA = schemas.Recycling
|
||||||
|
|
|
@ -493,9 +493,7 @@ class EraseBasic(JoinedWithOneDeviceMixin, ActionWithOneDevice):
|
||||||
return self.parent.phid()
|
return self.parent.phid()
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
def register_proof(self):
|
def connect_api(self):
|
||||||
"""This method is used for register a proof of erasure en dlt."""
|
|
||||||
|
|
||||||
if 'dpp' not in app.blueprints.keys() or not self.snapshot:
|
if 'dpp' not in app.blueprints.keys() or not self.snapshot:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -504,21 +502,25 @@ class EraseBasic(JoinedWithOneDeviceMixin, ActionWithOneDevice):
|
||||||
|
|
||||||
token_dlt = session.get('token_dlt')
|
token_dlt = session.get('token_dlt')
|
||||||
api_dlt = app.config.get('API_DLT')
|
api_dlt = app.config.get('API_DLT')
|
||||||
dh_instance = app.config.get('ID_FEDERATED', 'dh1')
|
|
||||||
if not token_dlt or not api_dlt:
|
if not token_dlt or not api_dlt:
|
||||||
return
|
return
|
||||||
|
|
||||||
api = API(api_dlt, token_dlt, "ethereum")
|
return API(api_dlt, token_dlt, "ethereum")
|
||||||
|
|
||||||
from ereuse_devicehub.modules.dpp.models import PROOF_ENUM, Proof
|
def register_proof(self):
|
||||||
|
"""This method is used for register a proof of erasure en dlt."""
|
||||||
|
from ereuse_devicehub.modules.dpp.models import PROOF_ENUM
|
||||||
|
|
||||||
deviceCHID = self.device.chid
|
deviceCHID = self.device.chid
|
||||||
docSig = self.snapshot.phid_dpp
|
docHash = self.snapshot.phid_dpp
|
||||||
docHash = docSig
|
|
||||||
docHashAlgorithm = 'sha3_256'
|
docHashAlgorithm = 'sha3_256'
|
||||||
docID = "{}".format(self.snapshot.uuid or '')
|
|
||||||
issuerID = "{dh}:{user}".format(dh=dh_instance, user=g.user.id)
|
|
||||||
proof_type = PROOF_ENUM['Erase']
|
proof_type = PROOF_ENUM['Erase']
|
||||||
|
dh_instance = app.config.get('ID_FEDERATED', 'dh1')
|
||||||
|
|
||||||
|
api = self.connect_api()
|
||||||
|
if not api:
|
||||||
|
return
|
||||||
|
|
||||||
result = api.generate_proof(
|
result = api.generate_proof(
|
||||||
deviceCHID,
|
deviceCHID,
|
||||||
docHashAlgorithm,
|
docHashAlgorithm,
|
||||||
|
@ -527,16 +529,21 @@ class EraseBasic(JoinedWithOneDeviceMixin, ActionWithOneDevice):
|
||||||
dh_instance,
|
dh_instance,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.register_erase_proof(result)
|
||||||
|
|
||||||
|
def register_erase_proof(self, result):
|
||||||
|
from ereuse_devicehub.modules.dpp.models import PROOF_ENUM, Proof
|
||||||
from ereuse_devicehub.resources.enums import StatusCode
|
from ereuse_devicehub.resources.enums import StatusCode
|
||||||
|
|
||||||
if result['Status'] == StatusCode.Success.value:
|
if result['Status'] == StatusCode.Success.value:
|
||||||
timestamp = (
|
timestamp = result.get('Data', {}).get('data', {}).get('timestamp')
|
||||||
result.get('Data', {}).get('data', {}).get('timestamp', time.time())
|
if not timestamp:
|
||||||
)
|
return
|
||||||
|
|
||||||
d = {
|
d = {
|
||||||
"type": PROOF_ENUM['Erase'],
|
"type": PROOF_ENUM['Erase'],
|
||||||
"device": self.device,
|
"device": self.device,
|
||||||
"snapshot": self.snapshot,
|
"action": self.snapshot,
|
||||||
"timestamp": timestamp,
|
"timestamp": timestamp,
|
||||||
"issuer_id": g.user.id,
|
"issuer_id": g.user.id,
|
||||||
}
|
}
|
||||||
|
@ -874,7 +881,6 @@ class Snapshot(JoinedWithOneDeviceMixin, ActionWithOneDevice):
|
||||||
return hdds
|
return hdds
|
||||||
|
|
||||||
def get_new_device(self):
|
def get_new_device(self):
|
||||||
|
|
||||||
if not self.device:
|
if not self.device:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
@ -983,7 +989,7 @@ class BenchmarkDataStorage(Benchmark):
|
||||||
write_speed = Column(Float(decimal_return_scale=2), nullable=False)
|
write_speed = Column(Float(decimal_return_scale=2), nullable=False)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return 'Read: {0:.2f} MB/s, write: {0:.2f} MB/s'.format(
|
return 'Read: {0:.2f} MB/s, write: {0:.2f} MB/s'.format( # noqa: F523
|
||||||
self.read_speed, self.write_speed
|
self.read_speed, self.write_speed
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1711,6 +1717,72 @@ class Ready(ActionWithMultipleDevices):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class EWaste(ActionWithMultipleDevices):
|
||||||
|
"""The device is declared as e-waste, this device is not allow use more.
|
||||||
|
|
||||||
|
Any people can declared as e-waste one device.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def register_proof(self, doc):
|
||||||
|
"""This method is used for register a proof of erasure en dlt."""
|
||||||
|
|
||||||
|
if 'dpp' not in app.blueprints.keys():
|
||||||
|
return
|
||||||
|
|
||||||
|
if not session.get('token_dlt'):
|
||||||
|
return
|
||||||
|
|
||||||
|
if not doc:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.doc = doc
|
||||||
|
token_dlt = session.get('token_dlt')
|
||||||
|
api_dlt = app.config.get('API_DLT')
|
||||||
|
dh_instance = app.config.get('ID_FEDERATED', 'dh1')
|
||||||
|
if not token_dlt or not api_dlt:
|
||||||
|
return
|
||||||
|
|
||||||
|
api = API(api_dlt, token_dlt, "ethereum")
|
||||||
|
|
||||||
|
from ereuse_devicehub.modules.dpp.models import PROOF_ENUM, Proof
|
||||||
|
from ereuse_devicehub.resources.enums import StatusCode
|
||||||
|
|
||||||
|
for device in self.devices:
|
||||||
|
deviceCHID = device.chid
|
||||||
|
docHash = self.generateDocSig()
|
||||||
|
docHashAlgorithm = 'sha3_256'
|
||||||
|
proof_type = PROOF_ENUM['EWaste']
|
||||||
|
result = api.generate_proof(
|
||||||
|
deviceCHID,
|
||||||
|
docHashAlgorithm,
|
||||||
|
docHash,
|
||||||
|
proof_type,
|
||||||
|
dh_instance,
|
||||||
|
)
|
||||||
|
|
||||||
|
if result['Status'] == StatusCode.Success.value:
|
||||||
|
timestamp = result.get('Data', {}).get('data', {}).get('timestamp')
|
||||||
|
|
||||||
|
if not timestamp:
|
||||||
|
return
|
||||||
|
|
||||||
|
d = {
|
||||||
|
"type": PROOF_ENUM['EWaste'],
|
||||||
|
"device": device,
|
||||||
|
"action": self,
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"issuer_id": g.user.id,
|
||||||
|
"normalizeDoc": self.doc,
|
||||||
|
}
|
||||||
|
proof = Proof(**d)
|
||||||
|
db.session.add(proof)
|
||||||
|
|
||||||
|
def generateDocSig(self):
|
||||||
|
if not self.doc:
|
||||||
|
return
|
||||||
|
return hashlib.sha3_256(self.doc.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
class ToPrepare(ActionWithMultipleDevices):
|
class ToPrepare(ActionWithMultipleDevices):
|
||||||
"""The device has been selected for preparation.
|
"""The device has been selected for preparation.
|
||||||
|
|
||||||
|
|
|
@ -523,6 +523,10 @@ class Ready(ActionWithMultipleDevicesCheckingOwner):
|
||||||
__doc__ = m.Ready.__doc__
|
__doc__ = m.Ready.__doc__
|
||||||
|
|
||||||
|
|
||||||
|
class EWaste(ActionWithMultipleDevicesCheckingOwner):
|
||||||
|
__doc__ = m.EWaste.__doc__
|
||||||
|
|
||||||
|
|
||||||
class ActionStatus(Action):
|
class ActionStatus(Action):
|
||||||
rol_user = NestedOn(s_user.User, dump_only=True, exclude=('token',))
|
rol_user = NestedOn(s_user.User, dump_only=True, exclude=('token',))
|
||||||
devices = NestedOn(
|
devices = NestedOn(
|
||||||
|
|
|
@ -4,7 +4,6 @@ import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import time
|
|
||||||
import uuid
|
import uuid
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from fractions import Fraction
|
from fractions import Fraction
|
||||||
|
@ -484,7 +483,8 @@ class Device(Thing):
|
||||||
"""The trading state, or None if no Trade action has
|
"""The trading state, or None if no Trade action has
|
||||||
ever been performed to this device. This extract the posibilities for to do.
|
ever been performed to this device. This extract the posibilities for to do.
|
||||||
This method is performed for show in the web.
|
This method is performed for show in the web.
|
||||||
If you need to do one simple and generic response you can put simple=True for that."""
|
If you need to do one simple and generic response you can put simple=True for that.
|
||||||
|
"""
|
||||||
if not hasattr(lot, 'trade'):
|
if not hasattr(lot, 'trade'):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -939,7 +939,7 @@ class Device(Thing):
|
||||||
}
|
}
|
||||||
return types.get(self.type, '')
|
return types.get(self.type, '')
|
||||||
|
|
||||||
def register_dlt(self):
|
def connect_api(self):
|
||||||
if 'dpp' not in app.blueprints.keys() or not self.hid:
|
if 'dpp' not in app.blueprints.keys() or not self.hid:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -951,20 +951,43 @@ class Device(Thing):
|
||||||
if not token_dlt or not api_dlt:
|
if not token_dlt or not api_dlt:
|
||||||
return
|
return
|
||||||
|
|
||||||
api = API(api_dlt, token_dlt, "ethereum")
|
return API(api_dlt, token_dlt, "ethereum")
|
||||||
|
|
||||||
|
def register_dlt(self):
|
||||||
|
api = self.connect_api()
|
||||||
|
if not api:
|
||||||
|
return
|
||||||
|
|
||||||
result = api.register_device(self.chid)
|
result = api.register_device(self.chid)
|
||||||
|
self.register_proof(result)
|
||||||
|
|
||||||
|
if app.config.get('ID_FEDERATED'):
|
||||||
|
api.add_service(
|
||||||
|
self.chid,
|
||||||
|
'DeviceHub',
|
||||||
|
app.config.get('ID_FEDERATED'),
|
||||||
|
'Inventory service',
|
||||||
|
'Inv',
|
||||||
|
)
|
||||||
|
|
||||||
|
def register_proof(self, result):
|
||||||
from ereuse_devicehub.modules.dpp.models import PROOF_ENUM, Proof
|
from ereuse_devicehub.modules.dpp.models import PROOF_ENUM, Proof
|
||||||
from ereuse_devicehub.resources.enums import StatusCode
|
from ereuse_devicehub.resources.enums import StatusCode
|
||||||
|
|
||||||
if result['Status'] == StatusCode.Success.value:
|
if result['Status'] == StatusCode.Success.value:
|
||||||
timestamp = (
|
timestamp = result.get('Data', {}).get('data', {}).get('timestamp')
|
||||||
result.get('Data', {}).get('data', {}).get('timestamp', time.time())
|
|
||||||
)
|
if not timestamp:
|
||||||
|
return
|
||||||
|
|
||||||
|
snapshot = [x for x in self.actions if x.t == 'Snapshot']
|
||||||
|
if not snapshot:
|
||||||
|
return
|
||||||
|
|
||||||
d = {
|
d = {
|
||||||
"type": PROOF_ENUM['Register'],
|
"type": PROOF_ENUM['Register'],
|
||||||
"device": self,
|
"device": self,
|
||||||
"snapshot": [x for x in self.actions if x.t == 'Snapshot'][0],
|
"actions": snapshot[0],
|
||||||
"timestamp": timestamp,
|
"timestamp": timestamp,
|
||||||
"issuer_id": g.user.id,
|
"issuer_id": g.user.id,
|
||||||
}
|
}
|
||||||
|
@ -978,15 +1001,6 @@ class Device(Thing):
|
||||||
if isinstance(c, DataStorage):
|
if isinstance(c, DataStorage):
|
||||||
c.register_dlt()
|
c.register_dlt()
|
||||||
|
|
||||||
if app.config.get('ID_FEDERATED'):
|
|
||||||
api.add_service(
|
|
||||||
self.chid,
|
|
||||||
'DeviceHub',
|
|
||||||
app.config.get('ID_FEDERATED'),
|
|
||||||
'Inventory service',
|
|
||||||
'Inv',
|
|
||||||
)
|
|
||||||
|
|
||||||
def unreliable(self):
|
def unreliable(self):
|
||||||
self.user_trusts = False
|
self.user_trusts = False
|
||||||
i = 0
|
i = 0
|
||||||
|
@ -1302,6 +1316,14 @@ class Placeholder(Thing):
|
||||||
return docs.union(self.binding.documents)
|
return docs.union(self.binding.documents)
|
||||||
return docs
|
return docs
|
||||||
|
|
||||||
|
@property
|
||||||
|
def proofs(self):
|
||||||
|
proofs = [p for p in self.device.proofs]
|
||||||
|
if self.binding:
|
||||||
|
proofs.extend([p for p in self.binding.proofs])
|
||||||
|
proofs.sort(key=lambda x: x.created, reverse=True)
|
||||||
|
return proofs
|
||||||
|
|
||||||
|
|
||||||
class Computer(Device):
|
class Computer(Device):
|
||||||
"""A chassis with components inside that can be processed
|
"""A chassis with components inside that can be processed
|
||||||
|
|
|
@ -390,10 +390,9 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if placeholder.binding %}
|
|
||||||
<div class="tab-pane fade profile-overview" id="proofs">
|
<div class="tab-pane fade profile-overview" id="proofs">
|
||||||
<h5 class="card-title">Proofs</h5>
|
<h5 class="card-title">Proofs</h5>
|
||||||
{% for proof in placeholder.binding.proofs %}
|
{% for proof in placeholder.proofs %}
|
||||||
<div class="list-group col-12">
|
<div class="list-group col-12">
|
||||||
<div class="list-group-item d-flex justify-content-between align-items-center">
|
<div class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -436,7 +435,6 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -226,6 +226,12 @@
|
||||||
Ready
|
Ready
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="javascript:newAction('EWaste')" class="dropdown-item">
|
||||||
|
<i class="bi bi-trash-fill"></i>
|
||||||
|
E-Waste
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Reference in a new issue